Passed
Push — master ( 711fef...2bfcff )
by Jeroen De
04:06 queued 27s
created
SCSS/vendor/scssphp/scssphp/src/Formatter/Expanded.php 2 patches
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -22,48 +22,48 @@
 block discarded – undo
22 22
  */
23 23
 class Expanded extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '  ';
32
-        $this->break = "\n";
33
-        $this->open = ' {';
34
-        $this->close = '}';
35
-        $this->tagSeparator = ', ';
36
-        $this->assignSeparator = ': ';
37
-        $this->keepSemicolons = true;
38
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '  ';
32
+		$this->break = "\n";
33
+		$this->open = ' {';
34
+		$this->close = '}';
35
+		$this->tagSeparator = ', ';
36
+		$this->assignSeparator = ': ';
37
+		$this->keepSemicolons = true;
38
+	}
39 39
 
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    protected function indentStr()
44
-    {
45
-        return str_repeat($this->indentChar, $this->indentLevel);
46
-    }
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	protected function indentStr()
44
+	{
45
+		return str_repeat($this->indentChar, $this->indentLevel);
46
+	}
47 47
 
48
-    /**
49
-     * {@inheritdoc}
50
-     */
51
-    protected function blockLines(OutputBlock $block)
52
-    {
53
-        $inner = $this->indentStr();
48
+	/**
49
+	 * {@inheritdoc}
50
+	 */
51
+	protected function blockLines(OutputBlock $block)
52
+	{
53
+		$inner = $this->indentStr();
54 54
 
55
-        $glue = $this->break . $inner;
55
+		$glue = $this->break . $inner;
56 56
 
57
-        foreach ($block->lines as $index => $line) {
58
-            if (substr($line, 0, 2) === '/*') {
59
-                $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
60
-            }
61
-        }
57
+		foreach ($block->lines as $index => $line) {
58
+			if (substr($line, 0, 2) === '/*') {
59
+				$block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
60
+			}
61
+		}
62 62
 
63
-        $this->write($inner . implode($glue, $block->lines));
63
+		$this->write($inner . implode($glue, $block->lines));
64 64
 
65
-        if (empty($block->selectors) || ! empty($block->children)) {
66
-            $this->write($this->break);
67
-        }
68
-    }
65
+		if (empty($block->selectors) || ! empty($block->children)) {
66
+			$this->write($this->break);
67
+		}
68
+	}
69 69
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -62,7 +62,7 @@
 block discarded – undo
62 62
 
63 63
         $this->write($inner . implode($glue, $block->lines));
64 64
 
65
-        if (empty($block->selectors) || ! empty($block->children)) {
65
+        if (empty($block->selectors) || !empty($block->children)) {
66 66
             $this->write($this->break);
67 67
         }
68 68
     }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -19,48 +19,48 @@
 block discarded – undo
19 19
  */
20 20
 class OutputBlock
21 21
 {
22
-    /**
23
-     * @var string
24
-     */
25
-    public $type;
22
+	/**
23
+	 * @var string
24
+	 */
25
+	public $type;
26 26
 
27
-    /**
28
-     * @var integer
29
-     */
30
-    public $depth;
27
+	/**
28
+	 * @var integer
29
+	 */
30
+	public $depth;
31 31
 
32
-    /**
33
-     * @var array
34
-     */
35
-    public $selectors;
32
+	/**
33
+	 * @var array
34
+	 */
35
+	public $selectors;
36 36
 
37
-    /**
38
-     * @var array
39
-     */
40
-    public $lines;
37
+	/**
38
+	 * @var array
39
+	 */
40
+	public $lines;
41 41
 
42
-    /**
43
-     * @var array
44
-     */
45
-    public $children;
42
+	/**
43
+	 * @var array
44
+	 */
45
+	public $children;
46 46
 
47
-    /**
48
-     * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
49
-     */
50
-    public $parent;
47
+	/**
48
+	 * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
49
+	 */
50
+	public $parent;
51 51
 
52
-    /**
53
-     * @var string
54
-     */
55
-    public $sourceName;
52
+	/**
53
+	 * @var string
54
+	 */
55
+	public $sourceName;
56 56
 
57
-    /**
58
-     * @var integer
59
-     */
60
-    public $sourceLine;
57
+	/**
58
+	 * @var integer
59
+	 */
60
+	public $sourceLine;
61 61
 
62
-    /**
63
-     * @var integer
64
-     */
65
-    public $sourceColumn;
62
+	/**
63
+	 * @var integer
64
+	 */
65
+	public $sourceColumn;
66 66
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Nested.php 2 patches
Indentation   +205 added lines, -205 removed lines patch added patch discarded remove patch
@@ -23,209 +23,209 @@
 block discarded – undo
23 23
  */
24 24
 class Nested extends Formatter
25 25
 {
26
-    /**
27
-     * @var integer
28
-     */
29
-    private $depth;
30
-
31
-    /**
32
-     * {@inheritdoc}
33
-     */
34
-    public function __construct()
35
-    {
36
-        $this->indentLevel = 0;
37
-        $this->indentChar = '  ';
38
-        $this->break = "\n";
39
-        $this->open = ' {';
40
-        $this->close = ' }';
41
-        $this->tagSeparator = ', ';
42
-        $this->assignSeparator = ': ';
43
-        $this->keepSemicolons = true;
44
-    }
45
-
46
-    /**
47
-     * {@inheritdoc}
48
-     */
49
-    protected function indentStr()
50
-    {
51
-        $n = $this->depth - 1;
52
-
53
-        return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
54
-    }
55
-
56
-    /**
57
-     * {@inheritdoc}
58
-     */
59
-    protected function blockLines(OutputBlock $block)
60
-    {
61
-        $inner = $this->indentStr();
62
-        $glue  = $this->break . $inner;
63
-
64
-        foreach ($block->lines as $index => $line) {
65
-            if (substr($line, 0, 2) === '/*') {
66
-                $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
67
-            }
68
-        }
69
-
70
-        $this->write($inner . implode($glue, $block->lines));
71
-    }
72
-
73
-    /**
74
-     * {@inheritdoc}
75
-     */
76
-    protected function block(OutputBlock $block)
77
-    {
78
-        static $depths;
79
-        static $downLevel;
80
-        static $closeBlock;
81
-        static $previousEmpty;
82
-        static $previousHasSelector;
83
-
84
-        if ($block->type === 'root') {
85
-            $depths = [ 0 ];
86
-            $downLevel = '';
87
-            $closeBlock = '';
88
-            $this->depth = 0;
89
-            $previousEmpty = false;
90
-            $previousHasSelector = false;
91
-        }
92
-
93
-        $isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]);
94
-        $isSupport = ($block->type === Type::T_DIRECTIVE
95
-            && $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false);
96
-
97
-        while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) {
98
-            array_pop($depths);
99
-            $this->depth--;
100
-
101
-            if (
102
-                ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
-                (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
104
-            ) {
105
-                $downLevel = $this->break;
106
-            }
107
-
108
-            if (empty($block->lines) && empty($block->children)) {
109
-                $previousEmpty = true;
110
-            }
111
-        }
112
-
113
-        if (empty($block->lines) && empty($block->children)) {
114
-            return;
115
-        }
116
-
117
-        $this->currentBlock = $block;
118
-
119
-        if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
120
-            if ($block->depth > end($depths)) {
121
-                if (! $previousEmpty || $this->depth < 1) {
122
-                    $this->depth++;
123
-
124
-                    $depths[] = $block->depth;
125
-                } else {
126
-                    // keep the current depth unchanged but take the block depth as a new reference for following blocks
127
-                    array_pop($depths);
128
-
129
-                    $depths[] = $block->depth;
130
-                }
131
-            }
132
-        }
133
-
134
-        $previousEmpty = ($block->type === Type::T_COMMENT);
135
-        $previousHasSelector = false;
136
-
137
-        if (! empty($block->selectors)) {
138
-            if ($closeBlock) {
139
-                $this->write($closeBlock);
140
-                $closeBlock = '';
141
-            }
142
-
143
-            if ($downLevel) {
144
-                $this->write($downLevel);
145
-                $downLevel = '';
146
-            }
147
-
148
-            $this->blockSelectors($block);
149
-
150
-            $this->indentLevel++;
151
-        }
152
-
153
-        if (! empty($block->lines)) {
154
-            if ($closeBlock) {
155
-                $this->write($closeBlock);
156
-                $closeBlock = '';
157
-            }
158
-
159
-            if ($downLevel) {
160
-                $this->write($downLevel);
161
-                $downLevel = '';
162
-            }
163
-
164
-            $this->blockLines($block);
165
-
166
-            $closeBlock = $this->break;
167
-        }
168
-
169
-        if (! empty($block->children)) {
170
-            if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
171
-                array_pop($depths);
172
-
173
-                $this->depth--;
174
-                $this->blockChildren($block);
175
-                $this->depth++;
176
-
177
-                $depths[] = $block->depth;
178
-            } else {
179
-                $this->blockChildren($block);
180
-            }
181
-        }
182
-
183
-        // reclear to not be spoiled by children if T_DIRECTIVE
184
-        if ($block->type === Type::T_DIRECTIVE) {
185
-            $previousHasSelector = false;
186
-        }
187
-
188
-        if (! empty($block->selectors)) {
189
-            $this->indentLevel--;
190
-
191
-            if (! $this->keepSemicolons) {
192
-                $this->strippedSemicolon = '';
193
-            }
194
-
195
-            $this->write($this->close);
196
-
197
-            $closeBlock = $this->break;
198
-
199
-            if ($this->depth > 1 && ! empty($block->children)) {
200
-                array_pop($depths);
201
-                $this->depth--;
202
-            }
203
-
204
-            if (! $isMediaOrDirective) {
205
-                $previousHasSelector = true;
206
-            }
207
-        }
208
-
209
-        if ($block->type === 'root') {
210
-            $this->write($this->break);
211
-        }
212
-    }
213
-
214
-    /**
215
-     * Block has flat child
216
-     *
217
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
218
-     *
219
-     * @return boolean
220
-     */
221
-    private function hasFlatChild($block)
222
-    {
223
-        foreach ($block->children as $child) {
224
-            if (empty($child->selectors)) {
225
-                return true;
226
-            }
227
-        }
228
-
229
-        return false;
230
-    }
26
+	/**
27
+	 * @var integer
28
+	 */
29
+	private $depth;
30
+
31
+	/**
32
+	 * {@inheritdoc}
33
+	 */
34
+	public function __construct()
35
+	{
36
+		$this->indentLevel = 0;
37
+		$this->indentChar = '  ';
38
+		$this->break = "\n";
39
+		$this->open = ' {';
40
+		$this->close = ' }';
41
+		$this->tagSeparator = ', ';
42
+		$this->assignSeparator = ': ';
43
+		$this->keepSemicolons = true;
44
+	}
45
+
46
+	/**
47
+	 * {@inheritdoc}
48
+	 */
49
+	protected function indentStr()
50
+	{
51
+		$n = $this->depth - 1;
52
+
53
+		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
54
+	}
55
+
56
+	/**
57
+	 * {@inheritdoc}
58
+	 */
59
+	protected function blockLines(OutputBlock $block)
60
+	{
61
+		$inner = $this->indentStr();
62
+		$glue  = $this->break . $inner;
63
+
64
+		foreach ($block->lines as $index => $line) {
65
+			if (substr($line, 0, 2) === '/*') {
66
+				$block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
67
+			}
68
+		}
69
+
70
+		$this->write($inner . implode($glue, $block->lines));
71
+	}
72
+
73
+	/**
74
+	 * {@inheritdoc}
75
+	 */
76
+	protected function block(OutputBlock $block)
77
+	{
78
+		static $depths;
79
+		static $downLevel;
80
+		static $closeBlock;
81
+		static $previousEmpty;
82
+		static $previousHasSelector;
83
+
84
+		if ($block->type === 'root') {
85
+			$depths = [ 0 ];
86
+			$downLevel = '';
87
+			$closeBlock = '';
88
+			$this->depth = 0;
89
+			$previousEmpty = false;
90
+			$previousHasSelector = false;
91
+		}
92
+
93
+		$isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]);
94
+		$isSupport = ($block->type === Type::T_DIRECTIVE
95
+			&& $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false);
96
+
97
+		while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) {
98
+			array_pop($depths);
99
+			$this->depth--;
100
+
101
+			if (
102
+				! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
+				(($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
104
+			) {
105
+				$downLevel = $this->break;
106
+			}
107
+
108
+			if (empty($block->lines) && empty($block->children)) {
109
+				$previousEmpty = true;
110
+			}
111
+		}
112
+
113
+		if (empty($block->lines) && empty($block->children)) {
114
+			return;
115
+		}
116
+
117
+		$this->currentBlock = $block;
118
+
119
+		if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
120
+			if ($block->depth > end($depths)) {
121
+				if (! $previousEmpty || $this->depth < 1) {
122
+					$this->depth++;
123
+
124
+					$depths[] = $block->depth;
125
+				} else {
126
+					// keep the current depth unchanged but take the block depth as a new reference for following blocks
127
+					array_pop($depths);
128
+
129
+					$depths[] = $block->depth;
130
+				}
131
+			}
132
+		}
133
+
134
+		$previousEmpty = ($block->type === Type::T_COMMENT);
135
+		$previousHasSelector = false;
136
+
137
+		if (! empty($block->selectors)) {
138
+			if ($closeBlock) {
139
+				$this->write($closeBlock);
140
+				$closeBlock = '';
141
+			}
142
+
143
+			if ($downLevel) {
144
+				$this->write($downLevel);
145
+				$downLevel = '';
146
+			}
147
+
148
+			$this->blockSelectors($block);
149
+
150
+			$this->indentLevel++;
151
+		}
152
+
153
+		if (! empty($block->lines)) {
154
+			if ($closeBlock) {
155
+				$this->write($closeBlock);
156
+				$closeBlock = '';
157
+			}
158
+
159
+			if ($downLevel) {
160
+				$this->write($downLevel);
161
+				$downLevel = '';
162
+			}
163
+
164
+			$this->blockLines($block);
165
+
166
+			$closeBlock = $this->break;
167
+		}
168
+
169
+		if (! empty($block->children)) {
170
+			if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
171
+				array_pop($depths);
172
+
173
+				$this->depth--;
174
+				$this->blockChildren($block);
175
+				$this->depth++;
176
+
177
+				$depths[] = $block->depth;
178
+			} else {
179
+				$this->blockChildren($block);
180
+			}
181
+		}
182
+
183
+		// reclear to not be spoiled by children if T_DIRECTIVE
184
+		if ($block->type === Type::T_DIRECTIVE) {
185
+			$previousHasSelector = false;
186
+		}
187
+
188
+		if (! empty($block->selectors)) {
189
+			$this->indentLevel--;
190
+
191
+			if (! $this->keepSemicolons) {
192
+				$this->strippedSemicolon = '';
193
+			}
194
+
195
+			$this->write($this->close);
196
+
197
+			$closeBlock = $this->break;
198
+
199
+			if ($this->depth > 1 && ! empty($block->children)) {
200
+				array_pop($depths);
201
+				$this->depth--;
202
+			}
203
+
204
+			if (! $isMediaOrDirective) {
205
+				$previousHasSelector = true;
206
+			}
207
+		}
208
+
209
+		if ($block->type === 'root') {
210
+			$this->write($this->break);
211
+		}
212
+	}
213
+
214
+	/**
215
+	 * Block has flat child
216
+	 *
217
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
218
+	 *
219
+	 * @return boolean
220
+	 */
221
+	private function hasFlatChild($block)
222
+	{
223
+		foreach ($block->children as $child) {
224
+			if (empty($child->selectors)) {
225
+				return true;
226
+			}
227
+		}
228
+
229
+		return false;
230
+	}
231 231
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -82,7 +82,7 @@  discard block
 block discarded – undo
82 82
         static $previousHasSelector;
83 83
 
84 84
         if ($block->type === 'root') {
85
-            $depths = [ 0 ];
85
+            $depths = [0];
86 86
             $downLevel = '';
87 87
             $closeBlock = '';
88 88
             $this->depth = 0;
@@ -99,8 +99,8 @@  discard block
 block discarded – undo
99 99
             $this->depth--;
100 100
 
101 101
             if (
102
-                ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
-                (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
102
+                !$this->depth && ($block->depth <= 1 || (!$this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
+                (($block->selectors && !$isMediaOrDirective) || $previousHasSelector)
104 104
             ) {
105 105
                 $downLevel = $this->break;
106 106
             }
@@ -116,9 +116,9 @@  discard block
 block discarded – undo
116 116
 
117 117
         $this->currentBlock = $block;
118 118
 
119
-        if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
119
+        if (!empty($block->lines) || (!empty($block->children) && ($this->depth < 1 || $isSupport))) {
120 120
             if ($block->depth > end($depths)) {
121
-                if (! $previousEmpty || $this->depth < 1) {
121
+                if (!$previousEmpty || $this->depth < 1) {
122 122
                     $this->depth++;
123 123
 
124 124
                     $depths[] = $block->depth;
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
         $previousEmpty = ($block->type === Type::T_COMMENT);
135 135
         $previousHasSelector = false;
136 136
 
137
-        if (! empty($block->selectors)) {
137
+        if (!empty($block->selectors)) {
138 138
             if ($closeBlock) {
139 139
                 $this->write($closeBlock);
140 140
                 $closeBlock = '';
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
             $this->indentLevel++;
151 151
         }
152 152
 
153
-        if (! empty($block->lines)) {
153
+        if (!empty($block->lines)) {
154 154
             if ($closeBlock) {
155 155
                 $this->write($closeBlock);
156 156
                 $closeBlock = '';
@@ -166,8 +166,8 @@  discard block
 block discarded – undo
166 166
             $closeBlock = $this->break;
167 167
         }
168 168
 
169
-        if (! empty($block->children)) {
170
-            if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
169
+        if (!empty($block->children)) {
170
+            if ($this->depth > 0 && ($isMediaOrDirective || !$this->hasFlatChild($block))) {
171 171
                 array_pop($depths);
172 172
 
173 173
                 $this->depth--;
@@ -185,10 +185,10 @@  discard block
 block discarded – undo
185 185
             $previousHasSelector = false;
186 186
         }
187 187
 
188
-        if (! empty($block->selectors)) {
188
+        if (!empty($block->selectors)) {
189 189
             $this->indentLevel--;
190 190
 
191
-            if (! $this->keepSemicolons) {
191
+            if (!$this->keepSemicolons) {
192 192
                 $this->strippedSemicolon = '';
193 193
             }
194 194
 
@@ -196,12 +196,12 @@  discard block
 block discarded – undo
196 196
 
197 197
             $closeBlock = $this->break;
198 198
 
199
-            if ($this->depth > 1 && ! empty($block->children)) {
199
+            if ($this->depth > 1 && !empty($block->children)) {
200 200
                 array_pop($depths);
201 201
                 $this->depth--;
202 202
             }
203 203
 
204
-            if (! $isMediaOrDirective) {
204
+            if (!$isMediaOrDirective) {
205 205
                 $previousHasSelector = true;
206 206
             }
207 207
         }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Crunched.php 2 patches
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -22,59 +22,59 @@
 block discarded – undo
22 22
  */
23 23
 class Crunched extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '  ';
32
-        $this->break = '';
33
-        $this->open = '{';
34
-        $this->close = '}';
35
-        $this->tagSeparator = ',';
36
-        $this->assignSeparator = ':';
37
-        $this->keepSemicolons = false;
38
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '  ';
32
+		$this->break = '';
33
+		$this->open = '{';
34
+		$this->close = '}';
35
+		$this->tagSeparator = ',';
36
+		$this->assignSeparator = ':';
37
+		$this->keepSemicolons = false;
38
+	}
39 39
 
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    public function blockLines(OutputBlock $block)
44
-    {
45
-        $inner = $this->indentStr();
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	public function blockLines(OutputBlock $block)
44
+	{
45
+		$inner = $this->indentStr();
46 46
 
47
-        $glue = $this->break . $inner;
47
+		$glue = $this->break . $inner;
48 48
 
49
-        foreach ($block->lines as $index => $line) {
50
-            if (substr($line, 0, 2) === '/*') {
51
-                unset($block->lines[$index]);
52
-            }
53
-        }
49
+		foreach ($block->lines as $index => $line) {
50
+			if (substr($line, 0, 2) === '/*') {
51
+				unset($block->lines[$index]);
52
+			}
53
+		}
54 54
 
55
-        $this->write($inner . implode($glue, $block->lines));
55
+		$this->write($inner . implode($glue, $block->lines));
56 56
 
57
-        if (! empty($block->children)) {
58
-            $this->write($this->break);
59
-        }
60
-    }
57
+		if (! empty($block->children)) {
58
+			$this->write($this->break);
59
+		}
60
+	}
61 61
 
62
-    /**
63
-     * Output block selectors
64
-     *
65
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
66
-     */
67
-    protected function blockSelectors(OutputBlock $block)
68
-    {
69
-        $inner = $this->indentStr();
62
+	/**
63
+	 * Output block selectors
64
+	 *
65
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
66
+	 */
67
+	protected function blockSelectors(OutputBlock $block)
68
+	{
69
+		$inner = $this->indentStr();
70 70
 
71
-        $this->write(
72
-            $inner
73
-            . implode(
74
-                $this->tagSeparator,
75
-                str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
76
-            )
77
-            . $this->open . $this->break
78
-        );
79
-    }
71
+		$this->write(
72
+			$inner
73
+			. implode(
74
+				$this->tagSeparator,
75
+				str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
76
+			)
77
+			. $this->open . $this->break
78
+		);
79
+	}
80 80
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -54,7 +54,7 @@
 block discarded – undo
54 54
 
55 55
         $this->write($inner . implode($glue, $block->lines));
56 56
 
57
-        if (! empty($block->children)) {
57
+        if (!empty($block->children)) {
58 58
             $this->write($this->break);
59 59
         }
60 60
     }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Debug.php 1 patch
Indentation   +97 added lines, -97 removed lines patch added patch discarded remove patch
@@ -22,101 +22,101 @@
 block discarded – undo
22 22
  */
23 23
 class Debug extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '';
32
-        $this->break = "\n";
33
-        $this->open = ' {';
34
-        $this->close = ' }';
35
-        $this->tagSeparator = ', ';
36
-        $this->assignSeparator = ': ';
37
-        $this->keepSemicolons = true;
38
-    }
39
-
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    protected function indentStr()
44
-    {
45
-        return str_repeat('  ', $this->indentLevel);
46
-    }
47
-
48
-    /**
49
-     * {@inheritdoc}
50
-     */
51
-    protected function blockLines(OutputBlock $block)
52
-    {
53
-        $indent = $this->indentStr();
54
-
55
-        if (empty($block->lines)) {
56
-            $this->write("{$indent}block->lines: []\n");
57
-
58
-            return;
59
-        }
60
-
61
-        foreach ($block->lines as $index => $line) {
62
-            $this->write("{$indent}block->lines[{$index}]: $line\n");
63
-        }
64
-    }
65
-
66
-    /**
67
-     * {@inheritdoc}
68
-     */
69
-    protected function blockSelectors(OutputBlock $block)
70
-    {
71
-        $indent = $this->indentStr();
72
-
73
-        if (empty($block->selectors)) {
74
-            $this->write("{$indent}block->selectors: []\n");
75
-
76
-            return;
77
-        }
78
-
79
-        foreach ($block->selectors as $index => $selector) {
80
-            $this->write("{$indent}block->selectors[{$index}]: $selector\n");
81
-        }
82
-    }
83
-
84
-    /**
85
-     * {@inheritdoc}
86
-     */
87
-    protected function blockChildren(OutputBlock $block)
88
-    {
89
-        $indent = $this->indentStr();
90
-
91
-        if (empty($block->children)) {
92
-            $this->write("{$indent}block->children: []\n");
93
-
94
-            return;
95
-        }
96
-
97
-        $this->indentLevel++;
98
-
99
-        foreach ($block->children as $i => $child) {
100
-            $this->block($child);
101
-        }
102
-
103
-        $this->indentLevel--;
104
-    }
105
-
106
-    /**
107
-     * {@inheritdoc}
108
-     */
109
-    protected function block(OutputBlock $block)
110
-    {
111
-        $indent = $this->indentStr();
112
-
113
-        $this->write("{$indent}block->type: {$block->type}\n" .
114
-             "{$indent}block->depth: {$block->depth}\n");
115
-
116
-        $this->currentBlock = $block;
117
-
118
-        $this->blockSelectors($block);
119
-        $this->blockLines($block);
120
-        $this->blockChildren($block);
121
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '';
32
+		$this->break = "\n";
33
+		$this->open = ' {';
34
+		$this->close = ' }';
35
+		$this->tagSeparator = ', ';
36
+		$this->assignSeparator = ': ';
37
+		$this->keepSemicolons = true;
38
+	}
39
+
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	protected function indentStr()
44
+	{
45
+		return str_repeat('  ', $this->indentLevel);
46
+	}
47
+
48
+	/**
49
+	 * {@inheritdoc}
50
+	 */
51
+	protected function blockLines(OutputBlock $block)
52
+	{
53
+		$indent = $this->indentStr();
54
+
55
+		if (empty($block->lines)) {
56
+			$this->write("{$indent}block->lines: []\n");
57
+
58
+			return;
59
+		}
60
+
61
+		foreach ($block->lines as $index => $line) {
62
+			$this->write("{$indent}block->lines[{$index}]: $line\n");
63
+		}
64
+	}
65
+
66
+	/**
67
+	 * {@inheritdoc}
68
+	 */
69
+	protected function blockSelectors(OutputBlock $block)
70
+	{
71
+		$indent = $this->indentStr();
72
+
73
+		if (empty($block->selectors)) {
74
+			$this->write("{$indent}block->selectors: []\n");
75
+
76
+			return;
77
+		}
78
+
79
+		foreach ($block->selectors as $index => $selector) {
80
+			$this->write("{$indent}block->selectors[{$index}]: $selector\n");
81
+		}
82
+	}
83
+
84
+	/**
85
+	 * {@inheritdoc}
86
+	 */
87
+	protected function blockChildren(OutputBlock $block)
88
+	{
89
+		$indent = $this->indentStr();
90
+
91
+		if (empty($block->children)) {
92
+			$this->write("{$indent}block->children: []\n");
93
+
94
+			return;
95
+		}
96
+
97
+		$this->indentLevel++;
98
+
99
+		foreach ($block->children as $i => $child) {
100
+			$this->block($child);
101
+		}
102
+
103
+		$this->indentLevel--;
104
+	}
105
+
106
+	/**
107
+	 * {@inheritdoc}
108
+	 */
109
+	protected function block(OutputBlock $block)
110
+	{
111
+		$indent = $this->indentStr();
112
+
113
+		$this->write("{$indent}block->type: {$block->type}\n" .
114
+			 "{$indent}block->depth: {$block->depth}\n");
115
+
116
+		$this->currentBlock = $block;
117
+
118
+		$this->blockSelectors($block);
119
+		$this->blockLines($block);
120
+		$this->blockChildren($block);
121
+	}
122 122
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Compact.php 1 patch
Indentation   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -21,26 +21,26 @@
 block discarded – undo
21 21
  */
22 22
 class Compact extends Formatter
23 23
 {
24
-    /**
25
-     * {@inheritdoc}
26
-     */
27
-    public function __construct()
28
-    {
29
-        $this->indentLevel = 0;
30
-        $this->indentChar = '';
31
-        $this->break = '';
32
-        $this->open = ' {';
33
-        $this->close = "}\n\n";
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 = '';
32
+		$this->open = ' {';
33
+		$this->close = "}\n\n";
34
+		$this->tagSeparator = ',';
35
+		$this->assignSeparator = ':';
36
+		$this->keepSemicolons = true;
37
+	}
38 38
 
39
-    /**
40
-     * {@inheritdoc}
41
-     */
42
-    public function indentStr()
43
-    {
44
-        return ' ';
45
-    }
39
+	/**
40
+	 * {@inheritdoc}
41
+	 */
42
+	public function indentStr()
43
+	{
44
+		return ' ';
45
+	}
46 46
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Compressed.php 2 patches
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -22,61 +22,61 @@
 block discarded – undo
22 22
  */
23 23
 class Compressed extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '  ';
32
-        $this->break = '';
33
-        $this->open = '{';
34
-        $this->close = '}';
35
-        $this->tagSeparator = ',';
36
-        $this->assignSeparator = ':';
37
-        $this->keepSemicolons = false;
38
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '  ';
32
+		$this->break = '';
33
+		$this->open = '{';
34
+		$this->close = '}';
35
+		$this->tagSeparator = ',';
36
+		$this->assignSeparator = ':';
37
+		$this->keepSemicolons = false;
38
+	}
39 39
 
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    public function blockLines(OutputBlock $block)
44
-    {
45
-        $inner = $this->indentStr();
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	public function blockLines(OutputBlock $block)
44
+	{
45
+		$inner = $this->indentStr();
46 46
 
47
-        $glue = $this->break . $inner;
47
+		$glue = $this->break . $inner;
48 48
 
49
-        foreach ($block->lines as $index => $line) {
50
-            if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') {
51
-                unset($block->lines[$index]);
52
-            } elseif (substr($line, 0, 3) === '/*!') {
53
-                $block->lines[$index] = '/*' . substr($line, 3);
54
-            }
55
-        }
49
+		foreach ($block->lines as $index => $line) {
50
+			if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') {
51
+				unset($block->lines[$index]);
52
+			} elseif (substr($line, 0, 3) === '/*!') {
53
+				$block->lines[$index] = '/*' . substr($line, 3);
54
+			}
55
+		}
56 56
 
57
-        $this->write($inner . implode($glue, $block->lines));
57
+		$this->write($inner . implode($glue, $block->lines));
58 58
 
59
-        if (! empty($block->children)) {
60
-            $this->write($this->break);
61
-        }
62
-    }
59
+		if (! empty($block->children)) {
60
+			$this->write($this->break);
61
+		}
62
+	}
63 63
 
64
-    /**
65
-     * Output block selectors
66
-     *
67
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
68
-     */
69
-    protected function blockSelectors(OutputBlock $block)
70
-    {
71
-        $inner = $this->indentStr();
64
+	/**
65
+	 * Output block selectors
66
+	 *
67
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
68
+	 */
69
+	protected function blockSelectors(OutputBlock $block)
70
+	{
71
+		$inner = $this->indentStr();
72 72
 
73
-        $this->write(
74
-            $inner
75
-            . implode(
76
-                $this->tagSeparator,
77
-                str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
78
-            )
79
-            . $this->open . $this->break
80
-        );
81
-    }
73
+		$this->write(
74
+			$inner
75
+			. implode(
76
+				$this->tagSeparator,
77
+				str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
78
+			)
79
+			. $this->open . $this->break
80
+		);
81
+	}
82 82
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -54,7 +54,7 @@
 block discarded – undo
54 54
 
55 55
         $this->write($inner . implode($glue, $block->lines));
56 56
 
57
-        if (! empty($block->children)) {
57
+        if (!empty($block->children)) {
58 58
             $this->write($this->break);
59 59
         }
60 60
     }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Compiler.php 2 patches
Indentation   +8147 added lines, -8148 removed lines patch added patch discarded remove patch
@@ -59,8211 +59,8210 @@
 block discarded – undo
59 59
  */
60 60
 class Compiler
61 61
 {
62
-    const LINE_COMMENTS = 1;
63
-    const DEBUG_INFO    = 2;
64
-
65
-    const WITH_RULE     = 1;
66
-    const WITH_MEDIA    = 2;
67
-    const WITH_SUPPORTS = 4;
68
-    const WITH_ALL      = 7;
69
-
70
-    const SOURCE_MAP_NONE   = 0;
71
-    const SOURCE_MAP_INLINE = 1;
72
-    const SOURCE_MAP_FILE   = 2;
73
-
74
-    /**
75
-     * @var array
76
-     */
77
-    protected static $operatorNames = [
78
-        '+'   => 'add',
79
-        '-'   => 'sub',
80
-        '*'   => 'mul',
81
-        '/'   => 'div',
82
-        '%'   => 'mod',
83
-
84
-        '=='  => 'eq',
85
-        '!='  => 'neq',
86
-        '<'   => 'lt',
87
-        '>'   => 'gt',
88
-
89
-        '<='  => 'lte',
90
-        '>='  => 'gte',
91
-        '<=>' => 'cmp',
92
-    ];
93
-
94
-    /**
95
-     * @var array
96
-     */
97
-    protected static $namespaces = [
98
-        'special'  => '%',
99
-        'mixin'    => '@',
100
-        'function' => '^',
101
-    ];
102
-
103
-    public static $true         = [Type::T_KEYWORD, 'true'];
104
-    public static $false        = [Type::T_KEYWORD, 'false'];
105
-    public static $NaN          = [Type::T_KEYWORD, 'NaN'];
106
-    public static $Infinity     = [Type::T_KEYWORD, 'Infinity'];
107
-    public static $null         = [Type::T_NULL];
108
-    public static $nullString   = [Type::T_STRING, '', []];
109
-    public static $defaultValue = [Type::T_KEYWORD, ''];
110
-    public static $selfSelector = [Type::T_SELF];
111
-    public static $emptyList    = [Type::T_LIST, '', []];
112
-    public static $emptyMap     = [Type::T_MAP, [], []];
113
-    public static $emptyString  = [Type::T_STRING, '"', []];
114
-    public static $with         = [Type::T_KEYWORD, 'with'];
115
-    public static $without      = [Type::T_KEYWORD, 'without'];
116
-
117
-    protected $importPaths = [''];
118
-    protected $importCache = [];
119
-    protected $importedFiles = [];
120
-    protected $userFunctions = [];
121
-    protected $registeredVars = [];
122
-    protected $registeredFeatures = [
123
-        'extend-selector-pseudoclass' => false,
124
-        'at-error'                    => true,
125
-        'units-level-3'               => false,
126
-        'global-variable-shadowing'   => false,
127
-    ];
128
-
129
-    protected $encoding = null;
130
-    protected $lineNumberStyle = null;
131
-
132
-    protected $sourceMap = self::SOURCE_MAP_NONE;
133
-    protected $sourceMapOptions = [];
134
-
135
-    /**
136
-     * @var string|\ScssPhp\ScssPhp\Formatter
137
-     */
138
-    protected $formatter = 'ScssPhp\ScssPhp\Formatter\Nested';
139
-
140
-    protected $rootEnv;
141
-    protected $rootBlock;
142
-
143
-    /**
144
-     * @var \ScssPhp\ScssPhp\Compiler\Environment
145
-     */
146
-    protected $env;
147
-    protected $scope;
148
-    protected $storeEnv;
149
-    protected $charsetSeen;
150
-    protected $sourceNames;
151
-
152
-    protected $cache;
153
-
154
-    protected $indentLevel;
155
-    protected $extends;
156
-    protected $extendsMap;
157
-    protected $parsedFiles;
158
-    protected $parser;
159
-    protected $sourceIndex;
160
-    protected $sourceLine;
161
-    protected $sourceColumn;
162
-    protected $stderr;
163
-    protected $shouldEvaluate;
164
-    protected $ignoreErrors;
165
-    protected $ignoreCallStackMessage = false;
166
-
167
-    protected $callStack = [];
168
-
169
-    /**
170
-     * Constructor
171
-     *
172
-     * @param array|null $cacheOptions
173
-     */
174
-    public function __construct($cacheOptions = null)
175
-    {
176
-        $this->parsedFiles = [];
177
-        $this->sourceNames = [];
178
-
179
-        if ($cacheOptions) {
180
-            $this->cache = new Cache($cacheOptions);
181
-        }
182
-
183
-        $this->stderr = fopen('php://stderr', 'w');
184
-    }
185
-
186
-    /**
187
-     * Get compiler options
188
-     *
189
-     * @return array
190
-     */
191
-    public function getCompileOptions()
192
-    {
193
-        $options = [
194
-            'importPaths'        => $this->importPaths,
195
-            'registeredVars'     => $this->registeredVars,
196
-            'registeredFeatures' => $this->registeredFeatures,
197
-            'encoding'           => $this->encoding,
198
-            'sourceMap'          => serialize($this->sourceMap),
199
-            'sourceMapOptions'   => $this->sourceMapOptions,
200
-            'formatter'          => $this->formatter,
201
-        ];
202
-
203
-        return $options;
204
-    }
205
-
206
-    /**
207
-     * Set an alternative error output stream, for testing purpose only
208
-     *
209
-     * @param resource $handle
210
-     */
211
-    public function setErrorOuput($handle)
212
-    {
213
-        $this->stderr = $handle;
214
-    }
215
-
216
-    /**
217
-     * Compile scss
218
-     *
219
-     * @api
220
-     *
221
-     * @param string $code
222
-     * @param string $path
223
-     *
224
-     * @return string
225
-     */
226
-    public function compile($code, $path = null)
227
-    {
228
-        if ($this->cache) {
229
-            $cacheKey       = ($path ? $path : '(stdin)') . ':' . md5($code);
230
-            $compileOptions = $this->getCompileOptions();
231
-            $cache          = $this->cache->getCache('compile', $cacheKey, $compileOptions);
232
-
233
-            if (\is_array($cache) && isset($cache['dependencies']) && isset($cache['out'])) {
234
-                // check if any dependency file changed before accepting the cache
235
-                foreach ($cache['dependencies'] as $file => $mtime) {
236
-                    if (! is_file($file) || filemtime($file) !== $mtime) {
237
-                        unset($cache);
238
-                        break;
239
-                    }
240
-                }
241
-
242
-                if (isset($cache)) {
243
-                    return $cache['out'];
244
-                }
245
-            }
246
-        }
247
-
248
-
249
-        $this->indentLevel    = -1;
250
-        $this->extends        = [];
251
-        $this->extendsMap     = [];
252
-        $this->sourceIndex    = null;
253
-        $this->sourceLine     = null;
254
-        $this->sourceColumn   = null;
255
-        $this->env            = null;
256
-        $this->scope          = null;
257
-        $this->storeEnv       = null;
258
-        $this->charsetSeen    = null;
259
-        $this->shouldEvaluate = null;
260
-        $this->ignoreCallStackMessage = false;
261
-
262
-        $this->parser = $this->parserFactory($path);
263
-        $tree         = $this->parser->parse($code);
264
-        $this->parser = null;
265
-
266
-        $this->formatter = new $this->formatter();
267
-        $this->rootBlock = null;
268
-        $this->rootEnv   = $this->pushEnv($tree);
269
-
270
-        $this->injectVariables($this->registeredVars);
271
-        $this->compileRoot($tree);
272
-        $this->popEnv();
273
-
274
-        $sourceMapGenerator = null;
275
-
276
-        if ($this->sourceMap) {
277
-            if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
278
-                $sourceMapGenerator = $this->sourceMap;
279
-                $this->sourceMap = self::SOURCE_MAP_FILE;
280
-            } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
281
-                $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
282
-            }
283
-        }
284
-
285
-        $out = $this->formatter->format($this->scope, $sourceMapGenerator);
286
-
287
-        if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
288
-            $sourceMap    = $sourceMapGenerator->generateJson();
289
-            $sourceMapUrl = null;
290
-
291
-            switch ($this->sourceMap) {
292
-                case self::SOURCE_MAP_INLINE:
293
-                    $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
294
-                    break;
295
-
296
-                case self::SOURCE_MAP_FILE:
297
-                    $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
298
-                    break;
299
-            }
300
-
301
-            $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
302
-        }
303
-
304
-        if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
305
-            $v = [
306
-                'dependencies' => $this->getParsedFiles(),
307
-                'out' => &$out,
308
-            ];
309
-
310
-            $this->cache->setCache('compile', $cacheKey, $v, $compileOptions);
311
-        }
312
-
313
-        return $out;
314
-    }
315
-
316
-    /**
317
-     * Instantiate parser
318
-     *
319
-     * @param string $path
320
-     *
321
-     * @return \ScssPhp\ScssPhp\Parser
322
-     */
323
-    protected function parserFactory($path)
324
-    {
325
-        // https://sass-lang.com/documentation/at-rules/import
326
-        // CSS files imported by Sass don’t allow any special Sass features.
327
-        // In order to make sure authors don’t accidentally write Sass in their CSS,
328
-        // all Sass features that aren’t also valid CSS will produce errors.
329
-        // Otherwise, the CSS will be rendered as-is. It can even be extended!
330
-        $cssOnly = false;
331
-
332
-        if (substr($path, '-4') === '.css') {
333
-            $cssOnly = true;
334
-        }
335
-
336
-        $parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly);
337
-
338
-        $this->sourceNames[] = $path;
339
-        $this->addParsedFile($path);
340
-
341
-        return $parser;
342
-    }
343
-
344
-    /**
345
-     * Is self extend?
346
-     *
347
-     * @param array $target
348
-     * @param array $origin
349
-     *
350
-     * @return boolean
351
-     */
352
-    protected function isSelfExtend($target, $origin)
353
-    {
354
-        foreach ($origin as $sel) {
355
-            if (\in_array($target, $sel)) {
356
-                return true;
357
-            }
358
-        }
359
-
360
-        return false;
361
-    }
362
-
363
-    /**
364
-     * Push extends
365
-     *
366
-     * @param array      $target
367
-     * @param array      $origin
368
-     * @param array|null $block
369
-     */
370
-    protected function pushExtends($target, $origin, $block)
371
-    {
372
-        $i = \count($this->extends);
373
-        $this->extends[] = [$target, $origin, $block];
374
-
375
-        foreach ($target as $part) {
376
-            if (isset($this->extendsMap[$part])) {
377
-                $this->extendsMap[$part][] = $i;
378
-            } else {
379
-                $this->extendsMap[$part] = [$i];
380
-            }
381
-        }
382
-    }
383
-
384
-    /**
385
-     * Make output block
386
-     *
387
-     * @param string $type
388
-     * @param array  $selectors
389
-     *
390
-     * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
391
-     */
392
-    protected function makeOutputBlock($type, $selectors = null)
393
-    {
394
-        $out = new OutputBlock();
395
-        $out->type      = $type;
396
-        $out->lines     = [];
397
-        $out->children  = [];
398
-        $out->parent    = $this->scope;
399
-        $out->selectors = $selectors;
400
-        $out->depth     = $this->env->depth;
401
-
402
-        if ($this->env->block instanceof Block) {
403
-            $out->sourceName   = $this->env->block->sourceName;
404
-            $out->sourceLine   = $this->env->block->sourceLine;
405
-            $out->sourceColumn = $this->env->block->sourceColumn;
406
-        } else {
407
-            $out->sourceName   = null;
408
-            $out->sourceLine   = null;
409
-            $out->sourceColumn = null;
410
-        }
411
-
412
-        return $out;
413
-    }
414
-
415
-    /**
416
-     * Compile root
417
-     *
418
-     * @param \ScssPhp\ScssPhp\Block $rootBlock
419
-     */
420
-    protected function compileRoot(Block $rootBlock)
421
-    {
422
-        $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
423
-
424
-        $this->compileChildrenNoReturn($rootBlock->children, $this->scope);
425
-        $this->flattenSelectors($this->scope);
426
-        $this->missingSelectors();
427
-    }
428
-
429
-    /**
430
-     * Report missing selectors
431
-     */
432
-    protected function missingSelectors()
433
-    {
434
-        foreach ($this->extends as $extend) {
435
-            if (isset($extend[3])) {
436
-                continue;
437
-            }
438
-
439
-            list($target, $origin, $block) = $extend;
440
-
441
-            // ignore if !optional
442
-            if ($block[2]) {
443
-                continue;
444
-            }
445
-
446
-            $target = implode(' ', $target);
447
-            $origin = $this->collapseSelectors($origin);
448
-
449
-            $this->sourceLine = $block[Parser::SOURCE_LINE];
450
-            throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
451
-        }
452
-    }
453
-
454
-    /**
455
-     * Flatten selectors
456
-     *
457
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
458
-     * @param string                                 $parentKey
459
-     */
460
-    protected function flattenSelectors(OutputBlock $block, $parentKey = null)
461
-    {
462
-        if ($block->selectors) {
463
-            $selectors = [];
464
-
465
-            foreach ($block->selectors as $s) {
466
-                $selectors[] = $s;
467
-
468
-                if (! \is_array($s)) {
469
-                    continue;
470
-                }
471
-
472
-                // check extends
473
-                if (! empty($this->extendsMap)) {
474
-                    $this->matchExtends($s, $selectors);
475
-
476
-                    // remove duplicates
477
-                    array_walk($selectors, function (&$value) {
478
-                        $value = serialize($value);
479
-                    });
480
-
481
-                    $selectors = array_unique($selectors);
482
-
483
-                    array_walk($selectors, function (&$value) {
484
-                        $value = unserialize($value);
485
-                    });
486
-                }
487
-            }
488
-
489
-            $block->selectors = [];
490
-            $placeholderSelector = false;
491
-
492
-            foreach ($selectors as $selector) {
493
-                if ($this->hasSelectorPlaceholder($selector)) {
494
-                    $placeholderSelector = true;
495
-                    continue;
496
-                }
497
-
498
-                $block->selectors[] = $this->compileSelector($selector);
499
-            }
500
-
501
-            if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) {
502
-                unset($block->parent->children[$parentKey]);
503
-
504
-                return;
505
-            }
506
-        }
507
-
508
-        foreach ($block->children as $key => $child) {
509
-            $this->flattenSelectors($child, $key);
510
-        }
511
-    }
512
-
513
-    /**
514
-     * Glue parts of :not( or :nth-child( ... that are in general splitted in selectors parts
515
-     *
516
-     * @param array $parts
517
-     *
518
-     * @return array
519
-     */
520
-    protected function glueFunctionSelectors($parts)
521
-    {
522
-        $new = [];
523
-
524
-        foreach ($parts as $part) {
525
-            if (\is_array($part)) {
526
-                $part = $this->glueFunctionSelectors($part);
527
-                $new[] = $part;
528
-            } else {
529
-                // a selector part finishing with a ) is the last part of a :not( or :nth-child(
530
-                // and need to be joined to this
531
-                if (
532
-                    \count($new) && \is_string($new[\count($new) - 1]) &&
533
-                    \strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
534
-                ) {
535
-                    while (\count($new) > 1 && substr($new[\count($new) - 1], -1) !== '(') {
536
-                        $part = array_pop($new) . $part;
537
-                    }
538
-                    $new[\count($new) - 1] .= $part;
539
-                } else {
540
-                    $new[] = $part;
541
-                }
542
-            }
543
-        }
544
-
545
-        return $new;
546
-    }
547
-
548
-    /**
549
-     * Match extends
550
-     *
551
-     * @param array   $selector
552
-     * @param array   $out
553
-     * @param integer $from
554
-     * @param boolean $initial
555
-     */
556
-    protected function matchExtends($selector, &$out, $from = 0, $initial = true)
557
-    {
558
-        static $partsPile = [];
559
-        $selector = $this->glueFunctionSelectors($selector);
560
-
561
-        if (\count($selector) == 1 && \in_array(reset($selector), $partsPile)) {
562
-            return;
563
-        }
564
-
565
-        $outRecurs = [];
566
-
567
-        foreach ($selector as $i => $part) {
568
-            if ($i < $from) {
569
-                continue;
570
-            }
571
-
572
-            // check that we are not building an infinite loop of extensions
573
-            // if the new part is just including a previous part don't try to extend anymore
574
-            if (\count($part) > 1) {
575
-                foreach ($partsPile as $previousPart) {
576
-                    if (! \count(array_diff($previousPart, $part))) {
577
-                        continue 2;
578
-                    }
579
-                }
580
-            }
581
-
582
-            $partsPile[] = $part;
583
-
584
-            if ($this->matchExtendsSingle($part, $origin, $initial)) {
585
-                $after       = \array_slice($selector, $i + 1);
586
-                $before      = \array_slice($selector, 0, $i);
587
-                list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
588
-
589
-                foreach ($origin as $new) {
590
-                    $k = 0;
591
-
592
-                    // remove shared parts
593
-                    if (\count($new) > 1) {
594
-                        while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
595
-                            $k++;
596
-                        }
597
-                    }
598
-
599
-                    if (\count($nonBreakableBefore) && $k === \count($new)) {
600
-                        $k--;
601
-                    }
602
-
603
-                    $replacement = [];
604
-                    $tempReplacement = $k > 0 ? \array_slice($new, $k) : $new;
605
-
606
-                    for ($l = \count($tempReplacement) - 1; $l >= 0; $l--) {
607
-                        $slice = [];
608
-
609
-                        foreach ($tempReplacement[$l] as $chunk) {
610
-                            if (! \in_array($chunk, $slice)) {
611
-                                $slice[] = $chunk;
612
-                            }
613
-                        }
614
-
615
-                        array_unshift($replacement, $slice);
616
-
617
-                        if (! $this->isImmediateRelationshipCombinator(end($slice))) {
618
-                            break;
619
-                        }
620
-                    }
621
-
622
-                    $afterBefore = $l != 0 ? \array_slice($tempReplacement, 0, $l) : [];
623
-
624
-                    // Merge shared direct relationships.
625
-                    $mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore);
626
-
627
-                    $result = array_merge(
628
-                        $before,
629
-                        $mergedBefore,
630
-                        $replacement,
631
-                        $after
632
-                    );
633
-
634
-                    if ($result === $selector) {
635
-                        continue;
636
-                    }
637
-
638
-                    $this->pushOrMergeExtentedSelector($out, $result);
639
-
640
-                    // recursively check for more matches
641
-                    $startRecurseFrom = \count($before) + min(\count($nonBreakableBefore), \count($mergedBefore));
642
-
643
-                    if (\count($origin) > 1) {
644
-                        $this->matchExtends($result, $out, $startRecurseFrom, false);
645
-                    } else {
646
-                        $this->matchExtends($result, $outRecurs, $startRecurseFrom, false);
647
-                    }
648
-
649
-                    // selector sequence merging
650
-                    if (! empty($before) && \count($new) > 1) {
651
-                        $preSharedParts = $k > 0 ? \array_slice($before, 0, $k) : [];
652
-                        $postSharedParts = $k > 0 ? \array_slice($before, $k) : $before;
653
-
654
-                        list($betweenSharedParts, $nonBreakabl2) = $this->extractRelationshipFromFragment($afterBefore);
655
-
656
-                        $result2 = array_merge(
657
-                            $preSharedParts,
658
-                            $betweenSharedParts,
659
-                            $postSharedParts,
660
-                            $nonBreakabl2,
661
-                            $nonBreakableBefore,
662
-                            $replacement,
663
-                            $after
664
-                        );
665
-
666
-                        $this->pushOrMergeExtentedSelector($out, $result2);
667
-                    }
668
-                }
669
-            }
670
-            array_pop($partsPile);
671
-        }
672
-
673
-        while (\count($outRecurs)) {
674
-            $result = array_shift($outRecurs);
675
-            $this->pushOrMergeExtentedSelector($out, $result);
676
-        }
677
-    }
678
-
679
-    /**
680
-     * Test a part for being a pseudo selector
681
-     *
682
-     * @param string $part
683
-     * @param array  $matches
684
-     *
685
-     * @return boolean
686
-     */
687
-    protected function isPseudoSelector($part, &$matches)
688
-    {
689
-        if (
690
-            strpos($part, ':') === 0 &&
691
-            preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
692
-        ) {
693
-            return true;
694
-        }
695
-
696
-        return false;
697
-    }
698
-
699
-    /**
700
-     * Push extended selector except if
701
-     *  - this is a pseudo selector
702
-     *  - same as previous
703
-     *  - in a white list
704
-     * in this case we merge the pseudo selector content
705
-     *
706
-     * @param array $out
707
-     * @param array $extended
708
-     */
709
-    protected function pushOrMergeExtentedSelector(&$out, $extended)
710
-    {
711
-        if (\count($out) && \count($extended) === 1 && \count(reset($extended)) === 1) {
712
-            $single = reset($extended);
713
-            $part = reset($single);
714
-
715
-            if (
716
-                $this->isPseudoSelector($part, $matchesExtended) &&
717
-                \in_array($matchesExtended[1], [ 'slotted' ])
718
-            ) {
719
-                $prev = end($out);
720
-                $prev = $this->glueFunctionSelectors($prev);
721
-
722
-                if (\count($prev) === 1 && \count(reset($prev)) === 1) {
723
-                    $single = reset($prev);
724
-                    $part = reset($single);
725
-
726
-                    if (
727
-                        $this->isPseudoSelector($part, $matchesPrev) &&
728
-                        $matchesPrev[1] === $matchesExtended[1]
729
-                    ) {
730
-                        $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
731
-                        $extended[1] = $matchesPrev[2] . ', ' . $extended[1];
732
-                        $extended = implode($matchesExtended[1] . '(', $extended);
733
-                        $extended = [ [ $extended ]];
734
-                        array_pop($out);
735
-                    }
736
-                }
737
-            }
738
-        }
739
-        $out[] = $extended;
740
-    }
741
-
742
-    /**
743
-     * Match extends single
744
-     *
745
-     * @param array   $rawSingle
746
-     * @param array   $outOrigin
747
-     * @param boolean $initial
748
-     *
749
-     * @return boolean
750
-     */
751
-    protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true)
752
-    {
753
-        $counts = [];
754
-        $single = [];
755
-
756
-        // simple usual cases, no need to do the whole trick
757
-        if (\in_array($rawSingle, [['>'],['+'],['~']])) {
758
-            return false;
759
-        }
760
-
761
-        foreach ($rawSingle as $part) {
762
-            // matches Number
763
-            if (! \is_string($part)) {
764
-                return false;
765
-            }
766
-
767
-            if (! preg_match('/^[\[.:#%]/', $part) && \count($single)) {
768
-                $single[\count($single) - 1] .= $part;
769
-            } else {
770
-                $single[] = $part;
771
-            }
772
-        }
773
-
774
-        $extendingDecoratedTag = false;
775
-
776
-        if (\count($single) > 1) {
777
-            $matches = null;
778
-            $extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false;
779
-        }
780
-
781
-        $outOrigin = [];
782
-        $found = false;
783
-
784
-        foreach ($single as $k => $part) {
785
-            if (isset($this->extendsMap[$part])) {
786
-                foreach ($this->extendsMap[$part] as $idx) {
787
-                    $counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
788
-                }
789
-            }
790
-
791
-            if (
792
-                $initial &&
793
-                $this->isPseudoSelector($part, $matches) &&
794
-                ! \in_array($matches[1], [ 'not' ])
795
-            ) {
796
-                $buffer    = $matches[2];
797
-                $parser    = $this->parserFactory(__METHOD__);
798
-
799
-                if ($parser->parseSelector($buffer, $subSelectors)) {
800
-                    foreach ($subSelectors as $ksub => $subSelector) {
801
-                        $subExtended = [];
802
-                        $this->matchExtends($subSelector, $subExtended, 0, false);
803
-
804
-                        if ($subExtended) {
805
-                            $subSelectorsExtended = $subSelectors;
806
-                            $subSelectorsExtended[$ksub] = $subExtended;
807
-
808
-                            foreach ($subSelectorsExtended as $ksse => $sse) {
809
-                                $subSelectorsExtended[$ksse] = $this->collapseSelectors($sse);
810
-                            }
811
-
812
-                            $subSelectorsExtended = implode(', ', $subSelectorsExtended);
813
-                            $singleExtended = $single;
814
-                            $singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part);
815
-                            $outOrigin[] = [ $singleExtended ];
816
-                            $found = true;
817
-                        }
818
-                    }
819
-                }
820
-            }
821
-        }
822
-
823
-        foreach ($counts as $idx => $count) {
824
-            list($target, $origin, /* $block */) = $this->extends[$idx];
825
-
826
-            $origin = $this->glueFunctionSelectors($origin);
827
-
828
-            // check count
829
-            if ($count !== \count($target)) {
830
-                continue;
831
-            }
832
-
833
-            $this->extends[$idx][3] = true;
834
-
835
-            $rem = array_diff($single, $target);
836
-
837
-            foreach ($origin as $j => $new) {
838
-                // prevent infinite loop when target extends itself
839
-                if ($this->isSelfExtend($single, $origin) && ! $initial) {
840
-                    return false;
841
-                }
842
-
843
-                $replacement = end($new);
844
-
845
-                // Extending a decorated tag with another tag is not possible.
846
-                if (
847
-                    $extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
848
-                    preg_match('/^[a-z0-9]+$/i', $replacement[0])
849
-                ) {
850
-                    unset($origin[$j]);
851
-                    continue;
852
-                }
853
-
854
-                $combined = $this->combineSelectorSingle($replacement, $rem);
855
-
856
-                if (\count(array_diff($combined, $origin[$j][\count($origin[$j]) - 1]))) {
857
-                    $origin[$j][\count($origin[$j]) - 1] = $combined;
858
-                }
859
-            }
860
-
861
-            $outOrigin = array_merge($outOrigin, $origin);
862
-
863
-            $found = true;
864
-        }
865
-
866
-        return $found;
867
-    }
868
-
869
-    /**
870
-     * Extract a relationship from the fragment.
871
-     *
872
-     * When extracting the last portion of a selector we will be left with a
873
-     * fragment which may end with a direction relationship combinator. This
874
-     * method will extract the relationship fragment and return it along side
875
-     * the rest.
876
-     *
877
-     * @param array $fragment The selector fragment maybe ending with a direction relationship combinator.
878
-     *
879
-     * @return array The selector without the relationship fragment if any, the relationship fragment.
880
-     */
881
-    protected function extractRelationshipFromFragment(array $fragment)
882
-    {
883
-        $parents = [];
884
-        $children = [];
885
-
886
-        $j = $i = \count($fragment);
887
-
888
-        for (;;) {
889
-            $children = $j != $i ? \array_slice($fragment, $j, $i - $j) : [];
890
-            $parents  = \array_slice($fragment, 0, $j);
891
-            $slice    = end($parents);
892
-
893
-            if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
894
-                break;
895
-            }
896
-
897
-            $j -= 2;
898
-        }
899
-
900
-        return [$parents, $children];
901
-    }
902
-
903
-    /**
904
-     * Combine selector single
905
-     *
906
-     * @param array $base
907
-     * @param array $other
908
-     *
909
-     * @return array
910
-     */
911
-    protected function combineSelectorSingle($base, $other)
912
-    {
913
-        $tag    = [];
914
-        $out    = [];
915
-        $wasTag = false;
916
-        $pseudo = [];
917
-
918
-        while (\count($other) && strpos(end($other), ':') === 0) {
919
-            array_unshift($pseudo, array_pop($other));
920
-        }
921
-
922
-        foreach ([array_reverse($base), array_reverse($other)] as $single) {
923
-            $rang = count($single);
924
-
925
-            foreach ($single as $part) {
926
-                if (preg_match('/^[\[:]/', $part)) {
927
-                    $out[] = $part;
928
-                    $wasTag = false;
929
-                } elseif (preg_match('/^[\.#]/', $part)) {
930
-                    array_unshift($out, $part);
931
-                    $wasTag = false;
932
-                } elseif (preg_match('/^[^_-]/', $part) && $rang === 1) {
933
-                    $tag[] = $part;
934
-                    $wasTag = true;
935
-                } elseif ($wasTag) {
936
-                    $tag[\count($tag) - 1] .= $part;
937
-                } else {
938
-                    array_unshift($out, $part);
939
-                }
940
-                $rang--;
941
-            }
942
-        }
943
-
944
-        if (\count($tag)) {
945
-            array_unshift($out, $tag[0]);
946
-        }
947
-
948
-        while (\count($pseudo)) {
949
-            $out[] = array_shift($pseudo);
950
-        }
951
-
952
-        return $out;
953
-    }
954
-
955
-    /**
956
-     * Compile media
957
-     *
958
-     * @param \ScssPhp\ScssPhp\Block $media
959
-     */
960
-    protected function compileMedia(Block $media)
961
-    {
962
-        $this->pushEnv($media);
963
-
964
-        $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
965
-
966
-        if (! empty($mediaQueries) && $mediaQueries) {
967
-            $previousScope = $this->scope;
968
-            $parentScope = $this->mediaParent($this->scope);
969
-
970
-            foreach ($mediaQueries as $mediaQuery) {
971
-                $this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
972
-
973
-                $parentScope->children[] = $this->scope;
974
-                $parentScope = $this->scope;
975
-            }
976
-
977
-            // top level properties in a media cause it to be wrapped
978
-            $needsWrap = false;
979
-
980
-            foreach ($media->children as $child) {
981
-                $type = $child[0];
982
-
983
-                if (
984
-                    $type !== Type::T_BLOCK &&
985
-                    $type !== Type::T_MEDIA &&
986
-                    $type !== Type::T_DIRECTIVE &&
987
-                    $type !== Type::T_IMPORT
988
-                ) {
989
-                    $needsWrap = true;
990
-                    break;
991
-                }
992
-            }
993
-
994
-            if ($needsWrap) {
995
-                $wrapped = new Block();
996
-                $wrapped->sourceName   = $media->sourceName;
997
-                $wrapped->sourceIndex  = $media->sourceIndex;
998
-                $wrapped->sourceLine   = $media->sourceLine;
999
-                $wrapped->sourceColumn = $media->sourceColumn;
1000
-                $wrapped->selectors    = [];
1001
-                $wrapped->comments     = [];
1002
-                $wrapped->parent       = $media;
1003
-                $wrapped->children     = $media->children;
1004
-
1005
-                $media->children = [[Type::T_BLOCK, $wrapped]];
1006
-
1007
-                if (isset($this->lineNumberStyle)) {
1008
-                    $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1009
-                    $annotation->depth = 0;
1010
-
1011
-                    $file = $this->sourceNames[$media->sourceIndex];
1012
-                    $line = $media->sourceLine;
1013
-
1014
-                    switch ($this->lineNumberStyle) {
1015
-                        case static::LINE_COMMENTS:
1016
-                            $annotation->lines[] = '/* line ' . $line
1017
-                                                 . ($file ? ', ' . $file : '')
1018
-                                                 . ' */';
1019
-                            break;
1020
-
1021
-                        case static::DEBUG_INFO:
1022
-                            $annotation->lines[] = '@media -sass-debug-info{'
1023
-                                                 . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1024
-                                                 . 'line{font-family:' . $line . '}}';
1025
-                            break;
1026
-                    }
1027
-
1028
-                    $this->scope->children[] = $annotation;
1029
-                }
1030
-            }
1031
-
1032
-            $this->compileChildrenNoReturn($media->children, $this->scope);
1033
-
1034
-            $this->scope = $previousScope;
1035
-        }
1036
-
1037
-        $this->popEnv();
1038
-    }
1039
-
1040
-    /**
1041
-     * Media parent
1042
-     *
1043
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1044
-     *
1045
-     * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
1046
-     */
1047
-    protected function mediaParent(OutputBlock $scope)
1048
-    {
1049
-        while (! empty($scope->parent)) {
1050
-            if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
1051
-                break;
1052
-            }
1053
-
1054
-            $scope = $scope->parent;
1055
-        }
1056
-
1057
-        return $scope;
1058
-    }
1059
-
1060
-    /**
1061
-     * Compile directive
1062
-     *
1063
-     * @param \ScssPhp\ScssPhp\Block|array $block
1064
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1065
-     */
1066
-    protected function compileDirective($directive, OutputBlock $out)
1067
-    {
1068
-        if (\is_array($directive)) {
1069
-            $s = '@' . $directive[0];
1070
-
1071
-            if (! empty($directive[1])) {
1072
-                $s .= ' ' . $this->compileValue($directive[1]);
1073
-            }
1074
-
1075
-            $this->appendRootDirective($s . ';', $out);
1076
-        } else {
1077
-            $s = '@' . $directive->name;
1078
-
1079
-            if (! empty($directive->value)) {
1080
-                $s .= ' ' . $this->compileValue($directive->value);
1081
-            }
1082
-
1083
-            if ($directive->name === 'keyframes' || substr($directive->name, -10) === '-keyframes') {
1084
-                $this->compileKeyframeBlock($directive, [$s]);
1085
-            } else {
1086
-                $this->compileNestedBlock($directive, [$s]);
1087
-            }
1088
-        }
1089
-    }
1090
-
1091
-    /**
1092
-     * Compile at-root
1093
-     *
1094
-     * @param \ScssPhp\ScssPhp\Block $block
1095
-     */
1096
-    protected function compileAtRoot(Block $block)
1097
-    {
1098
-        $env     = $this->pushEnv($block);
1099
-        $envs    = $this->compactEnv($env);
1100
-        list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null);
1101
-
1102
-        // wrap inline selector
1103
-        if ($block->selector) {
1104
-            $wrapped = new Block();
1105
-            $wrapped->sourceName   = $block->sourceName;
1106
-            $wrapped->sourceIndex  = $block->sourceIndex;
1107
-            $wrapped->sourceLine   = $block->sourceLine;
1108
-            $wrapped->sourceColumn = $block->sourceColumn;
1109
-            $wrapped->selectors    = $block->selector;
1110
-            $wrapped->comments     = [];
1111
-            $wrapped->parent       = $block;
1112
-            $wrapped->children     = $block->children;
1113
-            $wrapped->selfParent   = $block->selfParent;
1114
-
1115
-            $block->children = [[Type::T_BLOCK, $wrapped]];
1116
-            $block->selector = null;
1117
-        }
1118
-
1119
-        $selfParent = $block->selfParent;
1120
-
1121
-        if (
1122
-            ! $block->selfParent->selectors &&
1123
-            isset($block->parent) && $block->parent &&
1124
-            isset($block->parent->selectors) && $block->parent->selectors
1125
-        ) {
1126
-            $selfParent = $block->parent;
1127
-        }
1128
-
1129
-        $this->env = $this->filterWithWithout($envs, $with, $without);
1130
-
1131
-        $saveScope   = $this->scope;
1132
-        $this->scope = $this->filterScopeWithWithout($saveScope, $with, $without);
1133
-
1134
-        // propagate selfParent to the children where they still can be useful
1135
-        $this->compileChildrenNoReturn($block->children, $this->scope, $selfParent);
1136
-
1137
-        $this->scope = $this->completeScope($this->scope, $saveScope);
1138
-        $this->scope = $saveScope;
1139
-        $this->env   = $this->extractEnv($envs);
1140
-
1141
-        $this->popEnv();
1142
-    }
1143
-
1144
-    /**
1145
-     * Filter at-root scope depending of with/without option
1146
-     *
1147
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1148
-     * @param array                                  $with
1149
-     * @param array                                  $without
1150
-     *
1151
-     * @return mixed
1152
-     */
1153
-    protected function filterScopeWithWithout($scope, $with, $without)
1154
-    {
1155
-        $filteredScopes = [];
1156
-        $childStash = [];
1157
-
1158
-        if ($scope->type === TYPE::T_ROOT) {
1159
-            return $scope;
1160
-        }
1161
-
1162
-        // start from the root
1163
-        while ($scope->parent && $scope->parent->type !== TYPE::T_ROOT) {
1164
-            array_unshift($childStash, $scope);
1165
-            $scope = $scope->parent;
1166
-        }
1167
-
1168
-        for (;;) {
1169
-            if (! $scope) {
1170
-                break;
1171
-            }
1172
-
1173
-            if ($this->isWith($scope, $with, $without)) {
1174
-                $s = clone $scope;
1175
-                $s->children = [];
1176
-                $s->lines    = [];
1177
-                $s->parent   = null;
1178
-
1179
-                if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) {
1180
-                    $s->selectors = [];
1181
-                }
1182
-
1183
-                $filteredScopes[] = $s;
1184
-            }
1185
-
1186
-            if (\count($childStash)) {
1187
-                $scope = array_shift($childStash);
1188
-            } elseif ($scope->children) {
1189
-                $scope = end($scope->children);
1190
-            } else {
1191
-                $scope = null;
1192
-            }
1193
-        }
1194
-
1195
-        if (! \count($filteredScopes)) {
1196
-            return $this->rootBlock;
1197
-        }
1198
-
1199
-        $newScope = array_shift($filteredScopes);
1200
-        $newScope->parent = $this->rootBlock;
1201
-
1202
-        $this->rootBlock->children[] = $newScope;
1203
-
1204
-        $p = &$newScope;
1205
-
1206
-        while (\count($filteredScopes)) {
1207
-            $s = array_shift($filteredScopes);
1208
-            $s->parent = $p;
1209
-            $p->children[] = $s;
1210
-            $newScope = &$p->children[0];
1211
-            $p = &$p->children[0];
1212
-        }
1213
-
1214
-        return $newScope;
1215
-    }
1216
-
1217
-    /**
1218
-     * found missing selector from a at-root compilation in the previous scope
1219
-     * (if at-root is just enclosing a property, the selector is in the parent tree)
1220
-     *
1221
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1222
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1223
-     *
1224
-     * @return mixed
1225
-     */
1226
-    protected function completeScope($scope, $previousScope)
1227
-    {
1228
-        if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) {
1229
-            $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1230
-        }
1231
-
1232
-        if ($scope->children) {
1233
-            foreach ($scope->children as $k => $c) {
1234
-                $scope->children[$k] = $this->completeScope($c, $previousScope);
1235
-            }
1236
-        }
1237
-
1238
-        return $scope;
1239
-    }
1240
-
1241
-    /**
1242
-     * Find a selector by the depth node in the scope
1243
-     *
1244
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1245
-     * @param integer                                $depth
1246
-     *
1247
-     * @return array
1248
-     */
1249
-    protected function findScopeSelectors($scope, $depth)
1250
-    {
1251
-        if ($scope->depth === $depth && $scope->selectors) {
1252
-            return $scope->selectors;
1253
-        }
1254
-
1255
-        if ($scope->children) {
1256
-            foreach (array_reverse($scope->children) as $c) {
1257
-                if ($s = $this->findScopeSelectors($c, $depth)) {
1258
-                    return $s;
1259
-                }
1260
-            }
1261
-        }
1262
-
1263
-        return [];
1264
-    }
1265
-
1266
-    /**
1267
-     * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later
1268
-     *
1269
-     * @param array $withCondition
1270
-     *
1271
-     * @return array
1272
-     */
1273
-    protected function compileWith($withCondition)
1274
-    {
1275
-        // just compile what we have in 2 lists
1276
-        $with = [];
1277
-        $without = ['rule' => true];
1278
-
1279
-        if ($withCondition) {
1280
-            if ($withCondition[0] === Type::T_INTERPOLATE) {
1281
-                $w = $this->compileValue($withCondition);
1282
-
1283
-                $buffer = "($w)";
1284
-                $parser = $this->parserFactory(__METHOD__);
1285
-
1286
-                if ($parser->parseValue($buffer, $reParsedWith)) {
1287
-                    $withCondition = $reParsedWith;
1288
-                }
1289
-            }
1290
-
1291
-            if ($this->libMapHasKey([$withCondition, static::$with])) {
1292
-                $without = []; // cancel the default
1293
-                $list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
1294
-
1295
-                foreach ($list[2] as $item) {
1296
-                    $keyword = $this->compileStringContent($this->coerceString($item));
1297
-
1298
-                    $with[$keyword] = true;
1299
-                }
1300
-            }
1301
-
1302
-            if ($this->libMapHasKey([$withCondition, static::$without])) {
1303
-                $without = []; // cancel the default
1304
-                $list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
1305
-
1306
-                foreach ($list[2] as $item) {
1307
-                    $keyword = $this->compileStringContent($this->coerceString($item));
1308
-
1309
-                    $without[$keyword] = true;
1310
-                }
1311
-            }
1312
-        }
1313
-
1314
-        return [$with, $without];
1315
-    }
1316
-
1317
-    /**
1318
-     * Filter env stack
1319
-     *
1320
-     * @param array $envs
1321
-     * @param array $with
1322
-     * @param array $without
1323
-     *
1324
-     * @return \ScssPhp\ScssPhp\Compiler\Environment
1325
-     */
1326
-    protected function filterWithWithout($envs, $with, $without)
1327
-    {
1328
-        $filtered = [];
1329
-
1330
-        foreach ($envs as $e) {
1331
-            if ($e->block && ! $this->isWith($e->block, $with, $without)) {
1332
-                $ec = clone $e;
1333
-                $ec->block     = null;
1334
-                $ec->selectors = [];
1335
-
1336
-                $filtered[] = $ec;
1337
-            } else {
1338
-                $filtered[] = $e;
1339
-            }
1340
-        }
1341
-
1342
-        return $this->extractEnv($filtered);
1343
-    }
1344
-
1345
-    /**
1346
-     * Filter WITH rules
1347
-     *
1348
-     * @param \ScssPhp\ScssPhp\Block|\ScssPhp\ScssPhp\Formatter\OutputBlock $block
1349
-     * @param array                                                         $with
1350
-     * @param array                                                         $without
1351
-     *
1352
-     * @return boolean
1353
-     */
1354
-    protected function isWith($block, $with, $without)
1355
-    {
1356
-        if (isset($block->type)) {
1357
-            if ($block->type === Type::T_MEDIA) {
1358
-                return $this->testWithWithout('media', $with, $without);
1359
-            }
1360
-
1361
-            if ($block->type === Type::T_DIRECTIVE) {
1362
-                if (isset($block->name)) {
1363
-                    return $this->testWithWithout($block->name, $with, $without);
1364
-                } elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1365
-                    return $this->testWithWithout($m[1], $with, $without);
1366
-                } else {
1367
-                    return $this->testWithWithout('???', $with, $without);
1368
-                }
1369
-            }
1370
-        } elseif (isset($block->selectors)) {
1371
-            // a selector starting with number is a keyframe rule
1372
-            if (\count($block->selectors)) {
1373
-                $s = reset($block->selectors);
1374
-
1375
-                while (\is_array($s)) {
1376
-                    $s = reset($s);
1377
-                }
1378
-
1379
-                if (\is_object($s) && $s instanceof Node\Number) {
1380
-                    return $this->testWithWithout('keyframes', $with, $without);
1381
-                }
1382
-            }
1383
-
1384
-            return $this->testWithWithout('rule', $with, $without);
1385
-        }
1386
-
1387
-        return true;
1388
-    }
1389
-
1390
-    /**
1391
-     * Test a single type of block against with/without lists
1392
-     *
1393
-     * @param string $what
1394
-     * @param array  $with
1395
-     * @param array  $without
1396
-     *
1397
-     * @return boolean
1398
-     *   true if the block should be kept, false to reject
1399
-     */
1400
-    protected function testWithWithout($what, $with, $without)
1401
-    {
1402
-        // if without, reject only if in the list (or 'all' is in the list)
1403
-        if (\count($without)) {
1404
-            return (isset($without[$what]) || isset($without['all'])) ? false : true;
1405
-        }
1406
-
1407
-        // otherwise reject all what is not in the with list
1408
-        return (isset($with[$what]) || isset($with['all'])) ? true : false;
1409
-    }
1410
-
1411
-
1412
-    /**
1413
-     * Compile keyframe block
1414
-     *
1415
-     * @param \ScssPhp\ScssPhp\Block $block
1416
-     * @param array                  $selectors
1417
-     */
1418
-    protected function compileKeyframeBlock(Block $block, $selectors)
1419
-    {
1420
-        $env = $this->pushEnv($block);
1421
-
1422
-        $envs = $this->compactEnv($env);
1423
-
1424
-        $this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
1425
-            return ! isset($e->block->selectors);
1426
-        }));
1427
-
1428
-        $this->scope = $this->makeOutputBlock($block->type, $selectors);
1429
-        $this->scope->depth = 1;
1430
-        $this->scope->parent->children[] = $this->scope;
1431
-
1432
-        $this->compileChildrenNoReturn($block->children, $this->scope);
1433
-
1434
-        $this->scope = $this->scope->parent;
1435
-        $this->env   = $this->extractEnv($envs);
1436
-
1437
-        $this->popEnv();
1438
-    }
1439
-
1440
-    /**
1441
-     * Compile nested properties lines
1442
-     *
1443
-     * @param \ScssPhp\ScssPhp\Block                 $block
1444
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1445
-     */
1446
-    protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1447
-    {
1448
-        $prefix = $this->compileValue($block->prefix) . '-';
1449
-
1450
-        $nested = $this->makeOutputBlock($block->type);
1451
-        $nested->parent = $out;
1452
-
1453
-        if ($block->hasValue) {
1454
-            $nested->depth = $out->depth + 1;
1455
-        }
1456
-
1457
-        $out->children[] = $nested;
1458
-
1459
-        foreach ($block->children as $child) {
1460
-            switch ($child[0]) {
1461
-                case Type::T_ASSIGN:
1462
-                    array_unshift($child[1][2], $prefix);
1463
-                    break;
1464
-
1465
-                case Type::T_NESTED_PROPERTY:
1466
-                    array_unshift($child[1]->prefix[2], $prefix);
1467
-                    break;
1468
-            }
1469
-
1470
-            $this->compileChild($child, $nested);
1471
-        }
1472
-    }
1473
-
1474
-    /**
1475
-     * Compile nested block
1476
-     *
1477
-     * @param \ScssPhp\ScssPhp\Block $block
1478
-     * @param array                  $selectors
1479
-     */
1480
-    protected function compileNestedBlock(Block $block, $selectors)
1481
-    {
1482
-        $this->pushEnv($block);
1483
-
1484
-        $this->scope = $this->makeOutputBlock($block->type, $selectors);
1485
-        $this->scope->parent->children[] = $this->scope;
1486
-
1487
-        // wrap assign children in a block
1488
-        // except for @font-face
1489
-        if ($block->type !== Type::T_DIRECTIVE || $block->name !== 'font-face') {
1490
-            // need wrapping?
1491
-            $needWrapping = false;
1492
-
1493
-            foreach ($block->children as $child) {
1494
-                if ($child[0] === Type::T_ASSIGN) {
1495
-                    $needWrapping = true;
1496
-                    break;
1497
-                }
1498
-            }
1499
-
1500
-            if ($needWrapping) {
1501
-                $wrapped = new Block();
1502
-                $wrapped->sourceName   = $block->sourceName;
1503
-                $wrapped->sourceIndex  = $block->sourceIndex;
1504
-                $wrapped->sourceLine   = $block->sourceLine;
1505
-                $wrapped->sourceColumn = $block->sourceColumn;
1506
-                $wrapped->selectors    = [];
1507
-                $wrapped->comments     = [];
1508
-                $wrapped->parent       = $block;
1509
-                $wrapped->children     = $block->children;
1510
-                $wrapped->selfParent   = $block->selfParent;
1511
-
1512
-                $block->children = [[Type::T_BLOCK, $wrapped]];
1513
-            }
1514
-        }
1515
-
1516
-        $this->compileChildrenNoReturn($block->children, $this->scope);
1517
-
1518
-        $this->scope = $this->scope->parent;
1519
-
1520
-        $this->popEnv();
1521
-    }
1522
-
1523
-    /**
1524
-     * Recursively compiles a block.
1525
-     *
1526
-     * A block is analogous to a CSS block in most cases. A single SCSS document
1527
-     * is encapsulated in a block when parsed, but it does not have parent tags
1528
-     * so all of its children appear on the root level when compiled.
1529
-     *
1530
-     * Blocks are made up of selectors and children.
1531
-     *
1532
-     * The children of a block are just all the blocks that are defined within.
1533
-     *
1534
-     * Compiling the block involves pushing a fresh environment on the stack,
1535
-     * and iterating through the props, compiling each one.
1536
-     *
1537
-     * @see Compiler::compileChild()
1538
-     *
1539
-     * @param \ScssPhp\ScssPhp\Block $block
1540
-     */
1541
-    protected function compileBlock(Block $block)
1542
-    {
1543
-        $env = $this->pushEnv($block);
1544
-        $env->selectors = $this->evalSelectors($block->selectors);
1545
-
1546
-        $out = $this->makeOutputBlock(null);
1547
-
1548
-        if (isset($this->lineNumberStyle) && \count($env->selectors) && \count($block->children)) {
1549
-            $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1550
-            $annotation->depth = 0;
1551
-
1552
-            $file = $this->sourceNames[$block->sourceIndex];
1553
-            $line = $block->sourceLine;
1554
-
1555
-            switch ($this->lineNumberStyle) {
1556
-                case static::LINE_COMMENTS:
1557
-                    $annotation->lines[] = '/* line ' . $line
1558
-                                         . ($file ? ', ' . $file : '')
1559
-                                         . ' */';
1560
-                    break;
1561
-
1562
-                case static::DEBUG_INFO:
1563
-                    $annotation->lines[] = '@media -sass-debug-info{'
1564
-                                         . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1565
-                                         . 'line{font-family:' . $line . '}}';
1566
-                    break;
1567
-            }
1568
-
1569
-            $this->scope->children[] = $annotation;
1570
-        }
1571
-
1572
-        $this->scope->children[] = $out;
1573
-
1574
-        if (\count($block->children)) {
1575
-            $out->selectors = $this->multiplySelectors($env, $block->selfParent);
1576
-
1577
-            // propagate selfParent to the children where they still can be useful
1578
-            $selfParentSelectors = null;
1579
-
1580
-            if (isset($block->selfParent->selectors)) {
1581
-                $selfParentSelectors = $block->selfParent->selectors;
1582
-                $block->selfParent->selectors = $out->selectors;
1583
-            }
1584
-
1585
-            $this->compileChildrenNoReturn($block->children, $out, $block->selfParent);
1586
-
1587
-            // and revert for the following children of the same block
1588
-            if ($selfParentSelectors) {
1589
-                $block->selfParent->selectors = $selfParentSelectors;
1590
-            }
1591
-        }
1592
-
1593
-        $this->popEnv();
1594
-    }
1595
-
1596
-
1597
-    /**
1598
-     * Compile the value of a comment that can have interpolation
1599
-     *
1600
-     * @param array   $value
1601
-     * @param boolean $pushEnv
1602
-     *
1603
-     * @return array|mixed|string
1604
-     */
1605
-    protected function compileCommentValue($value, $pushEnv = false)
1606
-    {
1607
-        $c = $value[1];
1608
-
1609
-        if (isset($value[2])) {
1610
-            if ($pushEnv) {
1611
-                $this->pushEnv();
1612
-            }
1613
-
1614
-            $ignoreCallStackMessage = $this->ignoreCallStackMessage;
1615
-            $this->ignoreCallStackMessage = true;
1616
-
1617
-            try {
1618
-                $c = $this->compileValue($value[2]);
1619
-            } catch (\Exception $e) {
1620
-                // ignore error in comment compilation which are only interpolation
1621
-            }
1622
-
1623
-            $this->ignoreCallStackMessage = $ignoreCallStackMessage;
1624
-
1625
-            if ($pushEnv) {
1626
-                $this->popEnv();
1627
-            }
1628
-        }
1629
-
1630
-        return $c;
1631
-    }
1632
-
1633
-    /**
1634
-     * Compile root level comment
1635
-     *
1636
-     * @param array $block
1637
-     */
1638
-    protected function compileComment($block)
1639
-    {
1640
-        $out = $this->makeOutputBlock(Type::T_COMMENT);
1641
-        $out->lines[] = $this->compileCommentValue($block, true);
1642
-
1643
-        $this->scope->children[] = $out;
1644
-    }
1645
-
1646
-    /**
1647
-     * Evaluate selectors
1648
-     *
1649
-     * @param array $selectors
1650
-     *
1651
-     * @return array
1652
-     */
1653
-    protected function evalSelectors($selectors)
1654
-    {
1655
-        $this->shouldEvaluate = false;
1656
-
1657
-        $selectors = array_map([$this, 'evalSelector'], $selectors);
1658
-
1659
-        // after evaluating interpolates, we might need a second pass
1660
-        if ($this->shouldEvaluate) {
1661
-            $selectors = $this->replaceSelfSelector($selectors, '&');
1662
-            $buffer    = $this->collapseSelectors($selectors);
1663
-            $parser    = $this->parserFactory(__METHOD__);
1664
-
1665
-            if ($parser->parseSelector($buffer, $newSelectors)) {
1666
-                $selectors = array_map([$this, 'evalSelector'], $newSelectors);
1667
-            }
1668
-        }
1669
-
1670
-        return $selectors;
1671
-    }
1672
-
1673
-    /**
1674
-     * Evaluate selector
1675
-     *
1676
-     * @param array $selector
1677
-     *
1678
-     * @return array
1679
-     */
1680
-    protected function evalSelector($selector)
1681
-    {
1682
-        return array_map([$this, 'evalSelectorPart'], $selector);
1683
-    }
1684
-
1685
-    /**
1686
-     * Evaluate selector part; replaces all the interpolates, stripping quotes
1687
-     *
1688
-     * @param array $part
1689
-     *
1690
-     * @return array
1691
-     */
1692
-    protected function evalSelectorPart($part)
1693
-    {
1694
-        foreach ($part as &$p) {
1695
-            if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1696
-                $p = $this->compileValue($p);
1697
-
1698
-                // force re-evaluation
1699
-                if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1700
-                    $this->shouldEvaluate = true;
1701
-                }
1702
-            } elseif (
1703
-                \is_string($p) && \strlen($p) >= 2 &&
1704
-                ($first = $p[0]) && ($first === '"' || $first === "'") &&
1705
-                substr($p, -1) === $first
1706
-            ) {
1707
-                $p = substr($p, 1, -1);
1708
-            }
1709
-        }
1710
-
1711
-        return $this->flattenSelectorSingle($part);
1712
-    }
1713
-
1714
-    /**
1715
-     * Collapse selectors
1716
-     *
1717
-     * @param array   $selectors
1718
-     * @param boolean $selectorFormat
1719
-     *   if false return a collapsed string
1720
-     *   if true return an array description of a structured selector
1721
-     *
1722
-     * @return string
1723
-     */
1724
-    protected function collapseSelectors($selectors, $selectorFormat = false)
1725
-    {
1726
-        $parts = [];
1727
-
1728
-        foreach ($selectors as $selector) {
1729
-            $output = [];
1730
-            $glueNext = false;
1731
-
1732
-            foreach ($selector as $node) {
1733
-                $compound = '';
1734
-
1735
-                array_walk_recursive(
1736
-                    $node,
1737
-                    function ($value, $key) use (&$compound) {
1738
-                        $compound .= $value;
1739
-                    }
1740
-                );
1741
-
1742
-                if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) {
1743
-                    if (\count($output)) {
1744
-                        $output[\count($output) - 1] .= ' ' . $compound;
1745
-                    } else {
1746
-                        $output[] = $compound;
1747
-                    }
1748
-
1749
-                    $glueNext = true;
1750
-                } elseif ($glueNext) {
1751
-                    $output[\count($output) - 1] .= ' ' . $compound;
1752
-                    $glueNext = false;
1753
-                } else {
1754
-                    $output[] = $compound;
1755
-                }
1756
-            }
1757
-
1758
-            if ($selectorFormat) {
1759
-                foreach ($output as &$o) {
1760
-                    $o = [Type::T_STRING, '', [$o]];
1761
-                }
1762
-
1763
-                $output = [Type::T_LIST, ' ', $output];
1764
-            } else {
1765
-                $output = implode(' ', $output);
1766
-            }
1767
-
1768
-            $parts[] = $output;
1769
-        }
1770
-
1771
-        if ($selectorFormat) {
1772
-            $parts = [Type::T_LIST, ',', $parts];
1773
-        } else {
1774
-            $parts = implode(', ', $parts);
1775
-        }
1776
-
1777
-        return $parts;
1778
-    }
1779
-
1780
-    /**
1781
-     * Parse down the selector and revert [self] to "&" before a reparsing
1782
-     *
1783
-     * @param array $selectors
1784
-     *
1785
-     * @return array
1786
-     */
1787
-    protected function replaceSelfSelector($selectors, $replace = null)
1788
-    {
1789
-        foreach ($selectors as &$part) {
1790
-            if (\is_array($part)) {
1791
-                if ($part === [Type::T_SELF]) {
1792
-                    if (\is_null($replace)) {
1793
-                        $replace = $this->reduce([Type::T_SELF]);
1794
-                        $replace = $this->compileValue($replace);
1795
-                    }
1796
-                    $part = $replace;
1797
-                } else {
1798
-                    $part = $this->replaceSelfSelector($part, $replace);
1799
-                }
1800
-            }
1801
-        }
1802
-
1803
-        return $selectors;
1804
-    }
1805
-
1806
-    /**
1807
-     * Flatten selector single; joins together .classes and #ids
1808
-     *
1809
-     * @param array $single
1810
-     *
1811
-     * @return array
1812
-     */
1813
-    protected function flattenSelectorSingle($single)
1814
-    {
1815
-        $joined = [];
1816
-
1817
-        foreach ($single as $part) {
1818
-            if (
1819
-                empty($joined) ||
1820
-                ! \is_string($part) ||
1821
-                preg_match('/[\[.:#%]/', $part)
1822
-            ) {
1823
-                $joined[] = $part;
1824
-                continue;
1825
-            }
1826
-
1827
-            if (\is_array(end($joined))) {
1828
-                $joined[] = $part;
1829
-            } else {
1830
-                $joined[\count($joined) - 1] .= $part;
1831
-            }
1832
-        }
1833
-
1834
-        return $joined;
1835
-    }
1836
-
1837
-    /**
1838
-     * Compile selector to string; self(&) should have been replaced by now
1839
-     *
1840
-     * @param string|array $selector
1841
-     *
1842
-     * @return string
1843
-     */
1844
-    protected function compileSelector($selector)
1845
-    {
1846
-        if (! \is_array($selector)) {
1847
-            return $selector; // media and the like
1848
-        }
1849
-
1850
-        return implode(
1851
-            ' ',
1852
-            array_map(
1853
-                [$this, 'compileSelectorPart'],
1854
-                $selector
1855
-            )
1856
-        );
1857
-    }
1858
-
1859
-    /**
1860
-     * Compile selector part
1861
-     *
1862
-     * @param array $piece
1863
-     *
1864
-     * @return string
1865
-     */
1866
-    protected function compileSelectorPart($piece)
1867
-    {
1868
-        foreach ($piece as &$p) {
1869
-            if (! \is_array($p)) {
1870
-                continue;
1871
-            }
1872
-
1873
-            switch ($p[0]) {
1874
-                case Type::T_SELF:
1875
-                    $p = '&';
1876
-                    break;
1877
-
1878
-                default:
1879
-                    $p = $this->compileValue($p);
1880
-                    break;
1881
-            }
1882
-        }
1883
-
1884
-        return implode($piece);
1885
-    }
1886
-
1887
-    /**
1888
-     * Has selector placeholder?
1889
-     *
1890
-     * @param array $selector
1891
-     *
1892
-     * @return boolean
1893
-     */
1894
-    protected function hasSelectorPlaceholder($selector)
1895
-    {
1896
-        if (! \is_array($selector)) {
1897
-            return false;
1898
-        }
1899
-
1900
-        foreach ($selector as $parts) {
1901
-            foreach ($parts as $part) {
1902
-                if (\strlen($part) && '%' === $part[0]) {
1903
-                    return true;
1904
-                }
1905
-            }
1906
-        }
1907
-
1908
-        return false;
1909
-    }
1910
-
1911
-    protected function pushCallStack($name = '')
1912
-    {
1913
-        $this->callStack[] = [
1914
-          'n' => $name,
1915
-          Parser::SOURCE_INDEX => $this->sourceIndex,
1916
-          Parser::SOURCE_LINE => $this->sourceLine,
1917
-          Parser::SOURCE_COLUMN => $this->sourceColumn
1918
-        ];
1919
-
1920
-        // infinite calling loop
1921
-        if (\count($this->callStack) > 25000) {
1922
-            // not displayed but you can var_dump it to deep debug
1923
-            $msg = $this->callStackMessage(true, 100);
1924
-            $msg = 'Infinite calling loop';
1925
-
1926
-            throw $this->error($msg);
1927
-        }
1928
-    }
1929
-
1930
-    protected function popCallStack()
1931
-    {
1932
-        array_pop($this->callStack);
1933
-    }
1934
-
1935
-    /**
1936
-     * Compile children and return result
1937
-     *
1938
-     * @param array                                  $stms
1939
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1940
-     * @param string                                 $traceName
1941
-     *
1942
-     * @return array|null
1943
-     */
1944
-    protected function compileChildren($stms, OutputBlock $out, $traceName = '')
1945
-    {
1946
-        $this->pushCallStack($traceName);
1947
-
1948
-        foreach ($stms as $stm) {
1949
-            $ret = $this->compileChild($stm, $out);
1950
-
1951
-            if (isset($ret)) {
1952
-                $this->popCallStack();
1953
-
1954
-                return $ret;
1955
-            }
1956
-        }
1957
-
1958
-        $this->popCallStack();
1959
-
1960
-        return null;
1961
-    }
1962
-
1963
-    /**
1964
-     * Compile children and throw exception if unexpected @return
1965
-     *
1966
-     * @param array                                  $stms
1967
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1968
-     * @param \ScssPhp\ScssPhp\Block                 $selfParent
1969
-     * @param string                                 $traceName
1970
-     *
1971
-     * @throws \Exception
1972
-     */
1973
-    protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
1974
-    {
1975
-        $this->pushCallStack($traceName);
1976
-
1977
-        foreach ($stms as $stm) {
1978
-            if ($selfParent && isset($stm[1]) && \is_object($stm[1]) && $stm[1] instanceof Block) {
1979
-                $stm[1]->selfParent = $selfParent;
1980
-                $ret = $this->compileChild($stm, $out);
1981
-                $stm[1]->selfParent = null;
1982
-            } elseif ($selfParent && \in_array($stm[0], [TYPE::T_INCLUDE, TYPE::T_EXTEND])) {
1983
-                $stm['selfParent'] = $selfParent;
1984
-                $ret = $this->compileChild($stm, $out);
1985
-                unset($stm['selfParent']);
1986
-            } else {
1987
-                $ret = $this->compileChild($stm, $out);
1988
-            }
1989
-
1990
-            if (isset($ret)) {
1991
-                throw $this->error('@return may only be used within a function');
1992
-            }
1993
-        }
1994
-
1995
-        $this->popCallStack();
1996
-    }
1997
-
1998
-
1999
-    /**
2000
-     * evaluate media query : compile internal value keeping the structure inchanged
2001
-     *
2002
-     * @param array $queryList
2003
-     *
2004
-     * @return array
2005
-     */
2006
-    protected function evaluateMediaQuery($queryList)
2007
-    {
2008
-        static $parser = null;
2009
-
2010
-        $outQueryList = [];
2011
-
2012
-        foreach ($queryList as $kql => $query) {
2013
-            $shouldReparse = false;
2014
-
2015
-            foreach ($query as $kq => $q) {
2016
-                for ($i = 1; $i < \count($q); $i++) {
2017
-                    $value = $this->compileValue($q[$i]);
2018
-
2019
-                    // the parser had no mean to know if media type or expression if it was an interpolation
2020
-                    // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
2021
-                    if (
2022
-                        $q[0] == Type::T_MEDIA_TYPE &&
2023
-                        (strpos($value, '(') !== false ||
2024
-                        strpos($value, ')') !== false ||
2025
-                        strpos($value, ':') !== false ||
2026
-                        strpos($value, ',') !== false)
2027
-                    ) {
2028
-                        $shouldReparse = true;
2029
-                    }
2030
-
2031
-                    $queryList[$kql][$kq][$i] = [Type::T_KEYWORD, $value];
2032
-                }
2033
-            }
2034
-
2035
-            if ($shouldReparse) {
2036
-                if (\is_null($parser)) {
2037
-                    $parser = $this->parserFactory(__METHOD__);
2038
-                }
2039
-
2040
-                $queryString = $this->compileMediaQuery([$queryList[$kql]]);
2041
-                $queryString = reset($queryString);
2042
-
2043
-                if (strpos($queryString, '@media ') === 0) {
2044
-                    $queryString = substr($queryString, 7);
2045
-                    $queries = [];
2046
-
2047
-                    if ($parser->parseMediaQueryList($queryString, $queries)) {
2048
-                        $queries = $this->evaluateMediaQuery($queries[2]);
2049
-
2050
-                        while (\count($queries)) {
2051
-                            $outQueryList[] = array_shift($queries);
2052
-                        }
2053
-
2054
-                        continue;
2055
-                    }
2056
-                }
2057
-            }
2058
-
2059
-            $outQueryList[] = $queryList[$kql];
2060
-        }
2061
-
2062
-        return $outQueryList;
2063
-    }
2064
-
2065
-    /**
2066
-     * Compile media query
2067
-     *
2068
-     * @param array $queryList
2069
-     *
2070
-     * @return array
2071
-     */
2072
-    protected function compileMediaQuery($queryList)
2073
-    {
2074
-        $start   = '@media ';
2075
-        $default = trim($start);
2076
-        $out     = [];
2077
-        $current = '';
2078
-
2079
-        foreach ($queryList as $query) {
2080
-            $type = null;
2081
-            $parts = [];
2082
-
2083
-            $mediaTypeOnly = true;
2084
-
2085
-            foreach ($query as $q) {
2086
-                if ($q[0] !== Type::T_MEDIA_TYPE) {
2087
-                    $mediaTypeOnly = false;
2088
-                    break;
2089
-                }
2090
-            }
2091
-
2092
-            foreach ($query as $q) {
2093
-                switch ($q[0]) {
2094
-                    case Type::T_MEDIA_TYPE:
2095
-                        $newType = array_map([$this, 'compileValue'], \array_slice($q, 1));
2096
-
2097
-                        // combining not and anything else than media type is too risky and should be avoided
2098
-                        if (! $mediaTypeOnly) {
2099
-                            if (\in_array(Type::T_NOT, $newType) || ($type && \in_array(Type::T_NOT, $type) )) {
2100
-                                if ($type) {
2101
-                                    array_unshift($parts, implode(' ', array_filter($type)));
2102
-                                }
2103
-
2104
-                                if (! empty($parts)) {
2105
-                                    if (\strlen($current)) {
2106
-                                        $current .= $this->formatter->tagSeparator;
2107
-                                    }
2108
-
2109
-                                    $current .= implode(' and ', $parts);
2110
-                                }
2111
-
2112
-                                if ($current) {
2113
-                                    $out[] = $start . $current;
2114
-                                }
2115
-
2116
-                                $current = '';
2117
-                                $type    = null;
2118
-                                $parts   = [];
2119
-                            }
2120
-                        }
2121
-
2122
-                        if ($newType === ['all'] && $default) {
2123
-                            $default = $start . 'all';
2124
-                        }
2125
-
2126
-                        // all can be safely ignored and mixed with whatever else
2127
-                        if ($newType !== ['all']) {
2128
-                            if ($type) {
2129
-                                $type = $this->mergeMediaTypes($type, $newType);
2130
-
2131
-                                if (empty($type)) {
2132
-                                    // merge failed : ignore this query that is not valid, skip to the next one
2133
-                                    $parts = [];
2134
-                                    $default = ''; // if everything fail, no @media at all
2135
-                                    continue 3;
2136
-                                }
2137
-                            } else {
2138
-                                $type = $newType;
2139
-                            }
2140
-                        }
2141
-                        break;
2142
-
2143
-                    case Type::T_MEDIA_EXPRESSION:
2144
-                        if (isset($q[2])) {
2145
-                            $parts[] = '('
2146
-                                . $this->compileValue($q[1])
2147
-                                . $this->formatter->assignSeparator
2148
-                                . $this->compileValue($q[2])
2149
-                                . ')';
2150
-                        } else {
2151
-                            $parts[] = '('
2152
-                                . $this->compileValue($q[1])
2153
-                                . ')';
2154
-                        }
2155
-                        break;
2156
-
2157
-                    case Type::T_MEDIA_VALUE:
2158
-                        $parts[] = $this->compileValue($q[1]);
2159
-                        break;
2160
-                }
2161
-            }
2162
-
2163
-            if ($type) {
2164
-                array_unshift($parts, implode(' ', array_filter($type)));
2165
-            }
2166
-
2167
-            if (! empty($parts)) {
2168
-                if (\strlen($current)) {
2169
-                    $current .= $this->formatter->tagSeparator;
2170
-                }
2171
-
2172
-                $current .= implode(' and ', $parts);
2173
-            }
2174
-        }
2175
-
2176
-        if ($current) {
2177
-            $out[] = $start . $current;
2178
-        }
2179
-
2180
-        // no @media type except all, and no conflict?
2181
-        if (! $out && $default) {
2182
-            $out[] = $default;
2183
-        }
2184
-
2185
-        return $out;
2186
-    }
2187
-
2188
-    /**
2189
-     * Merge direct relationships between selectors
2190
-     *
2191
-     * @param array $selectors1
2192
-     * @param array $selectors2
2193
-     *
2194
-     * @return array
2195
-     */
2196
-    protected function mergeDirectRelationships($selectors1, $selectors2)
2197
-    {
2198
-        if (empty($selectors1) || empty($selectors2)) {
2199
-            return array_merge($selectors1, $selectors2);
2200
-        }
2201
-
2202
-        $part1 = end($selectors1);
2203
-        $part2 = end($selectors2);
2204
-
2205
-        if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2206
-            return array_merge($selectors1, $selectors2);
2207
-        }
2208
-
2209
-        $merged = [];
2210
-
2211
-        do {
2212
-            $part1 = array_pop($selectors1);
2213
-            $part2 = array_pop($selectors2);
2214
-
2215
-            if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2216
-                if ($this->isImmediateRelationshipCombinator(reset($merged)[0])) {
2217
-                    array_unshift($merged, [$part1[0] . $part2[0]]);
2218
-                    $merged = array_merge($selectors1, $selectors2, $merged);
2219
-                } else {
2220
-                    $merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged);
2221
-                }
2222
-
2223
-                break;
2224
-            }
2225
-
2226
-            array_unshift($merged, $part1);
2227
-        } while (! empty($selectors1) && ! empty($selectors2));
2228
-
2229
-        return $merged;
2230
-    }
2231
-
2232
-    /**
2233
-     * Merge media types
2234
-     *
2235
-     * @param array $type1
2236
-     * @param array $type2
2237
-     *
2238
-     * @return array|null
2239
-     */
2240
-    protected function mergeMediaTypes($type1, $type2)
2241
-    {
2242
-        if (empty($type1)) {
2243
-            return $type2;
2244
-        }
2245
-
2246
-        if (empty($type2)) {
2247
-            return $type1;
2248
-        }
2249
-
2250
-        if (\count($type1) > 1) {
2251
-            $m1 = strtolower($type1[0]);
2252
-            $t1 = strtolower($type1[1]);
2253
-        } else {
2254
-            $m1 = '';
2255
-            $t1 = strtolower($type1[0]);
2256
-        }
2257
-
2258
-        if (\count($type2) > 1) {
2259
-            $m2 = strtolower($type2[0]);
2260
-            $t2 = strtolower($type2[1]);
2261
-        } else {
2262
-            $m2 = '';
2263
-            $t2 = strtolower($type2[0]);
2264
-        }
2265
-
2266
-        if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) {
2267
-            if ($t1 === $t2) {
2268
-                return null;
2269
-            }
2270
-
2271
-            return [
2272
-                $m1 === Type::T_NOT ? $m2 : $m1,
2273
-                $m1 === Type::T_NOT ? $t2 : $t1,
2274
-            ];
2275
-        }
2276
-
2277
-        if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) {
2278
-            // CSS has no way of representing "neither screen nor print"
2279
-            if ($t1 !== $t2) {
2280
-                return null;
2281
-            }
2282
-
2283
-            return [Type::T_NOT, $t1];
2284
-        }
2285
-
2286
-        if ($t1 !== $t2) {
2287
-            return null;
2288
-        }
2289
-
2290
-        // t1 == t2, neither m1 nor m2 are "not"
2291
-        return [empty($m1) ? $m2 : $m1, $t1];
2292
-    }
2293
-
2294
-    /**
2295
-     * Compile import; returns true if the value was something that could be imported
2296
-     *
2297
-     * @param array                                  $rawPath
2298
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2299
-     * @param boolean                                $once
2300
-     *
2301
-     * @return boolean
2302
-     */
2303
-    protected function compileImport($rawPath, OutputBlock $out, $once = false)
2304
-    {
2305
-        if ($rawPath[0] === Type::T_STRING) {
2306
-            $path = $this->compileStringContent($rawPath);
2307
-
2308
-            if (strpos($path, 'url(') !== 0 && $path = $this->findImport($path)) {
2309
-                if (! $once || ! \in_array($path, $this->importedFiles)) {
2310
-                    $this->importFile($path, $out);
2311
-                    $this->importedFiles[] = $path;
2312
-                }
2313
-
2314
-                return true;
2315
-            }
2316
-
2317
-            $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2318
-
2319
-            return false;
2320
-        }
2321
-
2322
-        if ($rawPath[0] === Type::T_LIST) {
2323
-            // handle a list of strings
2324
-            if (\count($rawPath[2]) === 0) {
2325
-                return false;
2326
-            }
2327
-
2328
-            foreach ($rawPath[2] as $path) {
2329
-                if ($path[0] !== Type::T_STRING) {
2330
-                    $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2331
-
2332
-                    return false;
2333
-                }
2334
-            }
2335
-
2336
-            foreach ($rawPath[2] as $path) {
2337
-                $this->compileImport($path, $out, $once);
2338
-            }
2339
-
2340
-            return true;
2341
-        }
2342
-
2343
-        $this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2344
-
2345
-        return false;
2346
-    }
2347
-
2348
-    /**
2349
-     * @param $rawPath
2350
-     * @return string
2351
-     * @throws CompilerException
2352
-     */
2353
-    protected function compileImportPath($rawPath)
2354
-    {
2355
-        $path = $this->compileValue($rawPath);
2356
-
2357
-        // case url() without quotes : supress \r \n remaining in the path
2358
-        // if this is a real string there can not be CR or LF char
2359
-        if (strpos($path, 'url(') === 0) {
2360
-            $path = str_replace(array("\r", "\n"), array('', ' '), $path);
2361
-        } else {
2362
-            // if this is a file name in a string, spaces shoudl be escaped
2363
-            $path = $this->reduce($rawPath);
2364
-            $path = $this->escapeImportPathString($path);
2365
-            $path = $this->compileValue($path);
2366
-        }
2367
-
2368
-        return $path;
2369
-    }
2370
-
2371
-    /**
2372
-     * @param array $path
2373
-     * @return array
2374
-     * @throws CompilerException
2375
-     */
2376
-    protected function escapeImportPathString($path)
2377
-    {
2378
-        switch ($path[0]) {
2379
-            case Type::T_LIST:
2380
-                foreach ($path[2] as $k => $v) {
2381
-                    $path[2][$k] = $this->escapeImportPathString($v);
2382
-                }
2383
-                break;
2384
-            case Type::T_STRING:
2385
-                if ($path[1]) {
2386
-                    $path = $this->compileValue($path);
2387
-                    $path = str_replace(' ', '\\ ', $path);
2388
-                    $path = [Type::T_KEYWORD, $path];
2389
-                }
2390
-                break;
2391
-        }
2392
-
2393
-        return $path;
2394
-    }
2395
-
2396
-    /**
2397
-     * Append a root directive like @import or @charset as near as the possible from the source code
2398
-     * (keeping before comments, @import and @charset coming before in the source code)
2399
-     *
2400
-     * @param string                                        $line
2401
-     * @param @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2402
-     * @param array                                         $allowed
2403
-     */
2404
-    protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2405
-    {
2406
-        $root = $out;
2407
-
2408
-        while ($root->parent) {
2409
-            $root = $root->parent;
2410
-        }
2411
-
2412
-        $i = 0;
2413
-
2414
-        while ($i < \count($root->children)) {
2415
-            if (! isset($root->children[$i]->type) || ! \in_array($root->children[$i]->type, $allowed)) {
2416
-                break;
2417
-            }
2418
-
2419
-            $i++;
2420
-        }
2421
-
2422
-        // remove incompatible children from the bottom of the list
2423
-        $saveChildren = [];
2424
-
2425
-        while ($i < \count($root->children)) {
2426
-            $saveChildren[] = array_pop($root->children);
2427
-        }
2428
-
2429
-        // insert the directive as a comment
2430
-        $child = $this->makeOutputBlock(Type::T_COMMENT);
2431
-        $child->lines[]      = $line;
2432
-        $child->sourceName   = $this->sourceNames[$this->sourceIndex];
2433
-        $child->sourceLine   = $this->sourceLine;
2434
-        $child->sourceColumn = $this->sourceColumn;
2435
-
2436
-        $root->children[] = $child;
2437
-
2438
-        // repush children
2439
-        while (\count($saveChildren)) {
2440
-            $root->children[] = array_pop($saveChildren);
2441
-        }
2442
-    }
2443
-
2444
-    /**
2445
-     * Append lines to the current output block:
2446
-     * directly to the block or through a child if necessary
2447
-     *
2448
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2449
-     * @param string                                 $type
2450
-     * @param string|mixed                           $line
2451
-     */
2452
-    protected function appendOutputLine(OutputBlock $out, $type, $line)
2453
-    {
2454
-        $outWrite = &$out;
2455
-
2456
-        // check if it's a flat output or not
2457
-        if (\count($out->children)) {
2458
-            $lastChild = &$out->children[\count($out->children) - 1];
2459
-
2460
-            if (
2461
-                $lastChild->depth === $out->depth &&
2462
-                \is_null($lastChild->selectors) &&
2463
-                ! \count($lastChild->children)
2464
-            ) {
2465
-                $outWrite = $lastChild;
2466
-            } else {
2467
-                $nextLines = $this->makeOutputBlock($type);
2468
-                $nextLines->parent = $out;
2469
-                $nextLines->depth  = $out->depth;
2470
-
2471
-                $out->children[] = $nextLines;
2472
-                $outWrite = &$nextLines;
2473
-            }
2474
-        }
2475
-
2476
-        $outWrite->lines[] = $line;
2477
-    }
2478
-
2479
-    /**
2480
-     * Compile child; returns a value to halt execution
2481
-     *
2482
-     * @param array                                  $child
2483
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2484
-     *
2485
-     * @return array
2486
-     */
2487
-    protected function compileChild($child, OutputBlock $out)
2488
-    {
2489
-        if (isset($child[Parser::SOURCE_LINE])) {
2490
-            $this->sourceIndex  = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
2491
-            $this->sourceLine   = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
2492
-            $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
2493
-        } elseif (\is_array($child) && isset($child[1]->sourceLine)) {
2494
-            $this->sourceIndex  = $child[1]->sourceIndex;
2495
-            $this->sourceLine   = $child[1]->sourceLine;
2496
-            $this->sourceColumn = $child[1]->sourceColumn;
2497
-        } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2498
-            $this->sourceLine   = $out->sourceLine;
2499
-            $this->sourceIndex  = array_search($out->sourceName, $this->sourceNames);
2500
-            $this->sourceColumn = $out->sourceColumn;
2501
-
2502
-            if ($this->sourceIndex === false) {
2503
-                $this->sourceIndex = null;
2504
-            }
2505
-        }
2506
-
2507
-        switch ($child[0]) {
2508
-            case Type::T_SCSSPHP_IMPORT_ONCE:
2509
-                $rawPath = $this->reduce($child[1]);
2510
-
2511
-                $this->compileImport($rawPath, $out, true);
2512
-                break;
2513
-
2514
-            case Type::T_IMPORT:
2515
-                $rawPath = $this->reduce($child[1]);
2516
-
2517
-                $this->compileImport($rawPath, $out);
2518
-                break;
2519
-
2520
-            case Type::T_DIRECTIVE:
2521
-                $this->compileDirective($child[1], $out);
2522
-                break;
2523
-
2524
-            case Type::T_AT_ROOT:
2525
-                $this->compileAtRoot($child[1]);
2526
-                break;
2527
-
2528
-            case Type::T_MEDIA:
2529
-                $this->compileMedia($child[1]);
2530
-                break;
2531
-
2532
-            case Type::T_BLOCK:
2533
-                $this->compileBlock($child[1]);
2534
-                break;
2535
-
2536
-            case Type::T_CHARSET:
2537
-                if (! $this->charsetSeen) {
2538
-                    $this->charsetSeen = true;
2539
-                    $this->appendRootDirective('@charset ' . $this->compileValue($child[1]) . ';', $out);
2540
-                }
2541
-                break;
2542
-
2543
-            case Type::T_CUSTOM_PROPERTY:
2544
-                list(, $name, $value) = $child;
2545
-                $compiledName = $this->compileValue($name);
2546
-
2547
-                // if the value reduces to null from something else then
2548
-                // the property should be discarded
2549
-                if ($value[0] !== Type::T_NULL) {
2550
-                    $value = $this->reduce($value);
2551
-
2552
-                    if ($value[0] === Type::T_NULL || $value === static::$nullString) {
2553
-                        break;
2554
-                    }
2555
-                }
2556
-
2557
-                $compiledValue = $this->compileValue($value);
2558
-
2559
-                $line = $this->formatter->customProperty(
2560
-                    $compiledName,
2561
-                    $compiledValue
2562
-                );
2563
-
2564
-                $this->appendOutputLine($out, Type::T_ASSIGN, $line);
2565
-                break;
2566
-
2567
-            case Type::T_ASSIGN:
2568
-                list(, $name, $value) = $child;
2569
-
2570
-                if ($name[0] === Type::T_VARIABLE) {
2571
-                    $flags     = isset($child[3]) ? $child[3] : [];
2572
-                    $isDefault = \in_array('!default', $flags);
2573
-                    $isGlobal  = \in_array('!global', $flags);
2574
-
2575
-                    if ($isGlobal) {
2576
-                        $this->set($name[1], $this->reduce($value), false, $this->rootEnv, $value);
2577
-                        break;
2578
-                    }
2579
-
2580
-                    $shouldSet = $isDefault &&
2581
-                        (\is_null($result = $this->get($name[1], false)) ||
2582
-                        $result === static::$null);
2583
-
2584
-                    if (! $isDefault || $shouldSet) {
2585
-                        $this->set($name[1], $this->reduce($value), true, null, $value);
2586
-                    }
2587
-                    break;
2588
-                }
2589
-
2590
-                $compiledName = $this->compileValue($name);
2591
-
2592
-                // handle shorthand syntaxes : size / line-height...
2593
-                if (\in_array($compiledName, ['font', 'grid-row', 'grid-column', 'border-radius'])) {
2594
-                    if ($value[0] === Type::T_VARIABLE) {
2595
-                        // if the font value comes from variable, the content is already reduced
2596
-                        // (i.e., formulas were already calculated), so we need the original unreduced value
2597
-                        $value = $this->get($value[1], true, null, true);
2598
-                    }
2599
-
2600
-                    $shorthandValue=&$value;
2601
-
2602
-                    $shorthandDividerNeedsUnit = false;
2603
-                    $maxListElements           = null;
2604
-                    $maxShorthandDividers      = 1;
2605
-
2606
-                    switch ($compiledName) {
2607
-                        case 'border-radius':
2608
-                            $maxListElements = 4;
2609
-                            $shorthandDividerNeedsUnit = true;
2610
-                            break;
2611
-                    }
2612
-
2613
-                    if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
2614
-                        // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2615
-                        // we need to handle the first list element
2616
-                        $shorthandValue=&$value[2][0];
2617
-                    }
2618
-
2619
-                    if ($shorthandValue[0] === Type::T_EXPRESSION && $shorthandValue[1] === '/') {
2620
-                        $revert = true;
2621
-
2622
-                        if ($shorthandDividerNeedsUnit) {
2623
-                            $divider = $shorthandValue[3];
2624
-
2625
-                            if (\is_array($divider)) {
2626
-                                $divider = $this->reduce($divider, true);
2627
-                            }
2628
-
2629
-                            if (\intval($divider->dimension) && ! \count($divider->units)) {
2630
-                                $revert = false;
2631
-                            }
2632
-                        }
2633
-
2634
-                        if ($revert) {
2635
-                            $shorthandValue = $this->expToString($shorthandValue);
2636
-                        }
2637
-                    } elseif ($shorthandValue[0] === Type::T_LIST) {
2638
-                        foreach ($shorthandValue[2] as &$item) {
2639
-                            if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
2640
-                                if ($maxShorthandDividers > 0) {
2641
-                                    $revert = true;
2642
-
2643
-                                    // if the list of values is too long, this has to be a shorthand,
2644
-                                    // otherwise it could be a real division
2645
-                                    if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) {
2646
-                                        if ($shorthandDividerNeedsUnit) {
2647
-                                            $divider = $item[3];
2648
-
2649
-                                            if (\is_array($divider)) {
2650
-                                                $divider = $this->reduce($divider, true);
2651
-                                            }
2652
-
2653
-                                            if (\intval($divider->dimension) && ! \count($divider->units)) {
2654
-                                                $revert = false;
2655
-                                            }
2656
-                                        }
2657
-                                    }
2658
-
2659
-                                    if ($revert) {
2660
-                                        $item = $this->expToString($item);
2661
-                                        $maxShorthandDividers--;
2662
-                                    }
2663
-                                }
2664
-                            }
2665
-                        }
2666
-                    }
2667
-                }
2668
-
2669
-                // if the value reduces to null from something else then
2670
-                // the property should be discarded
2671
-                if ($value[0] !== Type::T_NULL) {
2672
-                    $value = $this->reduce($value);
2673
-
2674
-                    if ($value[0] === Type::T_NULL || $value === static::$nullString) {
2675
-                        break;
2676
-                    }
2677
-                }
2678
-
2679
-                $compiledValue = $this->compileValue($value);
2680
-
2681
-                // ignore empty value
2682
-                if (\strlen($compiledValue)) {
2683
-                    $line = $this->formatter->property(
2684
-                        $compiledName,
2685
-                        $compiledValue
2686
-                    );
2687
-                    $this->appendOutputLine($out, Type::T_ASSIGN, $line);
2688
-                }
2689
-                break;
2690
-
2691
-            case Type::T_COMMENT:
2692
-                if ($out->type === Type::T_ROOT) {
2693
-                    $this->compileComment($child);
2694
-                    break;
2695
-                }
2696
-
2697
-                $line = $this->compileCommentValue($child, true);
2698
-                $this->appendOutputLine($out, Type::T_COMMENT, $line);
2699
-                break;
2700
-
2701
-            case Type::T_MIXIN:
2702
-            case Type::T_FUNCTION:
2703
-                list(, $block) = $child;
2704
-                // the block need to be able to go up to it's parent env to resolve vars
2705
-                $block->parentEnv = $this->getStoreEnv();
2706
-                $this->set(static::$namespaces[$block->type] . $block->name, $block, true);
2707
-                break;
2708
-
2709
-            case Type::T_EXTEND:
2710
-                foreach ($child[1] as $sel) {
2711
-                    $sel = $this->replaceSelfSelector($sel);
2712
-                    $results = $this->evalSelectors([$sel]);
2713
-
2714
-                    foreach ($results as $result) {
2715
-                        // only use the first one
2716
-                        $result = current($result);
2717
-                        $selectors = $out->selectors;
2718
-
2719
-                        if (! $selectors && isset($child['selfParent'])) {
2720
-                            $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
2721
-                        }
2722
-
2723
-                        $this->pushExtends($result, $selectors, $child);
2724
-                    }
2725
-                }
2726
-                break;
2727
-
2728
-            case Type::T_IF:
2729
-                list(, $if) = $child;
2730
-
2731
-                if ($this->isTruthy($this->reduce($if->cond, true))) {
2732
-                    return $this->compileChildren($if->children, $out);
2733
-                }
2734
-
2735
-                foreach ($if->cases as $case) {
2736
-                    if (
2737
-                        $case->type === Type::T_ELSE ||
2738
-                        $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
2739
-                    ) {
2740
-                        return $this->compileChildren($case->children, $out);
2741
-                    }
2742
-                }
2743
-                break;
2744
-
2745
-            case Type::T_EACH:
2746
-                list(, $each) = $child;
2747
-
2748
-                $list = $this->coerceList($this->reduce($each->list), ',', true);
2749
-
2750
-                $this->pushEnv();
2751
-
2752
-                foreach ($list[2] as $item) {
2753
-                    if (\count($each->vars) === 1) {
2754
-                        $this->set($each->vars[0], $item, true);
2755
-                    } else {
2756
-                        list(,, $values) = $this->coerceList($item);
2757
-
2758
-                        foreach ($each->vars as $i => $var) {
2759
-                            $this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true);
2760
-                        }
2761
-                    }
2762
-
2763
-                    $ret = $this->compileChildren($each->children, $out);
2764
-
2765
-                    if ($ret) {
2766
-                        if ($ret[0] !== Type::T_CONTROL) {
2767
-                            $store = $this->env->store;
2768
-                            $this->popEnv();
2769
-                            $this->backPropagateEnv($store, $each->vars);
2770
-
2771
-                            return $ret;
2772
-                        }
2773
-
2774
-                        if ($ret[1]) {
2775
-                            break;
2776
-                        }
2777
-                    }
2778
-                }
2779
-                $store = $this->env->store;
2780
-                $this->popEnv();
2781
-                $this->backPropagateEnv($store, $each->vars);
2782
-
2783
-                break;
2784
-
2785
-            case Type::T_WHILE:
2786
-                list(, $while) = $child;
2787
-
2788
-                while ($this->isTruthy($this->reduce($while->cond, true))) {
2789
-                    $ret = $this->compileChildren($while->children, $out);
2790
-
2791
-                    if ($ret) {
2792
-                        if ($ret[0] !== Type::T_CONTROL) {
2793
-                            return $ret;
2794
-                        }
62
+	const LINE_COMMENTS = 1;
63
+	const DEBUG_INFO    = 2;
64
+
65
+	const WITH_RULE     = 1;
66
+	const WITH_MEDIA    = 2;
67
+	const WITH_SUPPORTS = 4;
68
+	const WITH_ALL      = 7;
69
+
70
+	const SOURCE_MAP_NONE   = 0;
71
+	const SOURCE_MAP_INLINE = 1;
72
+	const SOURCE_MAP_FILE   = 2;
73
+
74
+	/**
75
+	 * @var array
76
+	 */
77
+	protected static $operatorNames = [
78
+		'+'   => 'add',
79
+		'-'   => 'sub',
80
+		'*'   => 'mul',
81
+		'/'   => 'div',
82
+		'%'   => 'mod',
83
+
84
+		'=='  => 'eq',
85
+		'!='  => 'neq',
86
+		'<'   => 'lt',
87
+		'>'   => 'gt',
88
+
89
+		'<='  => 'lte',
90
+		'>='  => 'gte',
91
+		'<=>' => 'cmp',
92
+	];
93
+
94
+	/**
95
+	 * @var array
96
+	 */
97
+	protected static $namespaces = [
98
+		'special'  => '%',
99
+		'mixin'    => '@',
100
+		'function' => '^',
101
+	];
102
+
103
+	public static $true         = [Type::T_KEYWORD, 'true'];
104
+	public static $false        = [Type::T_KEYWORD, 'false'];
105
+	public static $NaN          = [Type::T_KEYWORD, 'NaN'];
106
+	public static $Infinity     = [Type::T_KEYWORD, 'Infinity'];
107
+	public static $null         = [Type::T_NULL];
108
+	public static $nullString   = [Type::T_STRING, '', []];
109
+	public static $defaultValue = [Type::T_KEYWORD, ''];
110
+	public static $selfSelector = [Type::T_SELF];
111
+	public static $emptyList    = [Type::T_LIST, '', []];
112
+	public static $emptyMap     = [Type::T_MAP, [], []];
113
+	public static $emptyString  = [Type::T_STRING, '"', []];
114
+	public static $with         = [Type::T_KEYWORD, 'with'];
115
+	public static $without      = [Type::T_KEYWORD, 'without'];
116
+
117
+	protected $importPaths = [''];
118
+	protected $importCache = [];
119
+	protected $importedFiles = [];
120
+	protected $userFunctions = [];
121
+	protected $registeredVars = [];
122
+	protected $registeredFeatures = [
123
+		'extend-selector-pseudoclass' => false,
124
+		'at-error'                    => true,
125
+		'units-level-3'               => false,
126
+		'global-variable-shadowing'   => false,
127
+	];
128
+
129
+	protected $encoding = null;
130
+	protected $lineNumberStyle = null;
131
+
132
+	protected $sourceMap = self::SOURCE_MAP_NONE;
133
+	protected $sourceMapOptions = [];
134
+
135
+	/**
136
+	 * @var string|\ScssPhp\ScssPhp\Formatter
137
+	 */
138
+	protected $formatter = 'ScssPhp\ScssPhp\Formatter\Nested';
139
+
140
+	protected $rootEnv;
141
+	protected $rootBlock;
142
+
143
+	/**
144
+	 * @var \ScssPhp\ScssPhp\Compiler\Environment
145
+	 */
146
+	protected $env;
147
+	protected $scope;
148
+	protected $storeEnv;
149
+	protected $charsetSeen;
150
+	protected $sourceNames;
151
+
152
+	protected $cache;
153
+
154
+	protected $indentLevel;
155
+	protected $extends;
156
+	protected $extendsMap;
157
+	protected $parsedFiles;
158
+	protected $parser;
159
+	protected $sourceIndex;
160
+	protected $sourceLine;
161
+	protected $sourceColumn;
162
+	protected $stderr;
163
+	protected $shouldEvaluate;
164
+	protected $ignoreErrors;
165
+	protected $ignoreCallStackMessage = false;
166
+
167
+	protected $callStack = [];
168
+
169
+	/**
170
+	 * Constructor
171
+	 *
172
+	 * @param array|null $cacheOptions
173
+	 */
174
+	public function __construct($cacheOptions = null)
175
+	{
176
+		$this->parsedFiles = [];
177
+		$this->sourceNames = [];
178
+
179
+		if ($cacheOptions) {
180
+			$this->cache = new Cache($cacheOptions);
181
+		}
182
+
183
+		$this->stderr = fopen('php://stderr', 'w');
184
+	}
185
+
186
+	/**
187
+	 * Get compiler options
188
+	 *
189
+	 * @return array
190
+	 */
191
+	public function getCompileOptions()
192
+	{
193
+		$options = [
194
+			'importPaths'        => $this->importPaths,
195
+			'registeredVars'     => $this->registeredVars,
196
+			'registeredFeatures' => $this->registeredFeatures,
197
+			'encoding'           => $this->encoding,
198
+			'sourceMap'          => serialize($this->sourceMap),
199
+			'sourceMapOptions'   => $this->sourceMapOptions,
200
+			'formatter'          => $this->formatter,
201
+		];
202
+
203
+		return $options;
204
+	}
205
+
206
+	/**
207
+	 * Set an alternative error output stream, for testing purpose only
208
+	 *
209
+	 * @param resource $handle
210
+	 */
211
+	public function setErrorOuput($handle)
212
+	{
213
+		$this->stderr = $handle;
214
+	}
215
+
216
+	/**
217
+	 * Compile scss
218
+	 *
219
+	 * @api
220
+	 *
221
+	 * @param string $code
222
+	 * @param string $path
223
+	 *
224
+	 * @return string
225
+	 */
226
+	public function compile($code, $path = null)
227
+	{
228
+		if ($this->cache) {
229
+			$cacheKey       = ($path ? $path : '(stdin)') . ':' . md5($code);
230
+			$compileOptions = $this->getCompileOptions();
231
+			$cache          = $this->cache->getCache('compile', $cacheKey, $compileOptions);
232
+
233
+			if (\is_array($cache) && isset($cache['dependencies']) && isset($cache['out'])) {
234
+				// check if any dependency file changed before accepting the cache
235
+				foreach ($cache['dependencies'] as $file => $mtime) {
236
+					if (! is_file($file) || filemtime($file) !== $mtime) {
237
+						unset($cache);
238
+						break;
239
+					}
240
+				}
241
+
242
+				if (isset($cache)) {
243
+					return $cache['out'];
244
+				}
245
+			}
246
+		}
247
+
248
+
249
+		$this->indentLevel    = -1;
250
+		$this->extends        = [];
251
+		$this->extendsMap     = [];
252
+		$this->sourceIndex    = null;
253
+		$this->sourceLine     = null;
254
+		$this->sourceColumn   = null;
255
+		$this->env            = null;
256
+		$this->scope          = null;
257
+		$this->storeEnv       = null;
258
+		$this->charsetSeen    = null;
259
+		$this->shouldEvaluate = null;
260
+		$this->ignoreCallStackMessage = false;
261
+
262
+		$this->parser = $this->parserFactory($path);
263
+		$tree         = $this->parser->parse($code);
264
+		$this->parser = null;
265
+
266
+		$this->formatter = new $this->formatter();
267
+		$this->rootBlock = null;
268
+		$this->rootEnv   = $this->pushEnv($tree);
269
+
270
+		$this->injectVariables($this->registeredVars);
271
+		$this->compileRoot($tree);
272
+		$this->popEnv();
273
+
274
+		$sourceMapGenerator = null;
275
+
276
+		if ($this->sourceMap) {
277
+			if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
278
+				$sourceMapGenerator = $this->sourceMap;
279
+				$this->sourceMap = self::SOURCE_MAP_FILE;
280
+			} elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
281
+				$sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
282
+			}
283
+		}
284
+
285
+		$out = $this->formatter->format($this->scope, $sourceMapGenerator);
286
+
287
+		if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
288
+			$sourceMap    = $sourceMapGenerator->generateJson();
289
+			$sourceMapUrl = null;
290
+
291
+			switch ($this->sourceMap) {
292
+				case self::SOURCE_MAP_INLINE:
293
+					$sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
294
+					break;
295
+
296
+				case self::SOURCE_MAP_FILE:
297
+					$sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
298
+					break;
299
+			}
300
+
301
+			$out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
302
+		}
303
+
304
+		if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
305
+			$v = [
306
+				'dependencies' => $this->getParsedFiles(),
307
+				'out' => &$out,
308
+			];
309
+
310
+			$this->cache->setCache('compile', $cacheKey, $v, $compileOptions);
311
+		}
312
+
313
+		return $out;
314
+	}
315
+
316
+	/**
317
+	 * Instantiate parser
318
+	 *
319
+	 * @param string $path
320
+	 *
321
+	 * @return \ScssPhp\ScssPhp\Parser
322
+	 */
323
+	protected function parserFactory($path)
324
+	{
325
+		// https://sass-lang.com/documentation/at-rules/import
326
+		// CSS files imported by Sass don’t allow any special Sass features.
327
+		// In order to make sure authors don’t accidentally write Sass in their CSS,
328
+		// all Sass features that aren’t also valid CSS will produce errors.
329
+		// Otherwise, the CSS will be rendered as-is. It can even be extended!
330
+		$cssOnly = false;
331
+
332
+		if (substr($path, '-4') === '.css') {
333
+			$cssOnly = true;
334
+		}
335
+
336
+		$parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly);
337
+
338
+		$this->sourceNames[] = $path;
339
+		$this->addParsedFile($path);
340
+
341
+		return $parser;
342
+	}
343
+
344
+	/**
345
+	 * Is self extend?
346
+	 *
347
+	 * @param array $target
348
+	 * @param array $origin
349
+	 *
350
+	 * @return boolean
351
+	 */
352
+	protected function isSelfExtend($target, $origin)
353
+	{
354
+		foreach ($origin as $sel) {
355
+			if (\in_array($target, $sel)) {
356
+				return true;
357
+			}
358
+		}
359
+
360
+		return false;
361
+	}
362
+
363
+	/**
364
+	 * Push extends
365
+	 *
366
+	 * @param array      $target
367
+	 * @param array      $origin
368
+	 * @param array|null $block
369
+	 */
370
+	protected function pushExtends($target, $origin, $block)
371
+	{
372
+		$i = \count($this->extends);
373
+		$this->extends[] = [$target, $origin, $block];
374
+
375
+		foreach ($target as $part) {
376
+			if (isset($this->extendsMap[$part])) {
377
+				$this->extendsMap[$part][] = $i;
378
+			} else {
379
+				$this->extendsMap[$part] = [$i];
380
+			}
381
+		}
382
+	}
383
+
384
+	/**
385
+	 * Make output block
386
+	 *
387
+	 * @param string $type
388
+	 * @param array  $selectors
389
+	 *
390
+	 * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
391
+	 */
392
+	protected function makeOutputBlock($type, $selectors = null)
393
+	{
394
+		$out = new OutputBlock();
395
+		$out->type      = $type;
396
+		$out->lines     = [];
397
+		$out->children  = [];
398
+		$out->parent    = $this->scope;
399
+		$out->selectors = $selectors;
400
+		$out->depth     = $this->env->depth;
401
+
402
+		if ($this->env->block instanceof Block) {
403
+			$out->sourceName   = $this->env->block->sourceName;
404
+			$out->sourceLine   = $this->env->block->sourceLine;
405
+			$out->sourceColumn = $this->env->block->sourceColumn;
406
+		} else {
407
+			$out->sourceName   = null;
408
+			$out->sourceLine   = null;
409
+			$out->sourceColumn = null;
410
+		}
411
+
412
+		return $out;
413
+	}
414
+
415
+	/**
416
+	 * Compile root
417
+	 *
418
+	 * @param \ScssPhp\ScssPhp\Block $rootBlock
419
+	 */
420
+	protected function compileRoot(Block $rootBlock)
421
+	{
422
+		$this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
423
+
424
+		$this->compileChildrenNoReturn($rootBlock->children, $this->scope);
425
+		$this->flattenSelectors($this->scope);
426
+		$this->missingSelectors();
427
+	}
428
+
429
+	/**
430
+	 * Report missing selectors
431
+	 */
432
+	protected function missingSelectors()
433
+	{
434
+		foreach ($this->extends as $extend) {
435
+			if (isset($extend[3])) {
436
+				continue;
437
+			}
438
+
439
+			list($target, $origin, $block) = $extend;
440
+
441
+			// ignore if !optional
442
+			if ($block[2]) {
443
+				continue;
444
+			}
445
+
446
+			$target = implode(' ', $target);
447
+			$origin = $this->collapseSelectors($origin);
448
+
449
+			$this->sourceLine = $block[Parser::SOURCE_LINE];
450
+			throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
451
+		}
452
+	}
453
+
454
+	/**
455
+	 * Flatten selectors
456
+	 *
457
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
458
+	 * @param string                                 $parentKey
459
+	 */
460
+	protected function flattenSelectors(OutputBlock $block, $parentKey = null)
461
+	{
462
+		if ($block->selectors) {
463
+			$selectors = [];
464
+
465
+			foreach ($block->selectors as $s) {
466
+				$selectors[] = $s;
467
+
468
+				if (! \is_array($s)) {
469
+					continue;
470
+				}
471
+
472
+				// check extends
473
+				if (! empty($this->extendsMap)) {
474
+					$this->matchExtends($s, $selectors);
475
+
476
+					// remove duplicates
477
+					array_walk($selectors, function (&$value) {
478
+						$value = serialize($value);
479
+					});
480
+
481
+					$selectors = array_unique($selectors);
482
+
483
+					array_walk($selectors, function (&$value) {
484
+						$value = unserialize($value);
485
+					});
486
+				}
487
+			}
488
+
489
+			$block->selectors = [];
490
+			$placeholderSelector = false;
491
+
492
+			foreach ($selectors as $selector) {
493
+				if ($this->hasSelectorPlaceholder($selector)) {
494
+					$placeholderSelector = true;
495
+					continue;
496
+				}
497
+
498
+				$block->selectors[] = $this->compileSelector($selector);
499
+			}
500
+
501
+			if ($placeholderSelector && 0 === \count($block->selectors) && null !== $parentKey) {
502
+				unset($block->parent->children[$parentKey]);
503
+
504
+				return;
505
+			}
506
+		}
507
+
508
+		foreach ($block->children as $key => $child) {
509
+			$this->flattenSelectors($child, $key);
510
+		}
511
+	}
512
+
513
+	/**
514
+	 * Glue parts of :not( or :nth-child( ... that are in general splitted in selectors parts
515
+	 *
516
+	 * @param array $parts
517
+	 *
518
+	 * @return array
519
+	 */
520
+	protected function glueFunctionSelectors($parts)
521
+	{
522
+		$new = [];
523
+
524
+		foreach ($parts as $part) {
525
+			if (\is_array($part)) {
526
+				$part = $this->glueFunctionSelectors($part);
527
+				$new[] = $part;
528
+			} else {
529
+				// a selector part finishing with a ) is the last part of a :not( or :nth-child(
530
+				// and need to be joined to this
531
+				if (
532
+					\count($new) && \is_string($new[\count($new) - 1]) &&
533
+					\strlen($part) && substr($part, -1) === ')' && strpos($part, '(') === false
534
+				) {
535
+					while (\count($new) > 1 && substr($new[\count($new) - 1], -1) !== '(') {
536
+						$part = array_pop($new) . $part;
537
+					}
538
+					$new[\count($new) - 1] .= $part;
539
+				} else {
540
+					$new[] = $part;
541
+				}
542
+			}
543
+		}
544
+
545
+		return $new;
546
+	}
547
+
548
+	/**
549
+	 * Match extends
550
+	 *
551
+	 * @param array   $selector
552
+	 * @param array   $out
553
+	 * @param integer $from
554
+	 * @param boolean $initial
555
+	 */
556
+	protected function matchExtends($selector, &$out, $from = 0, $initial = true)
557
+	{
558
+		static $partsPile = [];
559
+		$selector = $this->glueFunctionSelectors($selector);
560
+
561
+		if (\count($selector) == 1 && \in_array(reset($selector), $partsPile)) {
562
+			return;
563
+		}
564
+
565
+		$outRecurs = [];
566
+
567
+		foreach ($selector as $i => $part) {
568
+			if ($i < $from) {
569
+				continue;
570
+			}
571
+
572
+			// check that we are not building an infinite loop of extensions
573
+			// if the new part is just including a previous part don't try to extend anymore
574
+			if (\count($part) > 1) {
575
+				foreach ($partsPile as $previousPart) {
576
+					if (! \count(array_diff($previousPart, $part))) {
577
+						continue 2;
578
+					}
579
+				}
580
+			}
581
+
582
+			$partsPile[] = $part;
583
+
584
+			if ($this->matchExtendsSingle($part, $origin, $initial)) {
585
+				$after       = \array_slice($selector, $i + 1);
586
+				$before      = \array_slice($selector, 0, $i);
587
+				list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
588
+
589
+				foreach ($origin as $new) {
590
+					$k = 0;
591
+
592
+					// remove shared parts
593
+					if (\count($new) > 1) {
594
+						while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
595
+							$k++;
596
+						}
597
+					}
598
+
599
+					if (\count($nonBreakableBefore) && $k === \count($new)) {
600
+						$k--;
601
+					}
602
+
603
+					$replacement = [];
604
+					$tempReplacement = $k > 0 ? \array_slice($new, $k) : $new;
605
+
606
+					for ($l = \count($tempReplacement) - 1; $l >= 0; $l--) {
607
+						$slice = [];
608
+
609
+						foreach ($tempReplacement[$l] as $chunk) {
610
+							if (! \in_array($chunk, $slice)) {
611
+								$slice[] = $chunk;
612
+							}
613
+						}
614
+
615
+						array_unshift($replacement, $slice);
616
+
617
+						if (! $this->isImmediateRelationshipCombinator(end($slice))) {
618
+							break;
619
+						}
620
+					}
621
+
622
+					$afterBefore = $l != 0 ? \array_slice($tempReplacement, 0, $l) : [];
623
+
624
+					// Merge shared direct relationships.
625
+					$mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore);
626
+
627
+					$result = array_merge(
628
+						$before,
629
+						$mergedBefore,
630
+						$replacement,
631
+						$after
632
+					);
633
+
634
+					if ($result === $selector) {
635
+						continue;
636
+					}
637
+
638
+					$this->pushOrMergeExtentedSelector($out, $result);
639
+
640
+					// recursively check for more matches
641
+					$startRecurseFrom = \count($before) + min(\count($nonBreakableBefore), \count($mergedBefore));
642
+
643
+					if (\count($origin) > 1) {
644
+						$this->matchExtends($result, $out, $startRecurseFrom, false);
645
+					} else {
646
+						$this->matchExtends($result, $outRecurs, $startRecurseFrom, false);
647
+					}
648
+
649
+					// selector sequence merging
650
+					if (! empty($before) && \count($new) > 1) {
651
+						$preSharedParts = $k > 0 ? \array_slice($before, 0, $k) : [];
652
+						$postSharedParts = $k > 0 ? \array_slice($before, $k) : $before;
653
+
654
+						list($betweenSharedParts, $nonBreakabl2) = $this->extractRelationshipFromFragment($afterBefore);
655
+
656
+						$result2 = array_merge(
657
+							$preSharedParts,
658
+							$betweenSharedParts,
659
+							$postSharedParts,
660
+							$nonBreakabl2,
661
+							$nonBreakableBefore,
662
+							$replacement,
663
+							$after
664
+						);
665
+
666
+						$this->pushOrMergeExtentedSelector($out, $result2);
667
+					}
668
+				}
669
+			}
670
+			array_pop($partsPile);
671
+		}
672
+
673
+		while (\count($outRecurs)) {
674
+			$result = array_shift($outRecurs);
675
+			$this->pushOrMergeExtentedSelector($out, $result);
676
+		}
677
+	}
678
+
679
+	/**
680
+	 * Test a part for being a pseudo selector
681
+	 *
682
+	 * @param string $part
683
+	 * @param array  $matches
684
+	 *
685
+	 * @return boolean
686
+	 */
687
+	protected function isPseudoSelector($part, &$matches)
688
+	{
689
+		if (
690
+			strpos($part, ':') === 0 &&
691
+			preg_match(",^::?([\w-]+)\((.+)\)$,", $part, $matches)
692
+		) {
693
+			return true;
694
+		}
695
+
696
+		return false;
697
+	}
698
+
699
+	/**
700
+	 * Push extended selector except if
701
+	 *  - this is a pseudo selector
702
+	 *  - same as previous
703
+	 *  - in a white list
704
+	 * in this case we merge the pseudo selector content
705
+	 *
706
+	 * @param array $out
707
+	 * @param array $extended
708
+	 */
709
+	protected function pushOrMergeExtentedSelector(&$out, $extended)
710
+	{
711
+		if (\count($out) && \count($extended) === 1 && \count(reset($extended)) === 1) {
712
+			$single = reset($extended);
713
+			$part = reset($single);
714
+
715
+			if (
716
+				$this->isPseudoSelector($part, $matchesExtended) &&
717
+				\in_array($matchesExtended[1], [ 'slotted' ])
718
+			) {
719
+				$prev = end($out);
720
+				$prev = $this->glueFunctionSelectors($prev);
721
+
722
+				if (\count($prev) === 1 && \count(reset($prev)) === 1) {
723
+					$single = reset($prev);
724
+					$part = reset($single);
725
+
726
+					if (
727
+						$this->isPseudoSelector($part, $matchesPrev) &&
728
+						$matchesPrev[1] === $matchesExtended[1]
729
+					) {
730
+						$extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
731
+						$extended[1] = $matchesPrev[2] . ', ' . $extended[1];
732
+						$extended = implode($matchesExtended[1] . '(', $extended);
733
+						$extended = [ [ $extended ]];
734
+						array_pop($out);
735
+					}
736
+				}
737
+			}
738
+		}
739
+		$out[] = $extended;
740
+	}
741
+
742
+	/**
743
+	 * Match extends single
744
+	 *
745
+	 * @param array   $rawSingle
746
+	 * @param array   $outOrigin
747
+	 * @param boolean $initial
748
+	 *
749
+	 * @return boolean
750
+	 */
751
+	protected function matchExtendsSingle($rawSingle, &$outOrigin, $initial = true)
752
+	{
753
+		$counts = [];
754
+		$single = [];
755
+
756
+		// simple usual cases, no need to do the whole trick
757
+		if (\in_array($rawSingle, [['>'],['+'],['~']])) {
758
+			return false;
759
+		}
760
+
761
+		foreach ($rawSingle as $part) {
762
+			// matches Number
763
+			if (! \is_string($part)) {
764
+				return false;
765
+			}
766
+
767
+			if (! preg_match('/^[\[.:#%]/', $part) && \count($single)) {
768
+				$single[\count($single) - 1] .= $part;
769
+			} else {
770
+				$single[] = $part;
771
+			}
772
+		}
773
+
774
+		$extendingDecoratedTag = false;
775
+
776
+		if (\count($single) > 1) {
777
+			$matches = null;
778
+			$extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false;
779
+		}
780
+
781
+		$outOrigin = [];
782
+		$found = false;
783
+
784
+		foreach ($single as $k => $part) {
785
+			if (isset($this->extendsMap[$part])) {
786
+				foreach ($this->extendsMap[$part] as $idx) {
787
+					$counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
788
+				}
789
+			}
790
+
791
+			if (
792
+				$initial &&
793
+				$this->isPseudoSelector($part, $matches) &&
794
+				! \in_array($matches[1], [ 'not' ])
795
+			) {
796
+				$buffer    = $matches[2];
797
+				$parser    = $this->parserFactory(__METHOD__);
798
+
799
+				if ($parser->parseSelector($buffer, $subSelectors)) {
800
+					foreach ($subSelectors as $ksub => $subSelector) {
801
+						$subExtended = [];
802
+						$this->matchExtends($subSelector, $subExtended, 0, false);
803
+
804
+						if ($subExtended) {
805
+							$subSelectorsExtended = $subSelectors;
806
+							$subSelectorsExtended[$ksub] = $subExtended;
807
+
808
+							foreach ($subSelectorsExtended as $ksse => $sse) {
809
+								$subSelectorsExtended[$ksse] = $this->collapseSelectors($sse);
810
+							}
811
+
812
+							$subSelectorsExtended = implode(', ', $subSelectorsExtended);
813
+							$singleExtended = $single;
814
+							$singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part);
815
+							$outOrigin[] = [ $singleExtended ];
816
+							$found = true;
817
+						}
818
+					}
819
+				}
820
+			}
821
+		}
822
+
823
+		foreach ($counts as $idx => $count) {
824
+			list($target, $origin, /* $block */) = $this->extends[$idx];
825
+
826
+			$origin = $this->glueFunctionSelectors($origin);
827
+
828
+			// check count
829
+			if ($count !== \count($target)) {
830
+				continue;
831
+			}
832
+
833
+			$this->extends[$idx][3] = true;
834
+
835
+			$rem = array_diff($single, $target);
836
+
837
+			foreach ($origin as $j => $new) {
838
+				// prevent infinite loop when target extends itself
839
+				if ($this->isSelfExtend($single, $origin) && ! $initial) {
840
+					return false;
841
+				}
842
+
843
+				$replacement = end($new);
844
+
845
+				// Extending a decorated tag with another tag is not possible.
846
+				if (
847
+					$extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
848
+					preg_match('/^[a-z0-9]+$/i', $replacement[0])
849
+				) {
850
+					unset($origin[$j]);
851
+					continue;
852
+				}
853
+
854
+				$combined = $this->combineSelectorSingle($replacement, $rem);
855
+
856
+				if (\count(array_diff($combined, $origin[$j][\count($origin[$j]) - 1]))) {
857
+					$origin[$j][\count($origin[$j]) - 1] = $combined;
858
+				}
859
+			}
860
+
861
+			$outOrigin = array_merge($outOrigin, $origin);
862
+
863
+			$found = true;
864
+		}
865
+
866
+		return $found;
867
+	}
868
+
869
+	/**
870
+	 * Extract a relationship from the fragment.
871
+	 *
872
+	 * When extracting the last portion of a selector we will be left with a
873
+	 * fragment which may end with a direction relationship combinator. This
874
+	 * method will extract the relationship fragment and return it along side
875
+	 * the rest.
876
+	 *
877
+	 * @param array $fragment The selector fragment maybe ending with a direction relationship combinator.
878
+	 *
879
+	 * @return array The selector without the relationship fragment if any, the relationship fragment.
880
+	 */
881
+	protected function extractRelationshipFromFragment(array $fragment)
882
+	{
883
+		$parents = [];
884
+		$children = [];
885
+
886
+		$j = $i = \count($fragment);
887
+
888
+		for (;;) {
889
+			$children = $j != $i ? \array_slice($fragment, $j, $i - $j) : [];
890
+			$parents  = \array_slice($fragment, 0, $j);
891
+			$slice    = end($parents);
892
+
893
+			if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
894
+				break;
895
+			}
896
+
897
+			$j -= 2;
898
+		}
899
+
900
+		return [$parents, $children];
901
+	}
902
+
903
+	/**
904
+	 * Combine selector single
905
+	 *
906
+	 * @param array $base
907
+	 * @param array $other
908
+	 *
909
+	 * @return array
910
+	 */
911
+	protected function combineSelectorSingle($base, $other)
912
+	{
913
+		$tag    = [];
914
+		$out    = [];
915
+		$wasTag = false;
916
+		$pseudo = [];
917
+
918
+		while (\count($other) && strpos(end($other), ':') === 0) {
919
+			array_unshift($pseudo, array_pop($other));
920
+		}
921
+
922
+		foreach ([array_reverse($base), array_reverse($other)] as $single) {
923
+			$rang = count($single);
924
+
925
+			foreach ($single as $part) {
926
+				if (preg_match('/^[\[:]/', $part)) {
927
+					$out[] = $part;
928
+					$wasTag = false;
929
+				} elseif (preg_match('/^[\.#]/', $part)) {
930
+					array_unshift($out, $part);
931
+					$wasTag = false;
932
+				} elseif (preg_match('/^[^_-]/', $part) && $rang === 1) {
933
+					$tag[] = $part;
934
+					$wasTag = true;
935
+				} elseif ($wasTag) {
936
+					$tag[\count($tag) - 1] .= $part;
937
+				} else {
938
+					array_unshift($out, $part);
939
+				}
940
+				$rang--;
941
+			}
942
+		}
943
+
944
+		if (\count($tag)) {
945
+			array_unshift($out, $tag[0]);
946
+		}
947
+
948
+		while (\count($pseudo)) {
949
+			$out[] = array_shift($pseudo);
950
+		}
951
+
952
+		return $out;
953
+	}
954
+
955
+	/**
956
+	 * Compile media
957
+	 *
958
+	 * @param \ScssPhp\ScssPhp\Block $media
959
+	 */
960
+	protected function compileMedia(Block $media)
961
+	{
962
+		$this->pushEnv($media);
963
+
964
+		$mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
965
+
966
+		if (! empty($mediaQueries) && $mediaQueries) {
967
+			$previousScope = $this->scope;
968
+			$parentScope = $this->mediaParent($this->scope);
969
+
970
+			foreach ($mediaQueries as $mediaQuery) {
971
+				$this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
972
+
973
+				$parentScope->children[] = $this->scope;
974
+				$parentScope = $this->scope;
975
+			}
976
+
977
+			// top level properties in a media cause it to be wrapped
978
+			$needsWrap = false;
979
+
980
+			foreach ($media->children as $child) {
981
+				$type = $child[0];
982
+
983
+				if (
984
+					$type !== Type::T_BLOCK &&
985
+					$type !== Type::T_MEDIA &&
986
+					$type !== Type::T_DIRECTIVE &&
987
+					$type !== Type::T_IMPORT
988
+				) {
989
+					$needsWrap = true;
990
+					break;
991
+				}
992
+			}
993
+
994
+			if ($needsWrap) {
995
+				$wrapped = new Block();
996
+				$wrapped->sourceName   = $media->sourceName;
997
+				$wrapped->sourceIndex  = $media->sourceIndex;
998
+				$wrapped->sourceLine   = $media->sourceLine;
999
+				$wrapped->sourceColumn = $media->sourceColumn;
1000
+				$wrapped->selectors    = [];
1001
+				$wrapped->comments     = [];
1002
+				$wrapped->parent       = $media;
1003
+				$wrapped->children     = $media->children;
1004
+
1005
+				$media->children = [[Type::T_BLOCK, $wrapped]];
1006
+
1007
+				if (isset($this->lineNumberStyle)) {
1008
+					$annotation = $this->makeOutputBlock(Type::T_COMMENT);
1009
+					$annotation->depth = 0;
1010
+
1011
+					$file = $this->sourceNames[$media->sourceIndex];
1012
+					$line = $media->sourceLine;
1013
+
1014
+					switch ($this->lineNumberStyle) {
1015
+						case static::LINE_COMMENTS:
1016
+							$annotation->lines[] = '/* line ' . $line
1017
+												 . ($file ? ', ' . $file : '')
1018
+												 . ' */';
1019
+							break;
1020
+
1021
+						case static::DEBUG_INFO:
1022
+							$annotation->lines[] = '@media -sass-debug-info{'
1023
+												 . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1024
+												 . 'line{font-family:' . $line . '}}';
1025
+							break;
1026
+					}
1027
+
1028
+					$this->scope->children[] = $annotation;
1029
+				}
1030
+			}
1031
+
1032
+			$this->compileChildrenNoReturn($media->children, $this->scope);
1033
+
1034
+			$this->scope = $previousScope;
1035
+		}
1036
+
1037
+		$this->popEnv();
1038
+	}
1039
+
1040
+	/**
1041
+	 * Media parent
1042
+	 *
1043
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1044
+	 *
1045
+	 * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
1046
+	 */
1047
+	protected function mediaParent(OutputBlock $scope)
1048
+	{
1049
+		while (! empty($scope->parent)) {
1050
+			if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
1051
+				break;
1052
+			}
1053
+
1054
+			$scope = $scope->parent;
1055
+		}
1056
+
1057
+		return $scope;
1058
+	}
1059
+
1060
+	/**
1061
+	 * Compile directive
1062
+	 *
1063
+	 * @param \ScssPhp\ScssPhp\Block|array $block
1064
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1065
+	 */
1066
+	protected function compileDirective($directive, OutputBlock $out)
1067
+	{
1068
+		if (\is_array($directive)) {
1069
+			$s = '@' . $directive[0];
1070
+
1071
+			if (! empty($directive[1])) {
1072
+				$s .= ' ' . $this->compileValue($directive[1]);
1073
+			}
1074
+
1075
+			$this->appendRootDirective($s . ';', $out);
1076
+		} else {
1077
+			$s = '@' . $directive->name;
1078
+
1079
+			if (! empty($directive->value)) {
1080
+				$s .= ' ' . $this->compileValue($directive->value);
1081
+			}
1082
+
1083
+			if ($directive->name === 'keyframes' || substr($directive->name, -10) === '-keyframes') {
1084
+				$this->compileKeyframeBlock($directive, [$s]);
1085
+			} else {
1086
+				$this->compileNestedBlock($directive, [$s]);
1087
+			}
1088
+		}
1089
+	}
1090
+
1091
+	/**
1092
+	 * Compile at-root
1093
+	 *
1094
+	 * @param \ScssPhp\ScssPhp\Block $block
1095
+	 */
1096
+	protected function compileAtRoot(Block $block)
1097
+	{
1098
+		$env     = $this->pushEnv($block);
1099
+		$envs    = $this->compactEnv($env);
1100
+		list($with, $without) = $this->compileWith(isset($block->with) ? $block->with : null);
1101
+
1102
+		// wrap inline selector
1103
+		if ($block->selector) {
1104
+			$wrapped = new Block();
1105
+			$wrapped->sourceName   = $block->sourceName;
1106
+			$wrapped->sourceIndex  = $block->sourceIndex;
1107
+			$wrapped->sourceLine   = $block->sourceLine;
1108
+			$wrapped->sourceColumn = $block->sourceColumn;
1109
+			$wrapped->selectors    = $block->selector;
1110
+			$wrapped->comments     = [];
1111
+			$wrapped->parent       = $block;
1112
+			$wrapped->children     = $block->children;
1113
+			$wrapped->selfParent   = $block->selfParent;
1114
+
1115
+			$block->children = [[Type::T_BLOCK, $wrapped]];
1116
+			$block->selector = null;
1117
+		}
1118
+
1119
+		$selfParent = $block->selfParent;
1120
+
1121
+		if (
1122
+			! $block->selfParent->selectors &&
1123
+			isset($block->parent) && $block->parent &&
1124
+			isset($block->parent->selectors) && $block->parent->selectors
1125
+		) {
1126
+			$selfParent = $block->parent;
1127
+		}
1128
+
1129
+		$this->env = $this->filterWithWithout($envs, $with, $without);
1130
+
1131
+		$saveScope   = $this->scope;
1132
+		$this->scope = $this->filterScopeWithWithout($saveScope, $with, $without);
1133
+
1134
+		// propagate selfParent to the children where they still can be useful
1135
+		$this->compileChildrenNoReturn($block->children, $this->scope, $selfParent);
1136
+
1137
+		$this->scope = $this->completeScope($this->scope, $saveScope);
1138
+		$this->scope = $saveScope;
1139
+		$this->env   = $this->extractEnv($envs);
1140
+
1141
+		$this->popEnv();
1142
+	}
1143
+
1144
+	/**
1145
+	 * Filter at-root scope depending of with/without option
1146
+	 *
1147
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1148
+	 * @param array                                  $with
1149
+	 * @param array                                  $without
1150
+	 *
1151
+	 * @return mixed
1152
+	 */
1153
+	protected function filterScopeWithWithout($scope, $with, $without)
1154
+	{
1155
+		$filteredScopes = [];
1156
+		$childStash = [];
1157
+
1158
+		if ($scope->type === TYPE::T_ROOT) {
1159
+			return $scope;
1160
+		}
1161
+
1162
+		// start from the root
1163
+		while ($scope->parent && $scope->parent->type !== TYPE::T_ROOT) {
1164
+			array_unshift($childStash, $scope);
1165
+			$scope = $scope->parent;
1166
+		}
1167
+
1168
+		for (;;) {
1169
+			if (! $scope) {
1170
+				break;
1171
+			}
1172
+
1173
+			if ($this->isWith($scope, $with, $without)) {
1174
+				$s = clone $scope;
1175
+				$s->children = [];
1176
+				$s->lines    = [];
1177
+				$s->parent   = null;
1178
+
1179
+				if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) {
1180
+					$s->selectors = [];
1181
+				}
1182
+
1183
+				$filteredScopes[] = $s;
1184
+			}
1185
+
1186
+			if (\count($childStash)) {
1187
+				$scope = array_shift($childStash);
1188
+			} elseif ($scope->children) {
1189
+				$scope = end($scope->children);
1190
+			} else {
1191
+				$scope = null;
1192
+			}
1193
+		}
1194
+
1195
+		if (! \count($filteredScopes)) {
1196
+			return $this->rootBlock;
1197
+		}
1198
+
1199
+		$newScope = array_shift($filteredScopes);
1200
+		$newScope->parent = $this->rootBlock;
1201
+
1202
+		$this->rootBlock->children[] = $newScope;
1203
+
1204
+		$p = &$newScope;
1205
+
1206
+		while (\count($filteredScopes)) {
1207
+			$s = array_shift($filteredScopes);
1208
+			$s->parent = $p;
1209
+			$p->children[] = $s;
1210
+			$newScope = &$p->children[0];
1211
+			$p = &$p->children[0];
1212
+		}
1213
+
1214
+		return $newScope;
1215
+	}
1216
+
1217
+	/**
1218
+	 * found missing selector from a at-root compilation in the previous scope
1219
+	 * (if at-root is just enclosing a property, the selector is in the parent tree)
1220
+	 *
1221
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1222
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
1223
+	 *
1224
+	 * @return mixed
1225
+	 */
1226
+	protected function completeScope($scope, $previousScope)
1227
+	{
1228
+		if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) {
1229
+			$scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1230
+		}
1231
+
1232
+		if ($scope->children) {
1233
+			foreach ($scope->children as $k => $c) {
1234
+				$scope->children[$k] = $this->completeScope($c, $previousScope);
1235
+			}
1236
+		}
1237
+
1238
+		return $scope;
1239
+	}
1240
+
1241
+	/**
1242
+	 * Find a selector by the depth node in the scope
1243
+	 *
1244
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
1245
+	 * @param integer                                $depth
1246
+	 *
1247
+	 * @return array
1248
+	 */
1249
+	protected function findScopeSelectors($scope, $depth)
1250
+	{
1251
+		if ($scope->depth === $depth && $scope->selectors) {
1252
+			return $scope->selectors;
1253
+		}
1254
+
1255
+		if ($scope->children) {
1256
+			foreach (array_reverse($scope->children) as $c) {
1257
+				if ($s = $this->findScopeSelectors($c, $depth)) {
1258
+					return $s;
1259
+				}
1260
+			}
1261
+		}
1262
+
1263
+		return [];
1264
+	}
1265
+
1266
+	/**
1267
+	 * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later
1268
+	 *
1269
+	 * @param array $withCondition
1270
+	 *
1271
+	 * @return array
1272
+	 */
1273
+	protected function compileWith($withCondition)
1274
+	{
1275
+		// just compile what we have in 2 lists
1276
+		$with = [];
1277
+		$without = ['rule' => true];
1278
+
1279
+		if ($withCondition) {
1280
+			if ($withCondition[0] === Type::T_INTERPOLATE) {
1281
+				$w = $this->compileValue($withCondition);
1282
+
1283
+				$buffer = "($w)";
1284
+				$parser = $this->parserFactory(__METHOD__);
1285
+
1286
+				if ($parser->parseValue($buffer, $reParsedWith)) {
1287
+					$withCondition = $reParsedWith;
1288
+				}
1289
+			}
1290
+
1291
+			if ($this->libMapHasKey([$withCondition, static::$with])) {
1292
+				$without = []; // cancel the default
1293
+				$list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
1294
+
1295
+				foreach ($list[2] as $item) {
1296
+					$keyword = $this->compileStringContent($this->coerceString($item));
1297
+
1298
+					$with[$keyword] = true;
1299
+				}
1300
+			}
1301
+
1302
+			if ($this->libMapHasKey([$withCondition, static::$without])) {
1303
+				$without = []; // cancel the default
1304
+				$list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
1305
+
1306
+				foreach ($list[2] as $item) {
1307
+					$keyword = $this->compileStringContent($this->coerceString($item));
1308
+
1309
+					$without[$keyword] = true;
1310
+				}
1311
+			}
1312
+		}
1313
+
1314
+		return [$with, $without];
1315
+	}
1316
+
1317
+	/**
1318
+	 * Filter env stack
1319
+	 *
1320
+	 * @param array $envs
1321
+	 * @param array $with
1322
+	 * @param array $without
1323
+	 *
1324
+	 * @return \ScssPhp\ScssPhp\Compiler\Environment
1325
+	 */
1326
+	protected function filterWithWithout($envs, $with, $without)
1327
+	{
1328
+		$filtered = [];
1329
+
1330
+		foreach ($envs as $e) {
1331
+			if ($e->block && ! $this->isWith($e->block, $with, $without)) {
1332
+				$ec = clone $e;
1333
+				$ec->block     = null;
1334
+				$ec->selectors = [];
1335
+
1336
+				$filtered[] = $ec;
1337
+			} else {
1338
+				$filtered[] = $e;
1339
+			}
1340
+		}
1341
+
1342
+		return $this->extractEnv($filtered);
1343
+	}
1344
+
1345
+	/**
1346
+	 * Filter WITH rules
1347
+	 *
1348
+	 * @param \ScssPhp\ScssPhp\Block|\ScssPhp\ScssPhp\Formatter\OutputBlock $block
1349
+	 * @param array                                                         $with
1350
+	 * @param array                                                         $without
1351
+	 *
1352
+	 * @return boolean
1353
+	 */
1354
+	protected function isWith($block, $with, $without)
1355
+	{
1356
+		if (isset($block->type)) {
1357
+			if ($block->type === Type::T_MEDIA) {
1358
+				return $this->testWithWithout('media', $with, $without);
1359
+			}
1360
+
1361
+			if ($block->type === Type::T_DIRECTIVE) {
1362
+				if (isset($block->name)) {
1363
+					return $this->testWithWithout($block->name, $with, $without);
1364
+				} elseif (isset($block->selectors) && preg_match(',@(\w+),ims', json_encode($block->selectors), $m)) {
1365
+					return $this->testWithWithout($m[1], $with, $without);
1366
+				} else {
1367
+					return $this->testWithWithout('???', $with, $without);
1368
+				}
1369
+			}
1370
+		} elseif (isset($block->selectors)) {
1371
+			// a selector starting with number is a keyframe rule
1372
+			if (\count($block->selectors)) {
1373
+				$s = reset($block->selectors);
1374
+
1375
+				while (\is_array($s)) {
1376
+					$s = reset($s);
1377
+				}
1378
+
1379
+				if (\is_object($s) && $s instanceof Node\Number) {
1380
+					return $this->testWithWithout('keyframes', $with, $without);
1381
+				}
1382
+			}
1383
+
1384
+			return $this->testWithWithout('rule', $with, $without);
1385
+		}
1386
+
1387
+		return true;
1388
+	}
1389
+
1390
+	/**
1391
+	 * Test a single type of block against with/without lists
1392
+	 *
1393
+	 * @param string $what
1394
+	 * @param array  $with
1395
+	 * @param array  $without
1396
+	 *
1397
+	 * @return boolean
1398
+	 *   true if the block should be kept, false to reject
1399
+	 */
1400
+	protected function testWithWithout($what, $with, $without)
1401
+	{
1402
+		// if without, reject only if in the list (or 'all' is in the list)
1403
+		if (\count($without)) {
1404
+			return (isset($without[$what]) || isset($without['all'])) ? false : true;
1405
+		}
1406
+
1407
+		// otherwise reject all what is not in the with list
1408
+		return (isset($with[$what]) || isset($with['all'])) ? true : false;
1409
+	}
1410
+
1411
+
1412
+	/**
1413
+	 * Compile keyframe block
1414
+	 *
1415
+	 * @param \ScssPhp\ScssPhp\Block $block
1416
+	 * @param array                  $selectors
1417
+	 */
1418
+	protected function compileKeyframeBlock(Block $block, $selectors)
1419
+	{
1420
+		$env = $this->pushEnv($block);
1421
+
1422
+		$envs = $this->compactEnv($env);
1423
+
1424
+		$this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
1425
+			return ! isset($e->block->selectors);
1426
+		}));
1427
+
1428
+		$this->scope = $this->makeOutputBlock($block->type, $selectors);
1429
+		$this->scope->depth = 1;
1430
+		$this->scope->parent->children[] = $this->scope;
1431
+
1432
+		$this->compileChildrenNoReturn($block->children, $this->scope);
1433
+
1434
+		$this->scope = $this->scope->parent;
1435
+		$this->env   = $this->extractEnv($envs);
1436
+
1437
+		$this->popEnv();
1438
+	}
1439
+
1440
+	/**
1441
+	 * Compile nested properties lines
1442
+	 *
1443
+	 * @param \ScssPhp\ScssPhp\Block                 $block
1444
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1445
+	 */
1446
+	protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
1447
+	{
1448
+		$prefix = $this->compileValue($block->prefix) . '-';
1449
+
1450
+		$nested = $this->makeOutputBlock($block->type);
1451
+		$nested->parent = $out;
1452
+
1453
+		if ($block->hasValue) {
1454
+			$nested->depth = $out->depth + 1;
1455
+		}
1456
+
1457
+		$out->children[] = $nested;
1458
+
1459
+		foreach ($block->children as $child) {
1460
+			switch ($child[0]) {
1461
+				case Type::T_ASSIGN:
1462
+					array_unshift($child[1][2], $prefix);
1463
+					break;
1464
+
1465
+				case Type::T_NESTED_PROPERTY:
1466
+					array_unshift($child[1]->prefix[2], $prefix);
1467
+					break;
1468
+			}
1469
+
1470
+			$this->compileChild($child, $nested);
1471
+		}
1472
+	}
1473
+
1474
+	/**
1475
+	 * Compile nested block
1476
+	 *
1477
+	 * @param \ScssPhp\ScssPhp\Block $block
1478
+	 * @param array                  $selectors
1479
+	 */
1480
+	protected function compileNestedBlock(Block $block, $selectors)
1481
+	{
1482
+		$this->pushEnv($block);
1483
+
1484
+		$this->scope = $this->makeOutputBlock($block->type, $selectors);
1485
+		$this->scope->parent->children[] = $this->scope;
1486
+
1487
+		// wrap assign children in a block
1488
+		// except for @font-face
1489
+		if ($block->type !== Type::T_DIRECTIVE || $block->name !== 'font-face') {
1490
+			// need wrapping?
1491
+			$needWrapping = false;
1492
+
1493
+			foreach ($block->children as $child) {
1494
+				if ($child[0] === Type::T_ASSIGN) {
1495
+					$needWrapping = true;
1496
+					break;
1497
+				}
1498
+			}
1499
+
1500
+			if ($needWrapping) {
1501
+				$wrapped = new Block();
1502
+				$wrapped->sourceName   = $block->sourceName;
1503
+				$wrapped->sourceIndex  = $block->sourceIndex;
1504
+				$wrapped->sourceLine   = $block->sourceLine;
1505
+				$wrapped->sourceColumn = $block->sourceColumn;
1506
+				$wrapped->selectors    = [];
1507
+				$wrapped->comments     = [];
1508
+				$wrapped->parent       = $block;
1509
+				$wrapped->children     = $block->children;
1510
+				$wrapped->selfParent   = $block->selfParent;
1511
+
1512
+				$block->children = [[Type::T_BLOCK, $wrapped]];
1513
+			}
1514
+		}
1515
+
1516
+		$this->compileChildrenNoReturn($block->children, $this->scope);
1517
+
1518
+		$this->scope = $this->scope->parent;
1519
+
1520
+		$this->popEnv();
1521
+	}
1522
+
1523
+	/**
1524
+	 * Recursively compiles a block.
1525
+	 *
1526
+	 * A block is analogous to a CSS block in most cases. A single SCSS document
1527
+	 * is encapsulated in a block when parsed, but it does not have parent tags
1528
+	 * so all of its children appear on the root level when compiled.
1529
+	 *
1530
+	 * Blocks are made up of selectors and children.
1531
+	 *
1532
+	 * The children of a block are just all the blocks that are defined within.
1533
+	 *
1534
+	 * Compiling the block involves pushing a fresh environment on the stack,
1535
+	 * and iterating through the props, compiling each one.
1536
+	 *
1537
+	 * @see Compiler::compileChild()
1538
+	 *
1539
+	 * @param \ScssPhp\ScssPhp\Block $block
1540
+	 */
1541
+	protected function compileBlock(Block $block)
1542
+	{
1543
+		$env = $this->pushEnv($block);
1544
+		$env->selectors = $this->evalSelectors($block->selectors);
1545
+
1546
+		$out = $this->makeOutputBlock(null);
1547
+
1548
+		if (isset($this->lineNumberStyle) && \count($env->selectors) && \count($block->children)) {
1549
+			$annotation = $this->makeOutputBlock(Type::T_COMMENT);
1550
+			$annotation->depth = 0;
1551
+
1552
+			$file = $this->sourceNames[$block->sourceIndex];
1553
+			$line = $block->sourceLine;
1554
+
1555
+			switch ($this->lineNumberStyle) {
1556
+				case static::LINE_COMMENTS:
1557
+					$annotation->lines[] = '/* line ' . $line
1558
+										 . ($file ? ', ' . $file : '')
1559
+										 . ' */';
1560
+					break;
1561
+
1562
+				case static::DEBUG_INFO:
1563
+					$annotation->lines[] = '@media -sass-debug-info{'
1564
+										 . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1565
+										 . 'line{font-family:' . $line . '}}';
1566
+					break;
1567
+			}
1568
+
1569
+			$this->scope->children[] = $annotation;
1570
+		}
1571
+
1572
+		$this->scope->children[] = $out;
1573
+
1574
+		if (\count($block->children)) {
1575
+			$out->selectors = $this->multiplySelectors($env, $block->selfParent);
1576
+
1577
+			// propagate selfParent to the children where they still can be useful
1578
+			$selfParentSelectors = null;
1579
+
1580
+			if (isset($block->selfParent->selectors)) {
1581
+				$selfParentSelectors = $block->selfParent->selectors;
1582
+				$block->selfParent->selectors = $out->selectors;
1583
+			}
1584
+
1585
+			$this->compileChildrenNoReturn($block->children, $out, $block->selfParent);
1586
+
1587
+			// and revert for the following children of the same block
1588
+			if ($selfParentSelectors) {
1589
+				$block->selfParent->selectors = $selfParentSelectors;
1590
+			}
1591
+		}
1592
+
1593
+		$this->popEnv();
1594
+	}
1595
+
1596
+
1597
+	/**
1598
+	 * Compile the value of a comment that can have interpolation
1599
+	 *
1600
+	 * @param array   $value
1601
+	 * @param boolean $pushEnv
1602
+	 *
1603
+	 * @return array|mixed|string
1604
+	 */
1605
+	protected function compileCommentValue($value, $pushEnv = false)
1606
+	{
1607
+		$c = $value[1];
1608
+
1609
+		if (isset($value[2])) {
1610
+			if ($pushEnv) {
1611
+				$this->pushEnv();
1612
+			}
1613
+
1614
+			$ignoreCallStackMessage = $this->ignoreCallStackMessage;
1615
+			$this->ignoreCallStackMessage = true;
1616
+
1617
+			try {
1618
+				$c = $this->compileValue($value[2]);
1619
+			} catch (\Exception $e) {
1620
+				// ignore error in comment compilation which are only interpolation
1621
+			}
1622
+
1623
+			$this->ignoreCallStackMessage = $ignoreCallStackMessage;
1624
+
1625
+			if ($pushEnv) {
1626
+				$this->popEnv();
1627
+			}
1628
+		}
1629
+
1630
+		return $c;
1631
+	}
1632
+
1633
+	/**
1634
+	 * Compile root level comment
1635
+	 *
1636
+	 * @param array $block
1637
+	 */
1638
+	protected function compileComment($block)
1639
+	{
1640
+		$out = $this->makeOutputBlock(Type::T_COMMENT);
1641
+		$out->lines[] = $this->compileCommentValue($block, true);
1642
+
1643
+		$this->scope->children[] = $out;
1644
+	}
1645
+
1646
+	/**
1647
+	 * Evaluate selectors
1648
+	 *
1649
+	 * @param array $selectors
1650
+	 *
1651
+	 * @return array
1652
+	 */
1653
+	protected function evalSelectors($selectors)
1654
+	{
1655
+		$this->shouldEvaluate = false;
1656
+
1657
+		$selectors = array_map([$this, 'evalSelector'], $selectors);
1658
+
1659
+		// after evaluating interpolates, we might need a second pass
1660
+		if ($this->shouldEvaluate) {
1661
+			$selectors = $this->replaceSelfSelector($selectors, '&');
1662
+			$buffer    = $this->collapseSelectors($selectors);
1663
+			$parser    = $this->parserFactory(__METHOD__);
1664
+
1665
+			if ($parser->parseSelector($buffer, $newSelectors)) {
1666
+				$selectors = array_map([$this, 'evalSelector'], $newSelectors);
1667
+			}
1668
+		}
1669
+
1670
+		return $selectors;
1671
+	}
1672
+
1673
+	/**
1674
+	 * Evaluate selector
1675
+	 *
1676
+	 * @param array $selector
1677
+	 *
1678
+	 * @return array
1679
+	 */
1680
+	protected function evalSelector($selector)
1681
+	{
1682
+		return array_map([$this, 'evalSelectorPart'], $selector);
1683
+	}
1684
+
1685
+	/**
1686
+	 * Evaluate selector part; replaces all the interpolates, stripping quotes
1687
+	 *
1688
+	 * @param array $part
1689
+	 *
1690
+	 * @return array
1691
+	 */
1692
+	protected function evalSelectorPart($part)
1693
+	{
1694
+		foreach ($part as &$p) {
1695
+			if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1696
+				$p = $this->compileValue($p);
1697
+
1698
+				// force re-evaluation
1699
+				if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1700
+					$this->shouldEvaluate = true;
1701
+				}
1702
+			} elseif (
1703
+				\is_string($p) && \strlen($p) >= 2 &&
1704
+				($first = $p[0]) && ($first === '"' || $first === "'") &&
1705
+				substr($p, -1) === $first
1706
+			) {
1707
+				$p = substr($p, 1, -1);
1708
+			}
1709
+		}
1710
+
1711
+		return $this->flattenSelectorSingle($part);
1712
+	}
1713
+
1714
+	/**
1715
+	 * Collapse selectors
1716
+	 *
1717
+	 * @param array   $selectors
1718
+	 * @param boolean $selectorFormat
1719
+	 *   if false return a collapsed string
1720
+	 *   if true return an array description of a structured selector
1721
+	 *
1722
+	 * @return string
1723
+	 */
1724
+	protected function collapseSelectors($selectors, $selectorFormat = false)
1725
+	{
1726
+		$parts = [];
1727
+
1728
+		foreach ($selectors as $selector) {
1729
+			$output = [];
1730
+			$glueNext = false;
1731
+
1732
+			foreach ($selector as $node) {
1733
+				$compound = '';
1734
+
1735
+				array_walk_recursive(
1736
+					$node,
1737
+					function ($value, $key) use (&$compound) {
1738
+						$compound .= $value;
1739
+					}
1740
+				);
1741
+
1742
+				if ($selectorFormat && $this->isImmediateRelationshipCombinator($compound)) {
1743
+					if (\count($output)) {
1744
+						$output[\count($output) - 1] .= ' ' . $compound;
1745
+					} else {
1746
+						$output[] = $compound;
1747
+					}
1748
+
1749
+					$glueNext = true;
1750
+				} elseif ($glueNext) {
1751
+					$output[\count($output) - 1] .= ' ' . $compound;
1752
+					$glueNext = false;
1753
+				} else {
1754
+					$output[] = $compound;
1755
+				}
1756
+			}
1757
+
1758
+			if ($selectorFormat) {
1759
+				foreach ($output as &$o) {
1760
+					$o = [Type::T_STRING, '', [$o]];
1761
+				}
1762
+
1763
+				$output = [Type::T_LIST, ' ', $output];
1764
+			} else {
1765
+				$output = implode(' ', $output);
1766
+			}
1767
+
1768
+			$parts[] = $output;
1769
+		}
1770
+
1771
+		if ($selectorFormat) {
1772
+			$parts = [Type::T_LIST, ',', $parts];
1773
+		} else {
1774
+			$parts = implode(', ', $parts);
1775
+		}
1776
+
1777
+		return $parts;
1778
+	}
1779
+
1780
+	/**
1781
+	 * Parse down the selector and revert [self] to "&" before a reparsing
1782
+	 *
1783
+	 * @param array $selectors
1784
+	 *
1785
+	 * @return array
1786
+	 */
1787
+	protected function replaceSelfSelector($selectors, $replace = null)
1788
+	{
1789
+		foreach ($selectors as &$part) {
1790
+			if (\is_array($part)) {
1791
+				if ($part === [Type::T_SELF]) {
1792
+					if (\is_null($replace)) {
1793
+						$replace = $this->reduce([Type::T_SELF]);
1794
+						$replace = $this->compileValue($replace);
1795
+					}
1796
+					$part = $replace;
1797
+				} else {
1798
+					$part = $this->replaceSelfSelector($part, $replace);
1799
+				}
1800
+			}
1801
+		}
1802
+
1803
+		return $selectors;
1804
+	}
1805
+
1806
+	/**
1807
+	 * Flatten selector single; joins together .classes and #ids
1808
+	 *
1809
+	 * @param array $single
1810
+	 *
1811
+	 * @return array
1812
+	 */
1813
+	protected function flattenSelectorSingle($single)
1814
+	{
1815
+		$joined = [];
1816
+
1817
+		foreach ($single as $part) {
1818
+			if (
1819
+				empty($joined) ||
1820
+				! \is_string($part) ||
1821
+				preg_match('/[\[.:#%]/', $part)
1822
+			) {
1823
+				$joined[] = $part;
1824
+				continue;
1825
+			}
1826
+
1827
+			if (\is_array(end($joined))) {
1828
+				$joined[] = $part;
1829
+			} else {
1830
+				$joined[\count($joined) - 1] .= $part;
1831
+			}
1832
+		}
1833
+
1834
+		return $joined;
1835
+	}
1836
+
1837
+	/**
1838
+	 * Compile selector to string; self(&) should have been replaced by now
1839
+	 *
1840
+	 * @param string|array $selector
1841
+	 *
1842
+	 * @return string
1843
+	 */
1844
+	protected function compileSelector($selector)
1845
+	{
1846
+		if (! \is_array($selector)) {
1847
+			return $selector; // media and the like
1848
+		}
1849
+
1850
+		return implode(
1851
+			' ',
1852
+			array_map(
1853
+				[$this, 'compileSelectorPart'],
1854
+				$selector
1855
+			)
1856
+		);
1857
+	}
1858
+
1859
+	/**
1860
+	 * Compile selector part
1861
+	 *
1862
+	 * @param array $piece
1863
+	 *
1864
+	 * @return string
1865
+	 */
1866
+	protected function compileSelectorPart($piece)
1867
+	{
1868
+		foreach ($piece as &$p) {
1869
+			if (! \is_array($p)) {
1870
+				continue;
1871
+			}
1872
+
1873
+			switch ($p[0]) {
1874
+				case Type::T_SELF:
1875
+					$p = '&';
1876
+					break;
1877
+
1878
+				default:
1879
+					$p = $this->compileValue($p);
1880
+					break;
1881
+			}
1882
+		}
1883
+
1884
+		return implode($piece);
1885
+	}
1886
+
1887
+	/**
1888
+	 * Has selector placeholder?
1889
+	 *
1890
+	 * @param array $selector
1891
+	 *
1892
+	 * @return boolean
1893
+	 */
1894
+	protected function hasSelectorPlaceholder($selector)
1895
+	{
1896
+		if (! \is_array($selector)) {
1897
+			return false;
1898
+		}
1899
+
1900
+		foreach ($selector as $parts) {
1901
+			foreach ($parts as $part) {
1902
+				if (\strlen($part) && '%' === $part[0]) {
1903
+					return true;
1904
+				}
1905
+			}
1906
+		}
1907
+
1908
+		return false;
1909
+	}
1910
+
1911
+	protected function pushCallStack($name = '')
1912
+	{
1913
+		$this->callStack[] = [
1914
+		  'n' => $name,
1915
+		  Parser::SOURCE_INDEX => $this->sourceIndex,
1916
+		  Parser::SOURCE_LINE => $this->sourceLine,
1917
+		  Parser::SOURCE_COLUMN => $this->sourceColumn
1918
+		];
1919
+
1920
+		// infinite calling loop
1921
+		if (\count($this->callStack) > 25000) {
1922
+			// not displayed but you can var_dump it to deep debug
1923
+			$msg = $this->callStackMessage(true, 100);
1924
+			$msg = 'Infinite calling loop';
1925
+
1926
+			throw $this->error($msg);
1927
+		}
1928
+	}
1929
+
1930
+	protected function popCallStack()
1931
+	{
1932
+		array_pop($this->callStack);
1933
+	}
1934
+
1935
+	/**
1936
+	 * Compile children and return result
1937
+	 *
1938
+	 * @param array                                  $stms
1939
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1940
+	 * @param string                                 $traceName
1941
+	 *
1942
+	 * @return array|null
1943
+	 */
1944
+	protected function compileChildren($stms, OutputBlock $out, $traceName = '')
1945
+	{
1946
+		$this->pushCallStack($traceName);
1947
+
1948
+		foreach ($stms as $stm) {
1949
+			$ret = $this->compileChild($stm, $out);
1950
+
1951
+			if (isset($ret)) {
1952
+				$this->popCallStack();
1953
+
1954
+				return $ret;
1955
+			}
1956
+		}
1957
+
1958
+		$this->popCallStack();
1959
+
1960
+		return null;
1961
+	}
1962
+
1963
+	/**
1964
+	 * Compile children and throw exception if unexpected @return
1965
+	 *
1966
+	 * @param array                                  $stms
1967
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
1968
+	 * @param \ScssPhp\ScssPhp\Block                 $selfParent
1969
+	 * @param string                                 $traceName
1970
+	 *
1971
+	 * @throws \Exception
1972
+	 */
1973
+	protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
1974
+	{
1975
+		$this->pushCallStack($traceName);
1976
+
1977
+		foreach ($stms as $stm) {
1978
+			if ($selfParent && isset($stm[1]) && \is_object($stm[1]) && $stm[1] instanceof Block) {
1979
+				$stm[1]->selfParent = $selfParent;
1980
+				$ret = $this->compileChild($stm, $out);
1981
+				$stm[1]->selfParent = null;
1982
+			} elseif ($selfParent && \in_array($stm[0], [TYPE::T_INCLUDE, TYPE::T_EXTEND])) {
1983
+				$stm['selfParent'] = $selfParent;
1984
+				$ret = $this->compileChild($stm, $out);
1985
+				unset($stm['selfParent']);
1986
+			} else {
1987
+				$ret = $this->compileChild($stm, $out);
1988
+			}
1989
+
1990
+			if (isset($ret)) {
1991
+				throw $this->error('@return may only be used within a function');
1992
+			}
1993
+		}
1994
+
1995
+		$this->popCallStack();
1996
+	}
1997
+
1998
+
1999
+	/**
2000
+	 * evaluate media query : compile internal value keeping the structure inchanged
2001
+	 *
2002
+	 * @param array $queryList
2003
+	 *
2004
+	 * @return array
2005
+	 */
2006
+	protected function evaluateMediaQuery($queryList)
2007
+	{
2008
+		static $parser = null;
2009
+
2010
+		$outQueryList = [];
2011
+
2012
+		foreach ($queryList as $kql => $query) {
2013
+			$shouldReparse = false;
2014
+
2015
+			foreach ($query as $kq => $q) {
2016
+				for ($i = 1; $i < \count($q); $i++) {
2017
+					$value = $this->compileValue($q[$i]);
2018
+
2019
+					// the parser had no mean to know if media type or expression if it was an interpolation
2020
+					// so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
2021
+					if (
2022
+						$q[0] == Type::T_MEDIA_TYPE &&
2023
+						(strpos($value, '(') !== false ||
2024
+						strpos($value, ')') !== false ||
2025
+						strpos($value, ':') !== false ||
2026
+						strpos($value, ',') !== false)
2027
+					) {
2028
+						$shouldReparse = true;
2029
+					}
2030
+
2031
+					$queryList[$kql][$kq][$i] = [Type::T_KEYWORD, $value];
2032
+				}
2033
+			}
2034
+
2035
+			if ($shouldReparse) {
2036
+				if (\is_null($parser)) {
2037
+					$parser = $this->parserFactory(__METHOD__);
2038
+				}
2039
+
2040
+				$queryString = $this->compileMediaQuery([$queryList[$kql]]);
2041
+				$queryString = reset($queryString);
2042
+
2043
+				if (strpos($queryString, '@media ') === 0) {
2044
+					$queryString = substr($queryString, 7);
2045
+					$queries = [];
2046
+
2047
+					if ($parser->parseMediaQueryList($queryString, $queries)) {
2048
+						$queries = $this->evaluateMediaQuery($queries[2]);
2049
+
2050
+						while (\count($queries)) {
2051
+							$outQueryList[] = array_shift($queries);
2052
+						}
2053
+
2054
+						continue;
2055
+					}
2056
+				}
2057
+			}
2058
+
2059
+			$outQueryList[] = $queryList[$kql];
2060
+		}
2061
+
2062
+		return $outQueryList;
2063
+	}
2064
+
2065
+	/**
2066
+	 * Compile media query
2067
+	 *
2068
+	 * @param array $queryList
2069
+	 *
2070
+	 * @return array
2071
+	 */
2072
+	protected function compileMediaQuery($queryList)
2073
+	{
2074
+		$start   = '@media ';
2075
+		$default = trim($start);
2076
+		$out     = [];
2077
+		$current = '';
2078
+
2079
+		foreach ($queryList as $query) {
2080
+			$type = null;
2081
+			$parts = [];
2082
+
2083
+			$mediaTypeOnly = true;
2084
+
2085
+			foreach ($query as $q) {
2086
+				if ($q[0] !== Type::T_MEDIA_TYPE) {
2087
+					$mediaTypeOnly = false;
2088
+					break;
2089
+				}
2090
+			}
2091
+
2092
+			foreach ($query as $q) {
2093
+				switch ($q[0]) {
2094
+					case Type::T_MEDIA_TYPE:
2095
+						$newType = array_map([$this, 'compileValue'], \array_slice($q, 1));
2096
+
2097
+						// combining not and anything else than media type is too risky and should be avoided
2098
+						if (! $mediaTypeOnly) {
2099
+							if (\in_array(Type::T_NOT, $newType) || ($type && \in_array(Type::T_NOT, $type) )) {
2100
+								if ($type) {
2101
+									array_unshift($parts, implode(' ', array_filter($type)));
2102
+								}
2103
+
2104
+								if (! empty($parts)) {
2105
+									if (\strlen($current)) {
2106
+										$current .= $this->formatter->tagSeparator;
2107
+									}
2108
+
2109
+									$current .= implode(' and ', $parts);
2110
+								}
2111
+
2112
+								if ($current) {
2113
+									$out[] = $start . $current;
2114
+								}
2115
+
2116
+								$current = '';
2117
+								$type    = null;
2118
+								$parts   = [];
2119
+							}
2120
+						}
2121
+
2122
+						if ($newType === ['all'] && $default) {
2123
+							$default = $start . 'all';
2124
+						}
2125
+
2126
+						// all can be safely ignored and mixed with whatever else
2127
+						if ($newType !== ['all']) {
2128
+							if ($type) {
2129
+								$type = $this->mergeMediaTypes($type, $newType);
2130
+
2131
+								if (empty($type)) {
2132
+									// merge failed : ignore this query that is not valid, skip to the next one
2133
+									$parts = [];
2134
+									$default = ''; // if everything fail, no @media at all
2135
+									continue 3;
2136
+								}
2137
+							} else {
2138
+								$type = $newType;
2139
+							}
2140
+						}
2141
+						break;
2142
+
2143
+					case Type::T_MEDIA_EXPRESSION:
2144
+						if (isset($q[2])) {
2145
+							$parts[] = '('
2146
+								. $this->compileValue($q[1])
2147
+								. $this->formatter->assignSeparator
2148
+								. $this->compileValue($q[2])
2149
+								. ')';
2150
+						} else {
2151
+							$parts[] = '('
2152
+								. $this->compileValue($q[1])
2153
+								. ')';
2154
+						}
2155
+						break;
2156
+
2157
+					case Type::T_MEDIA_VALUE:
2158
+						$parts[] = $this->compileValue($q[1]);
2159
+						break;
2160
+				}
2161
+			}
2162
+
2163
+			if ($type) {
2164
+				array_unshift($parts, implode(' ', array_filter($type)));
2165
+			}
2166
+
2167
+			if (! empty($parts)) {
2168
+				if (\strlen($current)) {
2169
+					$current .= $this->formatter->tagSeparator;
2170
+				}
2171
+
2172
+				$current .= implode(' and ', $parts);
2173
+			}
2174
+		}
2175
+
2176
+		if ($current) {
2177
+			$out[] = $start . $current;
2178
+		}
2179
+
2180
+		// no @media type except all, and no conflict?
2181
+		if (! $out && $default) {
2182
+			$out[] = $default;
2183
+		}
2184
+
2185
+		return $out;
2186
+	}
2187
+
2188
+	/**
2189
+	 * Merge direct relationships between selectors
2190
+	 *
2191
+	 * @param array $selectors1
2192
+	 * @param array $selectors2
2193
+	 *
2194
+	 * @return array
2195
+	 */
2196
+	protected function mergeDirectRelationships($selectors1, $selectors2)
2197
+	{
2198
+		if (empty($selectors1) || empty($selectors2)) {
2199
+			return array_merge($selectors1, $selectors2);
2200
+		}
2201
+
2202
+		$part1 = end($selectors1);
2203
+		$part2 = end($selectors2);
2204
+
2205
+		if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2206
+			return array_merge($selectors1, $selectors2);
2207
+		}
2208
+
2209
+		$merged = [];
2210
+
2211
+		do {
2212
+			$part1 = array_pop($selectors1);
2213
+			$part2 = array_pop($selectors2);
2214
+
2215
+			if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2216
+				if ($this->isImmediateRelationshipCombinator(reset($merged)[0])) {
2217
+					array_unshift($merged, [$part1[0] . $part2[0]]);
2218
+					$merged = array_merge($selectors1, $selectors2, $merged);
2219
+				} else {
2220
+					$merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged);
2221
+				}
2222
+
2223
+				break;
2224
+			}
2225
+
2226
+			array_unshift($merged, $part1);
2227
+		} while (! empty($selectors1) && ! empty($selectors2));
2228
+
2229
+		return $merged;
2230
+	}
2231
+
2232
+	/**
2233
+	 * Merge media types
2234
+	 *
2235
+	 * @param array $type1
2236
+	 * @param array $type2
2237
+	 *
2238
+	 * @return array|null
2239
+	 */
2240
+	protected function mergeMediaTypes($type1, $type2)
2241
+	{
2242
+		if (empty($type1)) {
2243
+			return $type2;
2244
+		}
2245
+
2246
+		if (empty($type2)) {
2247
+			return $type1;
2248
+		}
2249
+
2250
+		if (\count($type1) > 1) {
2251
+			$m1 = strtolower($type1[0]);
2252
+			$t1 = strtolower($type1[1]);
2253
+		} else {
2254
+			$m1 = '';
2255
+			$t1 = strtolower($type1[0]);
2256
+		}
2257
+
2258
+		if (\count($type2) > 1) {
2259
+			$m2 = strtolower($type2[0]);
2260
+			$t2 = strtolower($type2[1]);
2261
+		} else {
2262
+			$m2 = '';
2263
+			$t2 = strtolower($type2[0]);
2264
+		}
2265
+
2266
+		if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) {
2267
+			if ($t1 === $t2) {
2268
+				return null;
2269
+			}
2270
+
2271
+			return [
2272
+				$m1 === Type::T_NOT ? $m2 : $m1,
2273
+				$m1 === Type::T_NOT ? $t2 : $t1,
2274
+			];
2275
+		}
2276
+
2277
+		if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) {
2278
+			// CSS has no way of representing "neither screen nor print"
2279
+			if ($t1 !== $t2) {
2280
+				return null;
2281
+			}
2282
+
2283
+			return [Type::T_NOT, $t1];
2284
+		}
2285
+
2286
+		if ($t1 !== $t2) {
2287
+			return null;
2288
+		}
2289
+
2290
+		// t1 == t2, neither m1 nor m2 are "not"
2291
+		return [empty($m1) ? $m2 : $m1, $t1];
2292
+	}
2293
+
2294
+	/**
2295
+	 * Compile import; returns true if the value was something that could be imported
2296
+	 *
2297
+	 * @param array                                  $rawPath
2298
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2299
+	 * @param boolean                                $once
2300
+	 *
2301
+	 * @return boolean
2302
+	 */
2303
+	protected function compileImport($rawPath, OutputBlock $out, $once = false)
2304
+	{
2305
+		if ($rawPath[0] === Type::T_STRING) {
2306
+			$path = $this->compileStringContent($rawPath);
2307
+
2308
+			if (strpos($path, 'url(') !== 0 && $path = $this->findImport($path)) {
2309
+				if (! $once || ! \in_array($path, $this->importedFiles)) {
2310
+					$this->importFile($path, $out);
2311
+					$this->importedFiles[] = $path;
2312
+				}
2313
+
2314
+				return true;
2315
+			}
2316
+
2317
+			$this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2318
+
2319
+			return false;
2320
+		}
2321
+
2322
+		if ($rawPath[0] === Type::T_LIST) {
2323
+			// handle a list of strings
2324
+			if (\count($rawPath[2]) === 0) {
2325
+				return false;
2326
+			}
2327
+
2328
+			foreach ($rawPath[2] as $path) {
2329
+				if ($path[0] !== Type::T_STRING) {
2330
+					$this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2331
+
2332
+					return false;
2333
+				}
2334
+			}
2335
+
2336
+			foreach ($rawPath[2] as $path) {
2337
+				$this->compileImport($path, $out, $once);
2338
+			}
2339
+
2340
+			return true;
2341
+		}
2342
+
2343
+		$this->appendRootDirective('@import ' . $this->compileImportPath($rawPath) . ';', $out);
2344
+
2345
+		return false;
2346
+	}
2347
+
2348
+	/**
2349
+	 * @param $rawPath
2350
+	 * @return string
2351
+	 * @throws CompilerException
2352
+	 */
2353
+	protected function compileImportPath($rawPath)
2354
+	{
2355
+		$path = $this->compileValue($rawPath);
2356
+
2357
+		// case url() without quotes : supress \r \n remaining in the path
2358
+		// if this is a real string there can not be CR or LF char
2359
+		if (strpos($path, 'url(') === 0) {
2360
+			$path = str_replace(array("\r", "\n"), array('', ' '), $path);
2361
+		} else {
2362
+			// if this is a file name in a string, spaces shoudl be escaped
2363
+			$path = $this->reduce($rawPath);
2364
+			$path = $this->escapeImportPathString($path);
2365
+			$path = $this->compileValue($path);
2366
+		}
2367
+
2368
+		return $path;
2369
+	}
2370
+
2371
+	/**
2372
+	 * @param array $path
2373
+	 * @return array
2374
+	 * @throws CompilerException
2375
+	 */
2376
+	protected function escapeImportPathString($path)
2377
+	{
2378
+		switch ($path[0]) {
2379
+			case Type::T_LIST:
2380
+				foreach ($path[2] as $k => $v) {
2381
+					$path[2][$k] = $this->escapeImportPathString($v);
2382
+				}
2383
+				break;
2384
+			case Type::T_STRING:
2385
+				if ($path[1]) {
2386
+					$path = $this->compileValue($path);
2387
+					$path = str_replace(' ', '\\ ', $path);
2388
+					$path = [Type::T_KEYWORD, $path];
2389
+				}
2390
+				break;
2391
+		}
2392
+
2393
+		return $path;
2394
+	}
2395
+
2396
+	/**
2397
+	 * Append a root directive like @import or @charset as near as the possible from the source code
2398
+	 * (keeping before comments, @import and @charset coming before in the source code)
2399
+	 *
2400
+	 * @param string                                        $line
2401
+	 * @param @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2402
+	 * @param array                                         $allowed
2403
+	 */
2404
+	protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
2405
+	{
2406
+		$root = $out;
2407
+
2408
+		while ($root->parent) {
2409
+			$root = $root->parent;
2410
+		}
2411
+
2412
+		$i = 0;
2413
+
2414
+		while ($i < \count($root->children)) {
2415
+			if (! isset($root->children[$i]->type) || ! \in_array($root->children[$i]->type, $allowed)) {
2416
+				break;
2417
+			}
2418
+
2419
+			$i++;
2420
+		}
2421
+
2422
+		// remove incompatible children from the bottom of the list
2423
+		$saveChildren = [];
2424
+
2425
+		while ($i < \count($root->children)) {
2426
+			$saveChildren[] = array_pop($root->children);
2427
+		}
2428
+
2429
+		// insert the directive as a comment
2430
+		$child = $this->makeOutputBlock(Type::T_COMMENT);
2431
+		$child->lines[]      = $line;
2432
+		$child->sourceName   = $this->sourceNames[$this->sourceIndex];
2433
+		$child->sourceLine   = $this->sourceLine;
2434
+		$child->sourceColumn = $this->sourceColumn;
2435
+
2436
+		$root->children[] = $child;
2437
+
2438
+		// repush children
2439
+		while (\count($saveChildren)) {
2440
+			$root->children[] = array_pop($saveChildren);
2441
+		}
2442
+	}
2443
+
2444
+	/**
2445
+	 * Append lines to the current output block:
2446
+	 * directly to the block or through a child if necessary
2447
+	 *
2448
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2449
+	 * @param string                                 $type
2450
+	 * @param string|mixed                           $line
2451
+	 */
2452
+	protected function appendOutputLine(OutputBlock $out, $type, $line)
2453
+	{
2454
+		$outWrite = &$out;
2455
+
2456
+		// check if it's a flat output or not
2457
+		if (\count($out->children)) {
2458
+			$lastChild = &$out->children[\count($out->children) - 1];
2459
+
2460
+			if (
2461
+				$lastChild->depth === $out->depth &&
2462
+				\is_null($lastChild->selectors) &&
2463
+				! \count($lastChild->children)
2464
+			) {
2465
+				$outWrite = $lastChild;
2466
+			} else {
2467
+				$nextLines = $this->makeOutputBlock($type);
2468
+				$nextLines->parent = $out;
2469
+				$nextLines->depth  = $out->depth;
2470
+
2471
+				$out->children[] = $nextLines;
2472
+				$outWrite = &$nextLines;
2473
+			}
2474
+		}
2475
+
2476
+		$outWrite->lines[] = $line;
2477
+	}
2478
+
2479
+	/**
2480
+	 * Compile child; returns a value to halt execution
2481
+	 *
2482
+	 * @param array                                  $child
2483
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
2484
+	 *
2485
+	 * @return array
2486
+	 */
2487
+	protected function compileChild($child, OutputBlock $out)
2488
+	{
2489
+		if (isset($child[Parser::SOURCE_LINE])) {
2490
+			$this->sourceIndex  = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
2491
+			$this->sourceLine   = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
2492
+			$this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
2493
+		} elseif (\is_array($child) && isset($child[1]->sourceLine)) {
2494
+			$this->sourceIndex  = $child[1]->sourceIndex;
2495
+			$this->sourceLine   = $child[1]->sourceLine;
2496
+			$this->sourceColumn = $child[1]->sourceColumn;
2497
+		} elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2498
+			$this->sourceLine   = $out->sourceLine;
2499
+			$this->sourceIndex  = array_search($out->sourceName, $this->sourceNames);
2500
+			$this->sourceColumn = $out->sourceColumn;
2501
+
2502
+			if ($this->sourceIndex === false) {
2503
+				$this->sourceIndex = null;
2504
+			}
2505
+		}
2506
+
2507
+		switch ($child[0]) {
2508
+			case Type::T_SCSSPHP_IMPORT_ONCE:
2509
+				$rawPath = $this->reduce($child[1]);
2510
+
2511
+				$this->compileImport($rawPath, $out, true);
2512
+				break;
2513
+
2514
+			case Type::T_IMPORT:
2515
+				$rawPath = $this->reduce($child[1]);
2516
+
2517
+				$this->compileImport($rawPath, $out);
2518
+				break;
2519
+
2520
+			case Type::T_DIRECTIVE:
2521
+				$this->compileDirective($child[1], $out);
2522
+				break;
2523
+
2524
+			case Type::T_AT_ROOT:
2525
+				$this->compileAtRoot($child[1]);
2526
+				break;
2527
+
2528
+			case Type::T_MEDIA:
2529
+				$this->compileMedia($child[1]);
2530
+				break;
2531
+
2532
+			case Type::T_BLOCK:
2533
+				$this->compileBlock($child[1]);
2534
+				break;
2535
+
2536
+			case Type::T_CHARSET:
2537
+				if (! $this->charsetSeen) {
2538
+					$this->charsetSeen = true;
2539
+					$this->appendRootDirective('@charset ' . $this->compileValue($child[1]) . ';', $out);
2540
+				}
2541
+				break;
2542
+
2543
+			case Type::T_CUSTOM_PROPERTY:
2544
+				list(, $name, $value) = $child;
2545
+				$compiledName = $this->compileValue($name);
2546
+
2547
+				// if the value reduces to null from something else then
2548
+				// the property should be discarded
2549
+				if ($value[0] !== Type::T_NULL) {
2550
+					$value = $this->reduce($value);
2551
+
2552
+					if ($value[0] === Type::T_NULL || $value === static::$nullString) {
2553
+						break;
2554
+					}
2555
+				}
2556
+
2557
+				$compiledValue = $this->compileValue($value);
2558
+
2559
+				$line = $this->formatter->customProperty(
2560
+					$compiledName,
2561
+					$compiledValue
2562
+				);
2563
+
2564
+				$this->appendOutputLine($out, Type::T_ASSIGN, $line);
2565
+				break;
2566
+
2567
+			case Type::T_ASSIGN:
2568
+				list(, $name, $value) = $child;
2569
+
2570
+				if ($name[0] === Type::T_VARIABLE) {
2571
+					$flags     = isset($child[3]) ? $child[3] : [];
2572
+					$isDefault = \in_array('!default', $flags);
2573
+					$isGlobal  = \in_array('!global', $flags);
2574
+
2575
+					if ($isGlobal) {
2576
+						$this->set($name[1], $this->reduce($value), false, $this->rootEnv, $value);
2577
+						break;
2578
+					}
2579
+
2580
+					$shouldSet = $isDefault &&
2581
+						(\is_null($result = $this->get($name[1], false)) ||
2582
+						$result === static::$null);
2583
+
2584
+					if (! $isDefault || $shouldSet) {
2585
+						$this->set($name[1], $this->reduce($value), true, null, $value);
2586
+					}
2587
+					break;
2588
+				}
2589
+
2590
+				$compiledName = $this->compileValue($name);
2591
+
2592
+				// handle shorthand syntaxes : size / line-height...
2593
+				if (\in_array($compiledName, ['font', 'grid-row', 'grid-column', 'border-radius'])) {
2594
+					if ($value[0] === Type::T_VARIABLE) {
2595
+						// if the font value comes from variable, the content is already reduced
2596
+						// (i.e., formulas were already calculated), so we need the original unreduced value
2597
+						$value = $this->get($value[1], true, null, true);
2598
+					}
2599
+
2600
+					$shorthandValue=&$value;
2601
+
2602
+					$shorthandDividerNeedsUnit = false;
2603
+					$maxListElements           = null;
2604
+					$maxShorthandDividers      = 1;
2605
+
2606
+					switch ($compiledName) {
2607
+						case 'border-radius':
2608
+							$maxListElements = 4;
2609
+							$shorthandDividerNeedsUnit = true;
2610
+							break;
2611
+					}
2612
+
2613
+					if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
2614
+						// this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2615
+						// we need to handle the first list element
2616
+						$shorthandValue=&$value[2][0];
2617
+					}
2618
+
2619
+					if ($shorthandValue[0] === Type::T_EXPRESSION && $shorthandValue[1] === '/') {
2620
+						$revert = true;
2621
+
2622
+						if ($shorthandDividerNeedsUnit) {
2623
+							$divider = $shorthandValue[3];
2624
+
2625
+							if (\is_array($divider)) {
2626
+								$divider = $this->reduce($divider, true);
2627
+							}
2628
+
2629
+							if (\intval($divider->dimension) && ! \count($divider->units)) {
2630
+								$revert = false;
2631
+							}
2632
+						}
2633
+
2634
+						if ($revert) {
2635
+							$shorthandValue = $this->expToString($shorthandValue);
2636
+						}
2637
+					} elseif ($shorthandValue[0] === Type::T_LIST) {
2638
+						foreach ($shorthandValue[2] as &$item) {
2639
+							if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
2640
+								if ($maxShorthandDividers > 0) {
2641
+									$revert = true;
2642
+
2643
+									// if the list of values is too long, this has to be a shorthand,
2644
+									// otherwise it could be a real division
2645
+									if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) {
2646
+										if ($shorthandDividerNeedsUnit) {
2647
+											$divider = $item[3];
2648
+
2649
+											if (\is_array($divider)) {
2650
+												$divider = $this->reduce($divider, true);
2651
+											}
2652
+
2653
+											if (\intval($divider->dimension) && ! \count($divider->units)) {
2654
+												$revert = false;
2655
+											}
2656
+										}
2657
+									}
2658
+
2659
+									if ($revert) {
2660
+										$item = $this->expToString($item);
2661
+										$maxShorthandDividers--;
2662
+									}
2663
+								}
2664
+							}
2665
+						}
2666
+					}
2667
+				}
2668
+
2669
+				// if the value reduces to null from something else then
2670
+				// the property should be discarded
2671
+				if ($value[0] !== Type::T_NULL) {
2672
+					$value = $this->reduce($value);
2673
+
2674
+					if ($value[0] === Type::T_NULL || $value === static::$nullString) {
2675
+						break;
2676
+					}
2677
+				}
2678
+
2679
+				$compiledValue = $this->compileValue($value);
2680
+
2681
+				// ignore empty value
2682
+				if (\strlen($compiledValue)) {
2683
+					$line = $this->formatter->property(
2684
+						$compiledName,
2685
+						$compiledValue
2686
+					);
2687
+					$this->appendOutputLine($out, Type::T_ASSIGN, $line);
2688
+				}
2689
+				break;
2690
+
2691
+			case Type::T_COMMENT:
2692
+				if ($out->type === Type::T_ROOT) {
2693
+					$this->compileComment($child);
2694
+					break;
2695
+				}
2696
+
2697
+				$line = $this->compileCommentValue($child, true);
2698
+				$this->appendOutputLine($out, Type::T_COMMENT, $line);
2699
+				break;
2700
+
2701
+			case Type::T_MIXIN:
2702
+			case Type::T_FUNCTION:
2703
+				list(, $block) = $child;
2704
+				// the block need to be able to go up to it's parent env to resolve vars
2705
+				$block->parentEnv = $this->getStoreEnv();
2706
+				$this->set(static::$namespaces[$block->type] . $block->name, $block, true);
2707
+				break;
2708
+
2709
+			case Type::T_EXTEND:
2710
+				foreach ($child[1] as $sel) {
2711
+					$sel = $this->replaceSelfSelector($sel);
2712
+					$results = $this->evalSelectors([$sel]);
2713
+
2714
+					foreach ($results as $result) {
2715
+						// only use the first one
2716
+						$result = current($result);
2717
+						$selectors = $out->selectors;
2718
+
2719
+						if (! $selectors && isset($child['selfParent'])) {
2720
+							$selectors = $this->multiplySelectors($this->env, $child['selfParent']);
2721
+						}
2722
+
2723
+						$this->pushExtends($result, $selectors, $child);
2724
+					}
2725
+				}
2726
+				break;
2727
+
2728
+			case Type::T_IF:
2729
+				list(, $if) = $child;
2730
+
2731
+				if ($this->isTruthy($this->reduce($if->cond, true))) {
2732
+					return $this->compileChildren($if->children, $out);
2733
+				}
2734
+
2735
+				foreach ($if->cases as $case) {
2736
+					if (
2737
+						$case->type === Type::T_ELSE ||
2738
+						$case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
2739
+					) {
2740
+						return $this->compileChildren($case->children, $out);
2741
+					}
2742
+				}
2743
+				break;
2744
+
2745
+			case Type::T_EACH:
2746
+				list(, $each) = $child;
2747
+
2748
+				$list = $this->coerceList($this->reduce($each->list), ',', true);
2749
+
2750
+				$this->pushEnv();
2751
+
2752
+				foreach ($list[2] as $item) {
2753
+					if (\count($each->vars) === 1) {
2754
+						$this->set($each->vars[0], $item, true);
2755
+					} else {
2756
+						list(,, $values) = $this->coerceList($item);
2757
+
2758
+						foreach ($each->vars as $i => $var) {
2759
+							$this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true);
2760
+						}
2761
+					}
2762
+
2763
+					$ret = $this->compileChildren($each->children, $out);
2764
+
2765
+					if ($ret) {
2766
+						if ($ret[0] !== Type::T_CONTROL) {
2767
+							$store = $this->env->store;
2768
+							$this->popEnv();
2769
+							$this->backPropagateEnv($store, $each->vars);
2770
+
2771
+							return $ret;
2772
+						}
2773
+
2774
+						if ($ret[1]) {
2775
+							break;
2776
+						}
2777
+					}
2778
+				}
2779
+				$store = $this->env->store;
2780
+				$this->popEnv();
2781
+				$this->backPropagateEnv($store, $each->vars);
2782
+
2783
+				break;
2784
+
2785
+			case Type::T_WHILE:
2786
+				list(, $while) = $child;
2787
+
2788
+				while ($this->isTruthy($this->reduce($while->cond, true))) {
2789
+					$ret = $this->compileChildren($while->children, $out);
2790
+
2791
+					if ($ret) {
2792
+						if ($ret[0] !== Type::T_CONTROL) {
2793
+							return $ret;
2794
+						}
2795 2795
 
2796
-                        if ($ret[1]) {
2797
-                            break;
2798
-                        }
2799
-                    }
2800
-                }
2801
-                break;
2802
-
2803
-            case Type::T_FOR:
2804
-                list(, $for) = $child;
2796
+						if ($ret[1]) {
2797
+							break;
2798
+						}
2799
+					}
2800
+				}
2801
+				break;
2802
+
2803
+			case Type::T_FOR:
2804
+				list(, $for) = $child;
2805 2805
 
2806
-                $start = $this->reduce($for->start, true);
2807
-                $end   = $this->reduce($for->end, true);
2806
+				$start = $this->reduce($for->start, true);
2807
+				$end   = $this->reduce($for->end, true);
2808 2808
 
2809
-                if (! $start instanceof Node\Number) {
2810
-                    throw $this->error('%s is not a number', $start[0]);
2811
-                }
2809
+				if (! $start instanceof Node\Number) {
2810
+					throw $this->error('%s is not a number', $start[0]);
2811
+				}
2812 2812
 
2813
-                if (! $end instanceof Node\Number) {
2814
-                    throw $this->error('%s is not a number', $end[0]);
2815
-                }
2816
-
2817
-                if (! ($start[2] == $end[2] || $end->unitless())) {
2818
-                    throw $this->error('Incompatible units: "%s" && "%s".', $start->unitStr(), $end->unitStr());
2819
-                }
2820
-
2821
-                $unit  = $start[2];
2822
-                $start = $start[1];
2823
-                $end   = $end[1];
2824
-
2825
-                $d = $start < $end ? 1 : -1;
2826
-
2827
-                $this->pushEnv();
2828
-
2829
-                for (;;) {
2830
-                    if (
2831
-                        (! $for->until && $start - $d == $end) ||
2832
-                        ($for->until && $start == $end)
2833
-                    ) {
2834
-                        break;
2835
-                    }
2836
-
2837
-                    $this->set($for->var, new Node\Number($start, $unit));
2838
-                    $start += $d;
2839
-
2840
-                    $ret = $this->compileChildren($for->children, $out);
2841
-
2842
-                    if ($ret) {
2843
-                        if ($ret[0] !== Type::T_CONTROL) {
2844
-                            $store = $this->env->store;
2845
-                            $this->popEnv();
2846
-                            $this->backPropagateEnv($store, [$for->var]);
2813
+				if (! $end instanceof Node\Number) {
2814
+					throw $this->error('%s is not a number', $end[0]);
2815
+				}
2816
+
2817
+				if (! ($start[2] == $end[2] || $end->unitless())) {
2818
+					throw $this->error('Incompatible units: "%s" && "%s".', $start->unitStr(), $end->unitStr());
2819
+				}
2820
+
2821
+				$unit  = $start[2];
2822
+				$start = $start[1];
2823
+				$end   = $end[1];
2824
+
2825
+				$d = $start < $end ? 1 : -1;
2826
+
2827
+				$this->pushEnv();
2828
+
2829
+				for (;;) {
2830
+					if (
2831
+						(! $for->until && $start - $d == $end) ||
2832
+						($for->until && $start == $end)
2833
+					) {
2834
+						break;
2835
+					}
2836
+
2837
+					$this->set($for->var, new Node\Number($start, $unit));
2838
+					$start += $d;
2839
+
2840
+					$ret = $this->compileChildren($for->children, $out);
2841
+
2842
+					if ($ret) {
2843
+						if ($ret[0] !== Type::T_CONTROL) {
2844
+							$store = $this->env->store;
2845
+							$this->popEnv();
2846
+							$this->backPropagateEnv($store, [$for->var]);
2847 2847
 
2848
-                            return $ret;
2849
-                        }
2850
-
2851
-                        if ($ret[1]) {
2852
-                            break;
2853
-                        }
2854
-                    }
2855
-                }
2856
-
2857
-                $store = $this->env->store;
2858
-                $this->popEnv();
2859
-                $this->backPropagateEnv($store, [$for->var]);
2848
+							return $ret;
2849
+						}
2850
+
2851
+						if ($ret[1]) {
2852
+							break;
2853
+						}
2854
+					}
2855
+				}
2856
+
2857
+				$store = $this->env->store;
2858
+				$this->popEnv();
2859
+				$this->backPropagateEnv($store, [$for->var]);
2860 2860
 
2861
-                break;
2861
+				break;
2862 2862
 
2863
-            case Type::T_BREAK:
2864
-                return [Type::T_CONTROL, true];
2865
-
2866
-            case Type::T_CONTINUE:
2867
-                return [Type::T_CONTROL, false];
2868
-
2869
-            case Type::T_RETURN:
2870
-                return $this->reduce($child[1], true);
2871
-
2872
-            case Type::T_NESTED_PROPERTY:
2873
-                $this->compileNestedPropertiesBlock($child[1], $out);
2874
-                break;
2875
-
2876
-            case Type::T_INCLUDE:
2877
-                // including a mixin
2878
-                list(, $name, $argValues, $content, $argUsing) = $child;
2863
+			case Type::T_BREAK:
2864
+				return [Type::T_CONTROL, true];
2865
+
2866
+			case Type::T_CONTINUE:
2867
+				return [Type::T_CONTROL, false];
2868
+
2869
+			case Type::T_RETURN:
2870
+				return $this->reduce($child[1], true);
2871
+
2872
+			case Type::T_NESTED_PROPERTY:
2873
+				$this->compileNestedPropertiesBlock($child[1], $out);
2874
+				break;
2875
+
2876
+			case Type::T_INCLUDE:
2877
+				// including a mixin
2878
+				list(, $name, $argValues, $content, $argUsing) = $child;
2879 2879
 
2880
-                $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
2880
+				$mixin = $this->get(static::$namespaces['mixin'] . $name, false);
2881 2881
 
2882
-                if (! $mixin) {
2883
-                    throw $this->error("Undefined mixin $name");
2884
-                }
2882
+				if (! $mixin) {
2883
+					throw $this->error("Undefined mixin $name");
2884
+				}
2885 2885
 
2886
-                $callingScope = $this->getStoreEnv();
2886
+				$callingScope = $this->getStoreEnv();
2887 2887
 
2888
-                // push scope, apply args
2889
-                $this->pushEnv();
2890
-                $this->env->depth--;
2888
+				// push scope, apply args
2889
+				$this->pushEnv();
2890
+				$this->env->depth--;
2891 2891
 
2892
-                // Find the parent selectors in the env to be able to know what '&' refers to in the mixin
2893
-                // and assign this fake parent to childs
2894
-                $selfParent = null;
2892
+				// Find the parent selectors in the env to be able to know what '&' refers to in the mixin
2893
+				// and assign this fake parent to childs
2894
+				$selfParent = null;
2895 2895
 
2896
-                if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) {
2897
-                    $selfParent = $child['selfParent'];
2898
-                } else {
2899
-                    $parentSelectors = $this->multiplySelectors($this->env);
2896
+				if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) {
2897
+					$selfParent = $child['selfParent'];
2898
+				} else {
2899
+					$parentSelectors = $this->multiplySelectors($this->env);
2900 2900
 
2901
-                    if ($parentSelectors) {
2902
-                        $parent = new Block();
2903
-                        $parent->selectors = $parentSelectors;
2901
+					if ($parentSelectors) {
2902
+						$parent = new Block();
2903
+						$parent->selectors = $parentSelectors;
2904 2904
 
2905
-                        foreach ($mixin->children as $k => $child) {
2906
-                            if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) {
2907
-                                $mixin->children[$k][1]->parent = $parent;
2908
-                            }
2909
-                        }
2910
-                    }
2911
-                }
2905
+						foreach ($mixin->children as $k => $child) {
2906
+							if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) {
2907
+								$mixin->children[$k][1]->parent = $parent;
2908
+							}
2909
+						}
2910
+					}
2911
+				}
2912 2912
 
2913
-                // clone the stored content to not have its scope spoiled by a further call to the same mixin
2914
-                // i.e., recursive @include of the same mixin
2915
-                if (isset($content)) {
2916
-                    $copyContent = clone $content;
2917
-                    $copyContent->scope = clone $callingScope;
2913
+				// clone the stored content to not have its scope spoiled by a further call to the same mixin
2914
+				// i.e., recursive @include of the same mixin
2915
+				if (isset($content)) {
2916
+					$copyContent = clone $content;
2917
+					$copyContent->scope = clone $callingScope;
2918 2918
 
2919
-                    $this->setRaw(static::$namespaces['special'] . 'content', $copyContent, $this->env);
2920
-                } else {
2921
-                    $this->setRaw(static::$namespaces['special'] . 'content', null, $this->env);
2922
-                }
2919
+					$this->setRaw(static::$namespaces['special'] . 'content', $copyContent, $this->env);
2920
+				} else {
2921
+					$this->setRaw(static::$namespaces['special'] . 'content', null, $this->env);
2922
+				}
2923 2923
 
2924
-                // save the "using" argument list for applying it to when "@content" is invoked
2925
-                if (isset($argUsing)) {
2926
-                    $this->setRaw(static::$namespaces['special'] . 'using', $argUsing, $this->env);
2927
-                } else {
2928
-                    $this->setRaw(static::$namespaces['special'] . 'using', null, $this->env);
2929
-                }
2924
+				// save the "using" argument list for applying it to when "@content" is invoked
2925
+				if (isset($argUsing)) {
2926
+					$this->setRaw(static::$namespaces['special'] . 'using', $argUsing, $this->env);
2927
+				} else {
2928
+					$this->setRaw(static::$namespaces['special'] . 'using', null, $this->env);
2929
+				}
2930 2930
 
2931
-                if (isset($mixin->args)) {
2932
-                    $this->applyArguments($mixin->args, $argValues);
2933
-                }
2931
+				if (isset($mixin->args)) {
2932
+					$this->applyArguments($mixin->args, $argValues);
2933
+				}
2934 2934
 
2935
-                $this->env->marker = 'mixin';
2935
+				$this->env->marker = 'mixin';
2936 2936
 
2937
-                if (! empty($mixin->parentEnv)) {
2938
-                    $this->env->declarationScopeParent = $mixin->parentEnv;
2939
-                } else {
2940
-                    throw $this->error("@mixin $name() without parentEnv");
2941
-                }
2937
+				if (! empty($mixin->parentEnv)) {
2938
+					$this->env->declarationScopeParent = $mixin->parentEnv;
2939
+				} else {
2940
+					throw $this->error("@mixin $name() without parentEnv");
2941
+				}
2942 2942
 
2943
-                $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . ' ' . $name);
2943
+				$this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . ' ' . $name);
2944 2944
 
2945
-                $this->popEnv();
2946
-                break;
2945
+				$this->popEnv();
2946
+				break;
2947 2947
 
2948
-            case Type::T_MIXIN_CONTENT:
2949
-                $env        = isset($this->storeEnv) ? $this->storeEnv : $this->env;
2950
-                $content    = $this->get(static::$namespaces['special'] . 'content', false, $env);
2951
-                $argUsing   = $this->get(static::$namespaces['special'] . 'using', false, $env);
2952
-                $argContent = $child[1];
2948
+			case Type::T_MIXIN_CONTENT:
2949
+				$env        = isset($this->storeEnv) ? $this->storeEnv : $this->env;
2950
+				$content    = $this->get(static::$namespaces['special'] . 'content', false, $env);
2951
+				$argUsing   = $this->get(static::$namespaces['special'] . 'using', false, $env);
2952
+				$argContent = $child[1];
2953 2953
 
2954
-                if (! $content) {
2955
-                    break;
2956
-                }
2954
+				if (! $content) {
2955
+					break;
2956
+				}
2957 2957
 
2958
-                $storeEnv = $this->storeEnv;
2959
-                $varsUsing = [];
2958
+				$storeEnv = $this->storeEnv;
2959
+				$varsUsing = [];
2960 2960
 
2961
-                if (isset($argUsing) && isset($argContent)) {
2962
-                    // Get the arguments provided for the content with the names provided in the "using" argument list
2963
-                    $this->storeEnv = null;
2964
-                    $varsUsing = $this->applyArguments($argUsing, $argContent, false);
2965
-                }
2966
-
2967
-                // restore the scope from the @content
2968
-                $this->storeEnv = $content->scope;
2969
-
2970
-                // append the vars from using if any
2971
-                foreach ($varsUsing as $name => $val) {
2972
-                    $this->set($name, $val, true, $this->storeEnv);
2973
-                }
2974
-
2975
-                $this->compileChildrenNoReturn($content->children, $out);
2976
-
2977
-                $this->storeEnv = $storeEnv;
2978
-                break;
2979
-
2980
-            case Type::T_DEBUG:
2981
-                list(, $value) = $child;
2982
-
2983
-                $fname = $this->sourceNames[$this->sourceIndex];
2984
-                $line  = $this->sourceLine;
2985
-                $value = $this->compileDebugValue($value);
2986
-
2987
-                fwrite($this->stderr, "$fname:$line DEBUG: $value\n");
2988
-                break;
2989
-
2990
-            case Type::T_WARN:
2991
-                list(, $value) = $child;
2992
-
2993
-                $fname = $this->sourceNames[$this->sourceIndex];
2994
-                $line  = $this->sourceLine;
2995
-                $value = $this->compileDebugValue($value);
2996
-
2997
-                fwrite($this->stderr, "WARNING: $value\n         on line $line of $fname\n\n");
2998
-                break;
2999
-
3000
-            case Type::T_ERROR:
3001
-                list(, $value) = $child;
3002
-
3003
-                $fname = $this->sourceNames[$this->sourceIndex];
3004
-                $line  = $this->sourceLine;
3005
-                $value = $this->compileValue($this->reduce($value, true));
3006
-
3007
-                throw $this->error("File $fname on line $line ERROR: $value\n");
3008
-
3009
-            case Type::T_CONTROL:
3010
-                throw $this->error('@break/@continue not permitted in this scope');
3011
-
3012
-            default:
3013
-                throw $this->error("unknown child type: $child[0]");
3014
-        }
3015
-    }
3016
-
3017
-    /**
3018
-     * Reduce expression to string
3019
-     *
3020
-     * @param array $exp
3021
-     *
3022
-     * @return array
3023
-     */
3024
-    protected function expToString($exp)
3025
-    {
3026
-        list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
3027
-
3028
-        $content = [$this->reduce($left)];
3029
-
3030
-        if ($whiteLeft) {
3031
-            $content[] = ' ';
3032
-        }
3033
-
3034
-        $content[] = $op;
3035
-
3036
-        if ($whiteRight) {
3037
-            $content[] = ' ';
3038
-        }
3039
-
3040
-        $content[] = $this->reduce($right);
3041
-
3042
-        return [Type::T_STRING, '', $content];
3043
-    }
3044
-
3045
-    /**
3046
-     * Is truthy?
3047
-     *
3048
-     * @param array $value
3049
-     *
3050
-     * @return boolean
3051
-     */
3052
-    protected function isTruthy($value)
3053
-    {
3054
-        return $value !== static::$false && $value !== static::$null;
3055
-    }
3056
-
3057
-    /**
3058
-     * Is the value a direct relationship combinator?
3059
-     *
3060
-     * @param string $value
3061
-     *
3062
-     * @return boolean
3063
-     */
3064
-    protected function isImmediateRelationshipCombinator($value)
3065
-    {
3066
-        return $value === '>' || $value === '+' || $value === '~';
3067
-    }
3068
-
3069
-    /**
3070
-     * Should $value cause its operand to eval
3071
-     *
3072
-     * @param array $value
3073
-     *
3074
-     * @return boolean
3075
-     */
3076
-    protected function shouldEval($value)
3077
-    {
3078
-        switch ($value[0]) {
3079
-            case Type::T_EXPRESSION:
3080
-                if ($value[1] === '/') {
3081
-                    return $this->shouldEval($value[2]) || $this->shouldEval($value[3]);
3082
-                }
3083
-
3084
-                // fall-thru
3085
-            case Type::T_VARIABLE:
3086
-            case Type::T_FUNCTION_CALL:
3087
-                return true;
3088
-        }
3089
-
3090
-        return false;
3091
-    }
3092
-
3093
-    /**
3094
-     * Reduce value
3095
-     *
3096
-     * @param array   $value
3097
-     * @param boolean $inExp
3098
-     *
3099
-     * @return null|string|array|\ScssPhp\ScssPhp\Node\Number
3100
-     */
3101
-    protected function reduce($value, $inExp = false)
3102
-    {
3103
-        if (\is_null($value)) {
3104
-            return null;
3105
-        }
3106
-
3107
-        switch ($value[0]) {
3108
-            case Type::T_EXPRESSION:
3109
-                list(, $op, $left, $right, $inParens) = $value;
3110
-
3111
-                $opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op;
3112
-                $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
3113
-
3114
-                $left = $this->reduce($left, true);
3115
-
3116
-                if ($op !== 'and' && $op !== 'or') {
3117
-                    $right = $this->reduce($right, true);
3118
-                }
3119
-
3120
-                // special case: looks like css shorthand
3121
-                if (
3122
-                    $opName == 'div' && ! $inParens && ! $inExp && isset($right[2]) &&
3123
-                    (($right[0] !== Type::T_NUMBER && $right[2] != '') ||
3124
-                    ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3125
-                ) {
3126
-                    return $this->expToString($value);
3127
-                }
3128
-
3129
-                $left  = $this->coerceForExpression($left);
3130
-                $right = $this->coerceForExpression($right);
3131
-                $ltype = $left[0];
3132
-                $rtype = $right[0];
3133
-
3134
-                $ucOpName = ucfirst($opName);
3135
-                $ucLType  = ucfirst($ltype);
3136
-                $ucRType  = ucfirst($rtype);
3137
-
3138
-                // this tries:
3139
-                // 1. op[op name][left type][right type]
3140
-                // 2. op[left type][right type] (passing the op as first arg
3141
-                // 3. op[op name]
3142
-                $fn = "op${ucOpName}${ucLType}${ucRType}";
3143
-
3144
-                if (
3145
-                    \is_callable([$this, $fn]) ||
3146
-                    (($fn = "op${ucLType}${ucRType}") &&
3147
-                        \is_callable([$this, $fn]) &&
3148
-                        $passOp = true) ||
3149
-                    (($fn = "op${ucOpName}") &&
3150
-                        \is_callable([$this, $fn]) &&
3151
-                        $genOp = true)
3152
-                ) {
3153
-                    $coerceUnit = false;
3154
-
3155
-                    if (
3156
-                        ! isset($genOp) &&
3157
-                        $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
3158
-                    ) {
3159
-                        $coerceUnit = true;
3160
-
3161
-                        switch ($opName) {
3162
-                            case 'mul':
3163
-                                $targetUnit = $left[2];
3164
-
3165
-                                foreach ($right[2] as $unit => $exp) {
3166
-                                    $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
3167
-                                }
3168
-                                break;
3169
-
3170
-                            case 'div':
3171
-                                $targetUnit = $left[2];
3172
-
3173
-                                foreach ($right[2] as $unit => $exp) {
3174
-                                    $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
3175
-                                }
3176
-                                break;
3177
-
3178
-                            case 'mod':
3179
-                                $targetUnit = $left[2];
3180
-                                break;
3181
-
3182
-                            default:
3183
-                                $targetUnit = $left->unitless() ? $right[2] : $left[2];
3184
-                        }
3185
-
3186
-                        $baseUnitLeft = $left->isNormalizable();
3187
-                        $baseUnitRight = $right->isNormalizable();
3188
-
3189
-                        if ($baseUnitLeft && $baseUnitRight && $baseUnitLeft === $baseUnitRight) {
3190
-                            $left = $left->normalize();
3191
-                            $right = $right->normalize();
3192
-                        } elseif ($coerceUnit) {
3193
-                            $left = new Node\Number($left[1], []);
3194
-                        }
3195
-                    }
3196
-
3197
-                    $shouldEval = $inParens || $inExp;
3198
-
3199
-                    if (isset($passOp)) {
3200
-                        $out = $this->$fn($op, $left, $right, $shouldEval);
3201
-                    } else {
3202
-                        $out = $this->$fn($left, $right, $shouldEval);
3203
-                    }
3204
-
3205
-                    if (isset($out)) {
3206
-                        if ($coerceUnit && $out[0] === Type::T_NUMBER) {
3207
-                            $out = $out->coerce($targetUnit);
3208
-                        }
3209
-
3210
-                        return $out;
3211
-                    }
3212
-                }
3213
-
3214
-                return $this->expToString($value);
3215
-
3216
-            case Type::T_UNARY:
3217
-                list(, $op, $exp, $inParens) = $value;
3218
-
3219
-                $inExp = $inExp || $this->shouldEval($exp);
3220
-                $exp = $this->reduce($exp);
3221
-
3222
-                if ($exp[0] === Type::T_NUMBER) {
3223
-                    switch ($op) {
3224
-                        case '+':
3225
-                            return new Node\Number($exp[1], $exp[2]);
3226
-
3227
-                        case '-':
3228
-                            return new Node\Number(-$exp[1], $exp[2]);
3229
-                    }
3230
-                }
3231
-
3232
-                if ($op === 'not') {
3233
-                    if ($inExp || $inParens) {
3234
-                        if ($exp === static::$false || $exp === static::$null) {
3235
-                            return static::$true;
3236
-                        }
3237
-
3238
-                        return static::$false;
3239
-                    }
3240
-
3241
-                    $op = $op . ' ';
3242
-                }
3243
-
3244
-                return [Type::T_STRING, '', [$op, $exp]];
3245
-
3246
-            case Type::T_VARIABLE:
3247
-                return $this->reduce($this->get($value[1]));
3248
-
3249
-            case Type::T_LIST:
3250
-                foreach ($value[2] as &$item) {
3251
-                    $item = $this->reduce($item);
3252
-                }
3253
-
3254
-                return $value;
3255
-
3256
-            case Type::T_MAP:
3257
-                foreach ($value[1] as &$item) {
3258
-                    $item = $this->reduce($item);
3259
-                }
3260
-
3261
-                foreach ($value[2] as &$item) {
3262
-                    $item = $this->reduce($item);
3263
-                }
3264
-
3265
-                return $value;
3266
-
3267
-            case Type::T_STRING:
3268
-                foreach ($value[2] as &$item) {
3269
-                    if (\is_array($item) || $item instanceof \ArrayAccess) {
3270
-                        $item = $this->reduce($item);
3271
-                    }
3272
-                }
3273
-
3274
-                return $value;
3275
-
3276
-            case Type::T_INTERPOLATE:
3277
-                $value[1] = $this->reduce($value[1]);
3278
-
3279
-                if ($inExp) {
3280
-                    return $value[1];
3281
-                }
3282
-
3283
-                return $value;
3284
-
3285
-            case Type::T_FUNCTION_CALL:
3286
-                return $this->fncall($value[1], $value[2]);
3287
-
3288
-            case Type::T_SELF:
3289
-                $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3290
-                $selfSelector = $this->multiplySelectors($this->env, $selfParent);
3291
-                $selfSelector = $this->collapseSelectors($selfSelector, true);
3292
-
3293
-                return $selfSelector;
3294
-
3295
-            default:
3296
-                return $value;
3297
-        }
3298
-    }
3299
-
3300
-    /**
3301
-     * Function caller
3302
-     *
3303
-     * @param string $name
3304
-     * @param array  $argValues
3305
-     *
3306
-     * @return array|null
3307
-     */
3308
-    protected function fncall($functionReference, $argValues)
3309
-    {
3310
-        // a string means this is a static hard reference coming from the parsing
3311
-        if (is_string($functionReference)) {
3312
-            $name = $functionReference;
3313
-
3314
-            $functionReference = $this->getFunctionReference($name);
3315
-            if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3316
-                $functionReference = [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
3317
-            }
3318
-        }
3319
-
3320
-        // a function type means we just want a plain css function call
3321
-        if ($functionReference[0] === Type::T_FUNCTION) {
3322
-            // for CSS functions, simply flatten the arguments into a list
3323
-            $listArgs = [];
3324
-
3325
-            foreach ((array) $argValues as $arg) {
3326
-                if (empty($arg[0]) || count($argValues) === 1) {
3327
-                    $listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1]));
3328
-                }
3329
-            }
3330
-
3331
-            return [Type::T_FUNCTION, $functionReference[1], [Type::T_LIST, ',', $listArgs]];
3332
-        }
3333
-
3334
-        if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3335
-            return static::$defaultValue;
3336
-        }
3337
-
3338
-
3339
-        switch ($functionReference[1]) {
3340
-            // SCSS @function
3341
-            case 'scss':
3342
-                return $this->callScssFunction($functionReference[3], $argValues);
3343
-
3344
-            // native PHP functions
3345
-            case 'user':
3346
-            case 'native':
3347
-                list(,,$name, $fn, $prototype) = $functionReference;
3348
-                $returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues);
3349
-
3350
-                if (! isset($returnValue)) {
3351
-                    return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues);
3352
-                }
3353
-
3354
-                return $returnValue;
3355
-
3356
-            default:
3357
-                return static::$defaultValue;
3358
-        }
3359
-    }
3360
-
3361
-    /**
3362
-     * Reformat fncall arguments to proper css function output
3363
-     * @param $arg
3364
-     * @return array|\ArrayAccess|Node\Number|string|null
3365
-     */
3366
-    protected function stringifyFncallArgs($arg)
3367
-    {
3368
-
3369
-        switch ($arg[0]) {
3370
-            case Type::T_LIST:
3371
-                foreach ($arg[2] as $k => $v) {
3372
-                    $arg[2][$k] = $this->stringifyFncallArgs($v);
3373
-                }
3374
-                break;
3375
-
3376
-            case Type::T_EXPRESSION:
3377
-                if ($arg[1] === '/') {
3378
-                    $arg[2] = $this->stringifyFncallArgs($arg[2]);
3379
-                    $arg[3] = $this->stringifyFncallArgs($arg[3]);
3380
-                    $arg[5] = $arg[6] = false; // no space around /
3381
-                    $arg = $this->expToString($arg);
3382
-                }
3383
-                break;
3384
-
3385
-            case Type::T_FUNCTION_CALL:
3386
-                $name = $arg[1];
3387
-
3388
-                if (in_array($name, ['max', 'min', 'calc'])) {
3389
-                    $args = $arg[2];
3390
-                    $arg = $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $args);
3391
-                }
3392
-                break;
3393
-        }
3394
-
3395
-        return $arg;
3396
-    }
3397
-
3398
-    /**
3399
-     * Find a function reference
3400
-     * @param string $name
3401
-     * @param bool $safeCopy
3402
-     * @return array
3403
-     */
3404
-    protected function getFunctionReference($name, $safeCopy = false)
3405
-    {
3406
-        // SCSS @function
3407
-        if ($func = $this->get(static::$namespaces['function'] . $name, false)) {
3408
-            if ($safeCopy) {
3409
-                $func = clone $func;
3410
-            }
3411
-
3412
-            return [Type::T_FUNCTION_REFERENCE, 'scss', $name, $func];
3413
-        }
3414
-
3415
-        // native PHP functions
3416
-
3417
-        // try to find a native lib function
3418
-        $normalizedName = $this->normalizeName($name);
3419
-        $libName = null;
3420
-
3421
-        if (isset($this->userFunctions[$normalizedName])) {
3422
-            // see if we can find a user function
3423
-            list($f, $prototype) = $this->userFunctions[$normalizedName];
3424
-
3425
-            return [Type::T_FUNCTION_REFERENCE, 'user', $name, $f, $prototype];
3426
-        }
3427
-
3428
-        if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) {
3429
-            $libName   = $f[1];
3430
-            $prototype = isset(static::$$libName) ? static::$$libName : null;
3431
-
3432
-            return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
3433
-        }
3434
-
3435
-        return static::$null;
3436
-    }
3437
-
3438
-
3439
-    /**
3440
-     * Normalize name
3441
-     *
3442
-     * @param string $name
3443
-     *
3444
-     * @return string
3445
-     */
3446
-    protected function normalizeName($name)
3447
-    {
3448
-        return str_replace('-', '_', $name);
3449
-    }
3450
-
3451
-    /**
3452
-     * Normalize value
3453
-     *
3454
-     * @param array $value
3455
-     *
3456
-     * @return array
3457
-     */
3458
-    public function normalizeValue($value)
3459
-    {
3460
-        $value = $this->coerceForExpression($this->reduce($value));
3461
-
3462
-        switch ($value[0]) {
3463
-            case Type::T_LIST:
3464
-                $value = $this->extractInterpolation($value);
3465
-
3466
-                if ($value[0] !== Type::T_LIST) {
3467
-                    return [Type::T_KEYWORD, $this->compileValue($value)];
3468
-                }
3469
-
3470
-                foreach ($value[2] as $key => $item) {
3471
-                    $value[2][$key] = $this->normalizeValue($item);
3472
-                }
3473
-
3474
-                if (! empty($value['enclosing'])) {
3475
-                    unset($value['enclosing']);
3476
-                }
3477
-
3478
-                return $value;
3479
-
3480
-            case Type::T_STRING:
3481
-                return [$value[0], '"', [$this->compileStringContent($value)]];
3482
-
3483
-            case Type::T_NUMBER:
3484
-                return $value->normalize();
3485
-
3486
-            case Type::T_INTERPOLATE:
3487
-                return [Type::T_KEYWORD, $this->compileValue($value)];
3488
-
3489
-            default:
3490
-                return $value;
3491
-        }
3492
-    }
3493
-
3494
-    /**
3495
-     * Add numbers
3496
-     *
3497
-     * @param array $left
3498
-     * @param array $right
3499
-     *
3500
-     * @return \ScssPhp\ScssPhp\Node\Number
3501
-     */
3502
-    protected function opAddNumberNumber($left, $right)
3503
-    {
3504
-        return new Node\Number($left[1] + $right[1], $left[2]);
3505
-    }
3506
-
3507
-    /**
3508
-     * Multiply numbers
3509
-     *
3510
-     * @param array $left
3511
-     * @param array $right
3512
-     *
3513
-     * @return \ScssPhp\ScssPhp\Node\Number
3514
-     */
3515
-    protected function opMulNumberNumber($left, $right)
3516
-    {
3517
-        return new Node\Number($left[1] * $right[1], $left[2]);
3518
-    }
3519
-
3520
-    /**
3521
-     * Subtract numbers
3522
-     *
3523
-     * @param array $left
3524
-     * @param array $right
3525
-     *
3526
-     * @return \ScssPhp\ScssPhp\Node\Number
3527
-     */
3528
-    protected function opSubNumberNumber($left, $right)
3529
-    {
3530
-        return new Node\Number($left[1] - $right[1], $left[2]);
3531
-    }
3532
-
3533
-    /**
3534
-     * Divide numbers
3535
-     *
3536
-     * @param array $left
3537
-     * @param array $right
3538
-     *
3539
-     * @return array|\ScssPhp\ScssPhp\Node\Number
3540
-     */
3541
-    protected function opDivNumberNumber($left, $right)
3542
-    {
3543
-        if ($right[1] == 0) {
3544
-            return ($left[1] == 0) ? static::$NaN : static::$Infinity;
3545
-        }
3546
-
3547
-        return new Node\Number($left[1] / $right[1], $left[2]);
3548
-    }
3549
-
3550
-    /**
3551
-     * Mod numbers
3552
-     *
3553
-     * @param array $left
3554
-     * @param array $right
3555
-     *
3556
-     * @return \ScssPhp\ScssPhp\Node\Number
3557
-     */
3558
-    protected function opModNumberNumber($left, $right)
3559
-    {
3560
-        if ($right[1] == 0) {
3561
-            return static::$NaN;
3562
-        }
3563
-
3564
-        return new Node\Number($left[1] % $right[1], $left[2]);
3565
-    }
3566
-
3567
-    /**
3568
-     * Add strings
3569
-     *
3570
-     * @param array $left
3571
-     * @param array $right
3572
-     *
3573
-     * @return array|null
3574
-     */
3575
-    protected function opAdd($left, $right)
3576
-    {
3577
-        if ($strLeft = $this->coerceString($left)) {
3578
-            if ($right[0] === Type::T_STRING) {
3579
-                $right[1] = '';
3580
-            }
3581
-
3582
-            $strLeft[2][] = $right;
3583
-
3584
-            return $strLeft;
3585
-        }
3586
-
3587
-        if ($strRight = $this->coerceString($right)) {
3588
-            if ($left[0] === Type::T_STRING) {
3589
-                $left[1] = '';
3590
-            }
3591
-
3592
-            array_unshift($strRight[2], $left);
3593
-
3594
-            return $strRight;
3595
-        }
3596
-
3597
-        return null;
3598
-    }
3599
-
3600
-    /**
3601
-     * Boolean and
3602
-     *
3603
-     * @param array   $left
3604
-     * @param array   $right
3605
-     * @param boolean $shouldEval
3606
-     *
3607
-     * @return array|null
3608
-     */
3609
-    protected function opAnd($left, $right, $shouldEval)
3610
-    {
3611
-        $truthy = ($left === static::$null || $right === static::$null) ||
3612
-                  ($left === static::$false || $left === static::$true) &&
3613
-                  ($right === static::$false || $right === static::$true);
3614
-
3615
-        if (! $shouldEval) {
3616
-            if (! $truthy) {
3617
-                return null;
3618
-            }
3619
-        }
3620
-
3621
-        if ($left !== static::$false && $left !== static::$null) {
3622
-            return $this->reduce($right, true);
3623
-        }
3624
-
3625
-        return $left;
3626
-    }
3627
-
3628
-    /**
3629
-     * Boolean or
3630
-     *
3631
-     * @param array   $left
3632
-     * @param array   $right
3633
-     * @param boolean $shouldEval
3634
-     *
3635
-     * @return array|null
3636
-     */
3637
-    protected function opOr($left, $right, $shouldEval)
3638
-    {
3639
-        $truthy = ($left === static::$null || $right === static::$null) ||
3640
-                  ($left === static::$false || $left === static::$true) &&
3641
-                  ($right === static::$false || $right === static::$true);
3642
-
3643
-        if (! $shouldEval) {
3644
-            if (! $truthy) {
3645
-                return null;
3646
-            }
3647
-        }
3648
-
3649
-        if ($left !== static::$false && $left !== static::$null) {
3650
-            return $left;
3651
-        }
3652
-
3653
-        return $this->reduce($right, true);
3654
-    }
3655
-
3656
-    /**
3657
-     * Compare colors
3658
-     *
3659
-     * @param string $op
3660
-     * @param array  $left
3661
-     * @param array  $right
3662
-     *
3663
-     * @return array
3664
-     */
3665
-    protected function opColorColor($op, $left, $right)
3666
-    {
3667
-        $out = [Type::T_COLOR];
3668
-
3669
-        foreach ([1, 2, 3] as $i) {
3670
-            $lval = isset($left[$i]) ? $left[$i] : 0;
3671
-            $rval = isset($right[$i]) ? $right[$i] : 0;
3672
-
3673
-            switch ($op) {
3674
-                case '+':
3675
-                    $out[] = $lval + $rval;
3676
-                    break;
3677
-
3678
-                case '-':
3679
-                    $out[] = $lval - $rval;
3680
-                    break;
3681
-
3682
-                case '*':
3683
-                    $out[] = $lval * $rval;
3684
-                    break;
3685
-
3686
-                case '%':
3687
-                    if ($rval == 0) {
3688
-                        throw $this->error("color: Can't take modulo by zero");
3689
-                    }
3690
-
3691
-                    $out[] = $lval % $rval;
3692
-                    break;
3693
-
3694
-                case '/':
3695
-                    if ($rval == 0) {
3696
-                        throw $this->error("color: Can't divide by zero");
3697
-                    }
3698
-
3699
-                    $out[] = (int) ($lval / $rval);
3700
-                    break;
3701
-
3702
-                case '==':
3703
-                    return $this->opEq($left, $right);
3704
-
3705
-                case '!=':
3706
-                    return $this->opNeq($left, $right);
3707
-
3708
-                default:
3709
-                    throw $this->error("color: unknown op $op");
3710
-            }
3711
-        }
3712
-
3713
-        if (isset($left[4])) {
3714
-            $out[4] = $left[4];
3715
-        } elseif (isset($right[4])) {
3716
-            $out[4] = $right[4];
3717
-        }
3718
-
3719
-        return $this->fixColor($out);
3720
-    }
3721
-
3722
-    /**
3723
-     * Compare color and number
3724
-     *
3725
-     * @param string $op
3726
-     * @param array  $left
3727
-     * @param array  $right
3728
-     *
3729
-     * @return array
3730
-     */
3731
-    protected function opColorNumber($op, $left, $right)
3732
-    {
3733
-        $value = $right[1];
3734
-
3735
-        return $this->opColorColor(
3736
-            $op,
3737
-            $left,
3738
-            [Type::T_COLOR, $value, $value, $value]
3739
-        );
3740
-    }
3741
-
3742
-    /**
3743
-     * Compare number and color
3744
-     *
3745
-     * @param string $op
3746
-     * @param array  $left
3747
-     * @param array  $right
3748
-     *
3749
-     * @return array
3750
-     */
3751
-    protected function opNumberColor($op, $left, $right)
3752
-    {
3753
-        $value = $left[1];
3754
-
3755
-        return $this->opColorColor(
3756
-            $op,
3757
-            [Type::T_COLOR, $value, $value, $value],
3758
-            $right
3759
-        );
3760
-    }
3761
-
3762
-    /**
3763
-     * Compare number1 == number2
3764
-     *
3765
-     * @param array $left
3766
-     * @param array $right
3767
-     *
3768
-     * @return array
3769
-     */
3770
-    protected function opEq($left, $right)
3771
-    {
3772
-        if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
3773
-            $lStr[1] = '';
3774
-            $rStr[1] = '';
3775
-
3776
-            $left = $this->compileValue($lStr);
3777
-            $right = $this->compileValue($rStr);
3778
-        }
3779
-
3780
-        return $this->toBool($left === $right);
3781
-    }
3782
-
3783
-    /**
3784
-     * Compare number1 != number2
3785
-     *
3786
-     * @param array $left
3787
-     * @param array $right
3788
-     *
3789
-     * @return array
3790
-     */
3791
-    protected function opNeq($left, $right)
3792
-    {
3793
-        if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
3794
-            $lStr[1] = '';
3795
-            $rStr[1] = '';
3796
-
3797
-            $left = $this->compileValue($lStr);
3798
-            $right = $this->compileValue($rStr);
3799
-        }
3800
-
3801
-        return $this->toBool($left !== $right);
3802
-    }
3803
-
3804
-    /**
3805
-     * Compare number1 >= number2
3806
-     *
3807
-     * @param array $left
3808
-     * @param array $right
3809
-     *
3810
-     * @return array
3811
-     */
3812
-    protected function opGteNumberNumber($left, $right)
3813
-    {
3814
-        return $this->toBool($left[1] >= $right[1]);
3815
-    }
3816
-
3817
-    /**
3818
-     * Compare number1 > number2
3819
-     *
3820
-     * @param array $left
3821
-     * @param array $right
3822
-     *
3823
-     * @return array
3824
-     */
3825
-    protected function opGtNumberNumber($left, $right)
3826
-    {
3827
-        return $this->toBool($left[1] > $right[1]);
3828
-    }
3829
-
3830
-    /**
3831
-     * Compare number1 <= number2
3832
-     *
3833
-     * @param array $left
3834
-     * @param array $right
3835
-     *
3836
-     * @return array
3837
-     */
3838
-    protected function opLteNumberNumber($left, $right)
3839
-    {
3840
-        return $this->toBool($left[1] <= $right[1]);
3841
-    }
3842
-
3843
-    /**
3844
-     * Compare number1 < number2
3845
-     *
3846
-     * @param array $left
3847
-     * @param array $right
3848
-     *
3849
-     * @return array
3850
-     */
3851
-    protected function opLtNumberNumber($left, $right)
3852
-    {
3853
-        return $this->toBool($left[1] < $right[1]);
3854
-    }
3855
-
3856
-    /**
3857
-     * Three-way comparison, aka spaceship operator
3858
-     *
3859
-     * @param array $left
3860
-     * @param array $right
3861
-     *
3862
-     * @return \ScssPhp\ScssPhp\Node\Number
3863
-     */
3864
-    protected function opCmpNumberNumber($left, $right)
3865
-    {
3866
-        $n = $left[1] - $right[1];
3867
-
3868
-        return new Node\Number($n ? $n / abs($n) : 0, '');
3869
-    }
3870
-
3871
-    /**
3872
-     * Cast to boolean
3873
-     *
3874
-     * @api
3875
-     *
3876
-     * @param mixed $thing
3877
-     *
3878
-     * @return array
3879
-     */
3880
-    public function toBool($thing)
3881
-    {
3882
-        return $thing ? static::$true : static::$false;
3883
-    }
3884
-
3885
-    /**
3886
-     * Compiles a primitive value into a CSS property value.
3887
-     *
3888
-     * Values in scssphp are typed by being wrapped in arrays, their format is
3889
-     * typically:
3890
-     *
3891
-     *     array(type, contents [, additional_contents]*)
3892
-     *
3893
-     * The input is expected to be reduced. This function will not work on
3894
-     * things like expressions and variables.
3895
-     *
3896
-     * @api
3897
-     *
3898
-     * @param array $value
3899
-     *
3900
-     * @return string|array
3901
-     */
3902
-    public function compileValue($value)
3903
-    {
3904
-        $value = $this->reduce($value);
3905
-
3906
-        switch ($value[0]) {
3907
-            case Type::T_KEYWORD:
3908
-                return $value[1];
3909
-
3910
-            case Type::T_COLOR:
3911
-                // [1] - red component (either number for a %)
3912
-                // [2] - green component
3913
-                // [3] - blue component
3914
-                // [4] - optional alpha component
3915
-                list(, $r, $g, $b) = $value;
3916
-
3917
-                $r = $this->compileRGBAValue($r);
3918
-                $g = $this->compileRGBAValue($g);
3919
-                $b = $this->compileRGBAValue($b);
3920
-
3921
-                if (\count($value) === 5) {
3922
-                    $alpha = $this->compileRGBAValue($value[4], true);
3923
-
3924
-                    if (! is_numeric($alpha) || $alpha < 1) {
3925
-                        $colorName = Colors::RGBaToColorName($r, $g, $b, $alpha);
3926
-
3927
-                        if (! \is_null($colorName)) {
3928
-                            return $colorName;
3929
-                        }
3930
-
3931
-                        if (is_numeric($alpha)) {
3932
-                            $a = new Node\Number($alpha, '');
3933
-                        } else {
3934
-                            $a = $alpha;
3935
-                        }
3936
-
3937
-                        return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $a . ')';
3938
-                    }
3939
-                }
3940
-
3941
-                if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b)) {
3942
-                    return 'rgb(' . $r . ', ' . $g . ', ' . $b . ')';
3943
-                }
3944
-
3945
-                $colorName = Colors::RGBaToColorName($r, $g, $b);
3946
-
3947
-                if (! \is_null($colorName)) {
3948
-                    return $colorName;
3949
-                }
3950
-
3951
-                $h = sprintf('#%02x%02x%02x', $r, $g, $b);
3952
-
3953
-                // Converting hex color to short notation (e.g. #003399 to #039)
3954
-                if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
3955
-                    $h = '#' . $h[1] . $h[3] . $h[5];
3956
-                }
3957
-
3958
-                return $h;
3959
-
3960
-            case Type::T_NUMBER:
3961
-                return $value->output($this);
3962
-
3963
-            case Type::T_STRING:
3964
-                $content = $this->compileStringContent($value);
3965
-
3966
-                if ($value[1]) {
3967
-                    // force double quote as string quote for the output in certain cases
3968
-                    if (
3969
-                        $value[1] === "'" &&
3970
-                        strpos($content, '"') === false &&
3971
-                        strpbrk($content, '{}') !== false
3972
-                    ) {
3973
-                        $value[1] = '"';
3974
-                    }
3975
-                    $content = str_replace(
3976
-                        array('\\a', "\n", "\f" , '\\'  , "\r" , $value[1]),
3977
-                        array("\r" , ' ' , '\\f', '\\\\', '\\a', '\\' . $value[1]),
3978
-                        $content
3979
-                    );
3980
-                }
3981
-
3982
-                return $value[1] . $content . $value[1];
3983
-
3984
-            case Type::T_FUNCTION:
3985
-                $args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
3986
-
3987
-                return "$value[1]($args)";
3988
-
3989
-            case Type::T_FUNCTION_REFERENCE:
3990
-                $name = ! empty($value[2]) ? $value[2] : '';
3991
-
3992
-                return "get-function(\"$name\")";
3993
-
3994
-            case Type::T_LIST:
3995
-                $value = $this->extractInterpolation($value);
3996
-
3997
-                if ($value[0] !== Type::T_LIST) {
3998
-                    return $this->compileValue($value);
3999
-                }
2961
+				if (isset($argUsing) && isset($argContent)) {
2962
+					// Get the arguments provided for the content with the names provided in the "using" argument list
2963
+					$this->storeEnv = null;
2964
+					$varsUsing = $this->applyArguments($argUsing, $argContent, false);
2965
+				}
2966
+
2967
+				// restore the scope from the @content
2968
+				$this->storeEnv = $content->scope;
2969
+
2970
+				// append the vars from using if any
2971
+				foreach ($varsUsing as $name => $val) {
2972
+					$this->set($name, $val, true, $this->storeEnv);
2973
+				}
2974
+
2975
+				$this->compileChildrenNoReturn($content->children, $out);
2976
+
2977
+				$this->storeEnv = $storeEnv;
2978
+				break;
2979
+
2980
+			case Type::T_DEBUG:
2981
+				list(, $value) = $child;
2982
+
2983
+				$fname = $this->sourceNames[$this->sourceIndex];
2984
+				$line  = $this->sourceLine;
2985
+				$value = $this->compileDebugValue($value);
2986
+
2987
+				fwrite($this->stderr, "$fname:$line DEBUG: $value\n");
2988
+				break;
2989
+
2990
+			case Type::T_WARN:
2991
+				list(, $value) = $child;
2992
+
2993
+				$fname = $this->sourceNames[$this->sourceIndex];
2994
+				$line  = $this->sourceLine;
2995
+				$value = $this->compileDebugValue($value);
2996
+
2997
+				fwrite($this->stderr, "WARNING: $value\n         on line $line of $fname\n\n");
2998
+				break;
2999
+
3000
+			case Type::T_ERROR:
3001
+				list(, $value) = $child;
3002
+
3003
+				$fname = $this->sourceNames[$this->sourceIndex];
3004
+				$line  = $this->sourceLine;
3005
+				$value = $this->compileValue($this->reduce($value, true));
3006
+
3007
+				throw $this->error("File $fname on line $line ERROR: $value\n");
3008
+
3009
+			case Type::T_CONTROL:
3010
+				throw $this->error('@break/@continue not permitted in this scope');
3011
+
3012
+			default:
3013
+				throw $this->error("unknown child type: $child[0]");
3014
+		}
3015
+	}
3016
+
3017
+	/**
3018
+	 * Reduce expression to string
3019
+	 *
3020
+	 * @param array $exp
3021
+	 *
3022
+	 * @return array
3023
+	 */
3024
+	protected function expToString($exp)
3025
+	{
3026
+		list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
3027
+
3028
+		$content = [$this->reduce($left)];
3029
+
3030
+		if ($whiteLeft) {
3031
+			$content[] = ' ';
3032
+		}
3033
+
3034
+		$content[] = $op;
3035
+
3036
+		if ($whiteRight) {
3037
+			$content[] = ' ';
3038
+		}
3039
+
3040
+		$content[] = $this->reduce($right);
3041
+
3042
+		return [Type::T_STRING, '', $content];
3043
+	}
3044
+
3045
+	/**
3046
+	 * Is truthy?
3047
+	 *
3048
+	 * @param array $value
3049
+	 *
3050
+	 * @return boolean
3051
+	 */
3052
+	protected function isTruthy($value)
3053
+	{
3054
+		return $value !== static::$false && $value !== static::$null;
3055
+	}
3056
+
3057
+	/**
3058
+	 * Is the value a direct relationship combinator?
3059
+	 *
3060
+	 * @param string $value
3061
+	 *
3062
+	 * @return boolean
3063
+	 */
3064
+	protected function isImmediateRelationshipCombinator($value)
3065
+	{
3066
+		return $value === '>' || $value === '+' || $value === '~';
3067
+	}
3068
+
3069
+	/**
3070
+	 * Should $value cause its operand to eval
3071
+	 *
3072
+	 * @param array $value
3073
+	 *
3074
+	 * @return boolean
3075
+	 */
3076
+	protected function shouldEval($value)
3077
+	{
3078
+		switch ($value[0]) {
3079
+			case Type::T_EXPRESSION:
3080
+				if ($value[1] === '/') {
3081
+					return $this->shouldEval($value[2]) || $this->shouldEval($value[3]);
3082
+				}
3083
+
3084
+				// fall-thru
3085
+			case Type::T_VARIABLE:
3086
+			case Type::T_FUNCTION_CALL:
3087
+				return true;
3088
+		}
3089
+
3090
+		return false;
3091
+	}
3092
+
3093
+	/**
3094
+	 * Reduce value
3095
+	 *
3096
+	 * @param array   $value
3097
+	 * @param boolean $inExp
3098
+	 *
3099
+	 * @return null|string|array|\ScssPhp\ScssPhp\Node\Number
3100
+	 */
3101
+	protected function reduce($value, $inExp = false)
3102
+	{
3103
+		if (\is_null($value)) {
3104
+			return null;
3105
+		}
3106
+
3107
+		switch ($value[0]) {
3108
+			case Type::T_EXPRESSION:
3109
+				list(, $op, $left, $right, $inParens) = $value;
3110
+
3111
+				$opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op;
3112
+				$inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
3113
+
3114
+				$left = $this->reduce($left, true);
3115
+
3116
+				if ($op !== 'and' && $op !== 'or') {
3117
+					$right = $this->reduce($right, true);
3118
+				}
3119
+
3120
+				// special case: looks like css shorthand
3121
+				if (
3122
+					$opName == 'div' && ! $inParens && ! $inExp && isset($right[2]) &&
3123
+					(($right[0] !== Type::T_NUMBER && $right[2] != '') ||
3124
+					($right[0] === Type::T_NUMBER && ! $right->unitless()))
3125
+				) {
3126
+					return $this->expToString($value);
3127
+				}
3128
+
3129
+				$left  = $this->coerceForExpression($left);
3130
+				$right = $this->coerceForExpression($right);
3131
+				$ltype = $left[0];
3132
+				$rtype = $right[0];
3133
+
3134
+				$ucOpName = ucfirst($opName);
3135
+				$ucLType  = ucfirst($ltype);
3136
+				$ucRType  = ucfirst($rtype);
3137
+
3138
+				// this tries:
3139
+				// 1. op[op name][left type][right type]
3140
+				// 2. op[left type][right type] (passing the op as first arg
3141
+				// 3. op[op name]
3142
+				$fn = "op${ucOpName}${ucLType}${ucRType}";
3143
+
3144
+				if (
3145
+					\is_callable([$this, $fn]) ||
3146
+					(($fn = "op${ucLType}${ucRType}") &&
3147
+						\is_callable([$this, $fn]) &&
3148
+						$passOp = true) ||
3149
+					(($fn = "op${ucOpName}") &&
3150
+						\is_callable([$this, $fn]) &&
3151
+						$genOp = true)
3152
+				) {
3153
+					$coerceUnit = false;
3154
+
3155
+					if (
3156
+						! isset($genOp) &&
3157
+						$left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
3158
+					) {
3159
+						$coerceUnit = true;
3160
+
3161
+						switch ($opName) {
3162
+							case 'mul':
3163
+								$targetUnit = $left[2];
3164
+
3165
+								foreach ($right[2] as $unit => $exp) {
3166
+									$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
3167
+								}
3168
+								break;
3169
+
3170
+							case 'div':
3171
+								$targetUnit = $left[2];
3172
+
3173
+								foreach ($right[2] as $unit => $exp) {
3174
+									$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
3175
+								}
3176
+								break;
3177
+
3178
+							case 'mod':
3179
+								$targetUnit = $left[2];
3180
+								break;
3181
+
3182
+							default:
3183
+								$targetUnit = $left->unitless() ? $right[2] : $left[2];
3184
+						}
3185
+
3186
+						$baseUnitLeft = $left->isNormalizable();
3187
+						$baseUnitRight = $right->isNormalizable();
3188
+
3189
+						if ($baseUnitLeft && $baseUnitRight && $baseUnitLeft === $baseUnitRight) {
3190
+							$left = $left->normalize();
3191
+							$right = $right->normalize();
3192
+						} elseif ($coerceUnit) {
3193
+							$left = new Node\Number($left[1], []);
3194
+						}
3195
+					}
3196
+
3197
+					$shouldEval = $inParens || $inExp;
3198
+
3199
+					if (isset($passOp)) {
3200
+						$out = $this->$fn($op, $left, $right, $shouldEval);
3201
+					} else {
3202
+						$out = $this->$fn($left, $right, $shouldEval);
3203
+					}
3204
+
3205
+					if (isset($out)) {
3206
+						if ($coerceUnit && $out[0] === Type::T_NUMBER) {
3207
+							$out = $out->coerce($targetUnit);
3208
+						}
3209
+
3210
+						return $out;
3211
+					}
3212
+				}
3213
+
3214
+				return $this->expToString($value);
3215
+
3216
+			case Type::T_UNARY:
3217
+				list(, $op, $exp, $inParens) = $value;
3218
+
3219
+				$inExp = $inExp || $this->shouldEval($exp);
3220
+				$exp = $this->reduce($exp);
3221
+
3222
+				if ($exp[0] === Type::T_NUMBER) {
3223
+					switch ($op) {
3224
+						case '+':
3225
+							return new Node\Number($exp[1], $exp[2]);
3226
+
3227
+						case '-':
3228
+							return new Node\Number(-$exp[1], $exp[2]);
3229
+					}
3230
+				}
3231
+
3232
+				if ($op === 'not') {
3233
+					if ($inExp || $inParens) {
3234
+						if ($exp === static::$false || $exp === static::$null) {
3235
+							return static::$true;
3236
+						}
3237
+
3238
+						return static::$false;
3239
+					}
3240
+
3241
+					$op = $op . ' ';
3242
+				}
3243
+
3244
+				return [Type::T_STRING, '', [$op, $exp]];
3245
+
3246
+			case Type::T_VARIABLE:
3247
+				return $this->reduce($this->get($value[1]));
3248
+
3249
+			case Type::T_LIST:
3250
+				foreach ($value[2] as &$item) {
3251
+					$item = $this->reduce($item);
3252
+				}
3253
+
3254
+				return $value;
3255
+
3256
+			case Type::T_MAP:
3257
+				foreach ($value[1] as &$item) {
3258
+					$item = $this->reduce($item);
3259
+				}
3260
+
3261
+				foreach ($value[2] as &$item) {
3262
+					$item = $this->reduce($item);
3263
+				}
3264
+
3265
+				return $value;
3266
+
3267
+			case Type::T_STRING:
3268
+				foreach ($value[2] as &$item) {
3269
+					if (\is_array($item) || $item instanceof \ArrayAccess) {
3270
+						$item = $this->reduce($item);
3271
+					}
3272
+				}
3273
+
3274
+				return $value;
3275
+
3276
+			case Type::T_INTERPOLATE:
3277
+				$value[1] = $this->reduce($value[1]);
3278
+
3279
+				if ($inExp) {
3280
+					return $value[1];
3281
+				}
3282
+
3283
+				return $value;
3284
+
3285
+			case Type::T_FUNCTION_CALL:
3286
+				return $this->fncall($value[1], $value[2]);
3287
+
3288
+			case Type::T_SELF:
3289
+				$selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3290
+				$selfSelector = $this->multiplySelectors($this->env, $selfParent);
3291
+				$selfSelector = $this->collapseSelectors($selfSelector, true);
3292
+
3293
+				return $selfSelector;
3294
+
3295
+			default:
3296
+				return $value;
3297
+		}
3298
+	}
3299
+
3300
+	/**
3301
+	 * Function caller
3302
+	 *
3303
+	 * @param string $name
3304
+	 * @param array  $argValues
3305
+	 *
3306
+	 * @return array|null
3307
+	 */
3308
+	protected function fncall($functionReference, $argValues)
3309
+	{
3310
+		// a string means this is a static hard reference coming from the parsing
3311
+		if (is_string($functionReference)) {
3312
+			$name = $functionReference;
3313
+
3314
+			$functionReference = $this->getFunctionReference($name);
3315
+			if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3316
+				$functionReference = [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
3317
+			}
3318
+		}
3319
+
3320
+		// a function type means we just want a plain css function call
3321
+		if ($functionReference[0] === Type::T_FUNCTION) {
3322
+			// for CSS functions, simply flatten the arguments into a list
3323
+			$listArgs = [];
3324
+
3325
+			foreach ((array) $argValues as $arg) {
3326
+				if (empty($arg[0]) || count($argValues) === 1) {
3327
+					$listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1]));
3328
+				}
3329
+			}
3330
+
3331
+			return [Type::T_FUNCTION, $functionReference[1], [Type::T_LIST, ',', $listArgs]];
3332
+		}
3333
+
3334
+		if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
3335
+			return static::$defaultValue;
3336
+		}
3337
+
3338
+
3339
+		switch ($functionReference[1]) {
3340
+			// SCSS @function
3341
+			case 'scss':
3342
+				return $this->callScssFunction($functionReference[3], $argValues);
3343
+
3344
+			// native PHP functions
3345
+			case 'user':
3346
+			case 'native':
3347
+				list(,,$name, $fn, $prototype) = $functionReference;
3348
+				$returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues);
3349
+
3350
+				if (! isset($returnValue)) {
3351
+					return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues);
3352
+				}
3353
+
3354
+				return $returnValue;
3355
+
3356
+			default:
3357
+				return static::$defaultValue;
3358
+		}
3359
+	}
3360
+
3361
+	/**
3362
+	 * Reformat fncall arguments to proper css function output
3363
+	 * @param $arg
3364
+	 * @return array|\ArrayAccess|Node\Number|string|null
3365
+	 */
3366
+	protected function stringifyFncallArgs($arg)
3367
+	{
3368
+
3369
+		switch ($arg[0]) {
3370
+			case Type::T_LIST:
3371
+				foreach ($arg[2] as $k => $v) {
3372
+					$arg[2][$k] = $this->stringifyFncallArgs($v);
3373
+				}
3374
+				break;
3375
+
3376
+			case Type::T_EXPRESSION:
3377
+				if ($arg[1] === '/') {
3378
+					$arg[2] = $this->stringifyFncallArgs($arg[2]);
3379
+					$arg[3] = $this->stringifyFncallArgs($arg[3]);
3380
+					$arg[5] = $arg[6] = false; // no space around /
3381
+					$arg = $this->expToString($arg);
3382
+				}
3383
+				break;
3384
+
3385
+			case Type::T_FUNCTION_CALL:
3386
+				$name = $arg[1];
3387
+
3388
+				if (in_array($name, ['max', 'min', 'calc'])) {
3389
+					$args = $arg[2];
3390
+					$arg = $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $args);
3391
+				}
3392
+				break;
3393
+		}
3394
+
3395
+		return $arg;
3396
+	}
3397
+
3398
+	/**
3399
+	 * Find a function reference
3400
+	 * @param string $name
3401
+	 * @param bool $safeCopy
3402
+	 * @return array
3403
+	 */
3404
+	protected function getFunctionReference($name, $safeCopy = false)
3405
+	{
3406
+		// SCSS @function
3407
+		if ($func = $this->get(static::$namespaces['function'] . $name, false)) {
3408
+			if ($safeCopy) {
3409
+				$func = clone $func;
3410
+			}
3411
+
3412
+			return [Type::T_FUNCTION_REFERENCE, 'scss', $name, $func];
3413
+		}
3414
+
3415
+		// native PHP functions
3416
+
3417
+		// try to find a native lib function
3418
+		$normalizedName = $this->normalizeName($name);
3419
+		$libName = null;
3420
+
3421
+		if (isset($this->userFunctions[$normalizedName])) {
3422
+			// see if we can find a user function
3423
+			list($f, $prototype) = $this->userFunctions[$normalizedName];
3424
+
3425
+			return [Type::T_FUNCTION_REFERENCE, 'user', $name, $f, $prototype];
3426
+		}
3427
+
3428
+		if (($f = $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) {
3429
+			$libName   = $f[1];
3430
+			$prototype = isset(static::$$libName) ? static::$$libName : null;
3431
+
3432
+			return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
3433
+		}
3434
+
3435
+		return static::$null;
3436
+	}
3437
+
3438
+
3439
+	/**
3440
+	 * Normalize name
3441
+	 *
3442
+	 * @param string $name
3443
+	 *
3444
+	 * @return string
3445
+	 */
3446
+	protected function normalizeName($name)
3447
+	{
3448
+		return str_replace('-', '_', $name);
3449
+	}
3450
+
3451
+	/**
3452
+	 * Normalize value
3453
+	 *
3454
+	 * @param array $value
3455
+	 *
3456
+	 * @return array
3457
+	 */
3458
+	public function normalizeValue($value)
3459
+	{
3460
+		$value = $this->coerceForExpression($this->reduce($value));
3461
+
3462
+		switch ($value[0]) {
3463
+			case Type::T_LIST:
3464
+				$value = $this->extractInterpolation($value);
3465
+
3466
+				if ($value[0] !== Type::T_LIST) {
3467
+					return [Type::T_KEYWORD, $this->compileValue($value)];
3468
+				}
3469
+
3470
+				foreach ($value[2] as $key => $item) {
3471
+					$value[2][$key] = $this->normalizeValue($item);
3472
+				}
3473
+
3474
+				if (! empty($value['enclosing'])) {
3475
+					unset($value['enclosing']);
3476
+				}
3477
+
3478
+				return $value;
3479
+
3480
+			case Type::T_STRING:
3481
+				return [$value[0], '"', [$this->compileStringContent($value)]];
3482
+
3483
+			case Type::T_NUMBER:
3484
+				return $value->normalize();
3485
+
3486
+			case Type::T_INTERPOLATE:
3487
+				return [Type::T_KEYWORD, $this->compileValue($value)];
3488
+
3489
+			default:
3490
+				return $value;
3491
+		}
3492
+	}
3493
+
3494
+	/**
3495
+	 * Add numbers
3496
+	 *
3497
+	 * @param array $left
3498
+	 * @param array $right
3499
+	 *
3500
+	 * @return \ScssPhp\ScssPhp\Node\Number
3501
+	 */
3502
+	protected function opAddNumberNumber($left, $right)
3503
+	{
3504
+		return new Node\Number($left[1] + $right[1], $left[2]);
3505
+	}
3506
+
3507
+	/**
3508
+	 * Multiply numbers
3509
+	 *
3510
+	 * @param array $left
3511
+	 * @param array $right
3512
+	 *
3513
+	 * @return \ScssPhp\ScssPhp\Node\Number
3514
+	 */
3515
+	protected function opMulNumberNumber($left, $right)
3516
+	{
3517
+		return new Node\Number($left[1] * $right[1], $left[2]);
3518
+	}
3519
+
3520
+	/**
3521
+	 * Subtract numbers
3522
+	 *
3523
+	 * @param array $left
3524
+	 * @param array $right
3525
+	 *
3526
+	 * @return \ScssPhp\ScssPhp\Node\Number
3527
+	 */
3528
+	protected function opSubNumberNumber($left, $right)
3529
+	{
3530
+		return new Node\Number($left[1] - $right[1], $left[2]);
3531
+	}
3532
+
3533
+	/**
3534
+	 * Divide numbers
3535
+	 *
3536
+	 * @param array $left
3537
+	 * @param array $right
3538
+	 *
3539
+	 * @return array|\ScssPhp\ScssPhp\Node\Number
3540
+	 */
3541
+	protected function opDivNumberNumber($left, $right)
3542
+	{
3543
+		if ($right[1] == 0) {
3544
+			return ($left[1] == 0) ? static::$NaN : static::$Infinity;
3545
+		}
3546
+
3547
+		return new Node\Number($left[1] / $right[1], $left[2]);
3548
+	}
3549
+
3550
+	/**
3551
+	 * Mod numbers
3552
+	 *
3553
+	 * @param array $left
3554
+	 * @param array $right
3555
+	 *
3556
+	 * @return \ScssPhp\ScssPhp\Node\Number
3557
+	 */
3558
+	protected function opModNumberNumber($left, $right)
3559
+	{
3560
+		if ($right[1] == 0) {
3561
+			return static::$NaN;
3562
+		}
3563
+
3564
+		return new Node\Number($left[1] % $right[1], $left[2]);
3565
+	}
3566
+
3567
+	/**
3568
+	 * Add strings
3569
+	 *
3570
+	 * @param array $left
3571
+	 * @param array $right
3572
+	 *
3573
+	 * @return array|null
3574
+	 */
3575
+	protected function opAdd($left, $right)
3576
+	{
3577
+		if ($strLeft = $this->coerceString($left)) {
3578
+			if ($right[0] === Type::T_STRING) {
3579
+				$right[1] = '';
3580
+			}
3581
+
3582
+			$strLeft[2][] = $right;
3583
+
3584
+			return $strLeft;
3585
+		}
3586
+
3587
+		if ($strRight = $this->coerceString($right)) {
3588
+			if ($left[0] === Type::T_STRING) {
3589
+				$left[1] = '';
3590
+			}
3591
+
3592
+			array_unshift($strRight[2], $left);
3593
+
3594
+			return $strRight;
3595
+		}
3596
+
3597
+		return null;
3598
+	}
3599
+
3600
+	/**
3601
+	 * Boolean and
3602
+	 *
3603
+	 * @param array   $left
3604
+	 * @param array   $right
3605
+	 * @param boolean $shouldEval
3606
+	 *
3607
+	 * @return array|null
3608
+	 */
3609
+	protected function opAnd($left, $right, $shouldEval)
3610
+	{
3611
+		$truthy = ($left === static::$null || $right === static::$null) ||
3612
+				  ($left === static::$false || $left === static::$true) &&
3613
+				  ($right === static::$false || $right === static::$true);
3614
+
3615
+		if (! $shouldEval) {
3616
+			if (! $truthy) {
3617
+				return null;
3618
+			}
3619
+		}
3620
+
3621
+		if ($left !== static::$false && $left !== static::$null) {
3622
+			return $this->reduce($right, true);
3623
+		}
3624
+
3625
+		return $left;
3626
+	}
3627
+
3628
+	/**
3629
+	 * Boolean or
3630
+	 *
3631
+	 * @param array   $left
3632
+	 * @param array   $right
3633
+	 * @param boolean $shouldEval
3634
+	 *
3635
+	 * @return array|null
3636
+	 */
3637
+	protected function opOr($left, $right, $shouldEval)
3638
+	{
3639
+		$truthy = ($left === static::$null || $right === static::$null) ||
3640
+				  ($left === static::$false || $left === static::$true) &&
3641
+				  ($right === static::$false || $right === static::$true);
3642
+
3643
+		if (! $shouldEval) {
3644
+			if (! $truthy) {
3645
+				return null;
3646
+			}
3647
+		}
3648
+
3649
+		if ($left !== static::$false && $left !== static::$null) {
3650
+			return $left;
3651
+		}
3652
+
3653
+		return $this->reduce($right, true);
3654
+	}
3655
+
3656
+	/**
3657
+	 * Compare colors
3658
+	 *
3659
+	 * @param string $op
3660
+	 * @param array  $left
3661
+	 * @param array  $right
3662
+	 *
3663
+	 * @return array
3664
+	 */
3665
+	protected function opColorColor($op, $left, $right)
3666
+	{
3667
+		$out = [Type::T_COLOR];
3668
+
3669
+		foreach ([1, 2, 3] as $i) {
3670
+			$lval = isset($left[$i]) ? $left[$i] : 0;
3671
+			$rval = isset($right[$i]) ? $right[$i] : 0;
3672
+
3673
+			switch ($op) {
3674
+				case '+':
3675
+					$out[] = $lval + $rval;
3676
+					break;
3677
+
3678
+				case '-':
3679
+					$out[] = $lval - $rval;
3680
+					break;
3681
+
3682
+				case '*':
3683
+					$out[] = $lval * $rval;
3684
+					break;
3685
+
3686
+				case '%':
3687
+					if ($rval == 0) {
3688
+						throw $this->error("color: Can't take modulo by zero");
3689
+					}
3690
+
3691
+					$out[] = $lval % $rval;
3692
+					break;
3693
+
3694
+				case '/':
3695
+					if ($rval == 0) {
3696
+						throw $this->error("color: Can't divide by zero");
3697
+					}
3698
+
3699
+					$out[] = (int) ($lval / $rval);
3700
+					break;
3701
+
3702
+				case '==':
3703
+					return $this->opEq($left, $right);
3704
+
3705
+				case '!=':
3706
+					return $this->opNeq($left, $right);
3707
+
3708
+				default:
3709
+					throw $this->error("color: unknown op $op");
3710
+			}
3711
+		}
3712
+
3713
+		if (isset($left[4])) {
3714
+			$out[4] = $left[4];
3715
+		} elseif (isset($right[4])) {
3716
+			$out[4] = $right[4];
3717
+		}
3718
+
3719
+		return $this->fixColor($out);
3720
+	}
3721
+
3722
+	/**
3723
+	 * Compare color and number
3724
+	 *
3725
+	 * @param string $op
3726
+	 * @param array  $left
3727
+	 * @param array  $right
3728
+	 *
3729
+	 * @return array
3730
+	 */
3731
+	protected function opColorNumber($op, $left, $right)
3732
+	{
3733
+		$value = $right[1];
3734
+
3735
+		return $this->opColorColor(
3736
+			$op,
3737
+			$left,
3738
+			[Type::T_COLOR, $value, $value, $value]
3739
+		);
3740
+	}
3741
+
3742
+	/**
3743
+	 * Compare number and color
3744
+	 *
3745
+	 * @param string $op
3746
+	 * @param array  $left
3747
+	 * @param array  $right
3748
+	 *
3749
+	 * @return array
3750
+	 */
3751
+	protected function opNumberColor($op, $left, $right)
3752
+	{
3753
+		$value = $left[1];
3754
+
3755
+		return $this->opColorColor(
3756
+			$op,
3757
+			[Type::T_COLOR, $value, $value, $value],
3758
+			$right
3759
+		);
3760
+	}
3761
+
3762
+	/**
3763
+	 * Compare number1 == number2
3764
+	 *
3765
+	 * @param array $left
3766
+	 * @param array $right
3767
+	 *
3768
+	 * @return array
3769
+	 */
3770
+	protected function opEq($left, $right)
3771
+	{
3772
+		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
3773
+			$lStr[1] = '';
3774
+			$rStr[1] = '';
3775
+
3776
+			$left = $this->compileValue($lStr);
3777
+			$right = $this->compileValue($rStr);
3778
+		}
3779
+
3780
+		return $this->toBool($left === $right);
3781
+	}
3782
+
3783
+	/**
3784
+	 * Compare number1 != number2
3785
+	 *
3786
+	 * @param array $left
3787
+	 * @param array $right
3788
+	 *
3789
+	 * @return array
3790
+	 */
3791
+	protected function opNeq($left, $right)
3792
+	{
3793
+		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
3794
+			$lStr[1] = '';
3795
+			$rStr[1] = '';
3796
+
3797
+			$left = $this->compileValue($lStr);
3798
+			$right = $this->compileValue($rStr);
3799
+		}
3800
+
3801
+		return $this->toBool($left !== $right);
3802
+	}
3803
+
3804
+	/**
3805
+	 * Compare number1 >= number2
3806
+	 *
3807
+	 * @param array $left
3808
+	 * @param array $right
3809
+	 *
3810
+	 * @return array
3811
+	 */
3812
+	protected function opGteNumberNumber($left, $right)
3813
+	{
3814
+		return $this->toBool($left[1] >= $right[1]);
3815
+	}
3816
+
3817
+	/**
3818
+	 * Compare number1 > number2
3819
+	 *
3820
+	 * @param array $left
3821
+	 * @param array $right
3822
+	 *
3823
+	 * @return array
3824
+	 */
3825
+	protected function opGtNumberNumber($left, $right)
3826
+	{
3827
+		return $this->toBool($left[1] > $right[1]);
3828
+	}
3829
+
3830
+	/**
3831
+	 * Compare number1 <= number2
3832
+	 *
3833
+	 * @param array $left
3834
+	 * @param array $right
3835
+	 *
3836
+	 * @return array
3837
+	 */
3838
+	protected function opLteNumberNumber($left, $right)
3839
+	{
3840
+		return $this->toBool($left[1] <= $right[1]);
3841
+	}
3842
+
3843
+	/**
3844
+	 * Compare number1 < number2
3845
+	 *
3846
+	 * @param array $left
3847
+	 * @param array $right
3848
+	 *
3849
+	 * @return array
3850
+	 */
3851
+	protected function opLtNumberNumber($left, $right)
3852
+	{
3853
+		return $this->toBool($left[1] < $right[1]);
3854
+	}
3855
+
3856
+	/**
3857
+	 * Three-way comparison, aka spaceship operator
3858
+	 *
3859
+	 * @param array $left
3860
+	 * @param array $right
3861
+	 *
3862
+	 * @return \ScssPhp\ScssPhp\Node\Number
3863
+	 */
3864
+	protected function opCmpNumberNumber($left, $right)
3865
+	{
3866
+		$n = $left[1] - $right[1];
3867
+
3868
+		return new Node\Number($n ? $n / abs($n) : 0, '');
3869
+	}
3870
+
3871
+	/**
3872
+	 * Cast to boolean
3873
+	 *
3874
+	 * @api
3875
+	 *
3876
+	 * @param mixed $thing
3877
+	 *
3878
+	 * @return array
3879
+	 */
3880
+	public function toBool($thing)
3881
+	{
3882
+		return $thing ? static::$true : static::$false;
3883
+	}
3884
+
3885
+	/**
3886
+	 * Compiles a primitive value into a CSS property value.
3887
+	 *
3888
+	 * Values in scssphp are typed by being wrapped in arrays, their format is
3889
+	 * typically:
3890
+	 *
3891
+	 *     array(type, contents [, additional_contents]*)
3892
+	 *
3893
+	 * The input is expected to be reduced. This function will not work on
3894
+	 * things like expressions and variables.
3895
+	 *
3896
+	 * @api
3897
+	 *
3898
+	 * @param array $value
3899
+	 *
3900
+	 * @return string|array
3901
+	 */
3902
+	public function compileValue($value)
3903
+	{
3904
+		$value = $this->reduce($value);
3905
+
3906
+		switch ($value[0]) {
3907
+			case Type::T_KEYWORD:
3908
+				return $value[1];
3909
+
3910
+			case Type::T_COLOR:
3911
+				// [1] - red component (either number for a %)
3912
+				// [2] - green component
3913
+				// [3] - blue component
3914
+				// [4] - optional alpha component
3915
+				list(, $r, $g, $b) = $value;
3916
+
3917
+				$r = $this->compileRGBAValue($r);
3918
+				$g = $this->compileRGBAValue($g);
3919
+				$b = $this->compileRGBAValue($b);
3920
+
3921
+				if (\count($value) === 5) {
3922
+					$alpha = $this->compileRGBAValue($value[4], true);
3923
+
3924
+					if (! is_numeric($alpha) || $alpha < 1) {
3925
+						$colorName = Colors::RGBaToColorName($r, $g, $b, $alpha);
3926
+
3927
+						if (! \is_null($colorName)) {
3928
+							return $colorName;
3929
+						}
3930
+
3931
+						if (is_numeric($alpha)) {
3932
+							$a = new Node\Number($alpha, '');
3933
+						} else {
3934
+							$a = $alpha;
3935
+						}
3936
+
3937
+						return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $a . ')';
3938
+					}
3939
+				}
3940
+
3941
+				if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b)) {
3942
+					return 'rgb(' . $r . ', ' . $g . ', ' . $b . ')';
3943
+				}
3944
+
3945
+				$colorName = Colors::RGBaToColorName($r, $g, $b);
3946
+
3947
+				if (! \is_null($colorName)) {
3948
+					return $colorName;
3949
+				}
3950
+
3951
+				$h = sprintf('#%02x%02x%02x', $r, $g, $b);
3952
+
3953
+				// Converting hex color to short notation (e.g. #003399 to #039)
3954
+				if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
3955
+					$h = '#' . $h[1] . $h[3] . $h[5];
3956
+				}
3957
+
3958
+				return $h;
3959
+
3960
+			case Type::T_NUMBER:
3961
+				return $value->output($this);
3962
+
3963
+			case Type::T_STRING:
3964
+				$content = $this->compileStringContent($value);
3965
+
3966
+				if ($value[1]) {
3967
+					// force double quote as string quote for the output in certain cases
3968
+					if (
3969
+						$value[1] === "'" &&
3970
+						strpos($content, '"') === false &&
3971
+						strpbrk($content, '{}') !== false
3972
+					) {
3973
+						$value[1] = '"';
3974
+					}
3975
+					$content = str_replace(
3976
+						array('\\a', "\n", "\f" , '\\'  , "\r" , $value[1]),
3977
+						array("\r" , ' ' , '\\f', '\\\\', '\\a', '\\' . $value[1]),
3978
+						$content
3979
+					);
3980
+				}
3981
+
3982
+				return $value[1] . $content . $value[1];
3983
+
3984
+			case Type::T_FUNCTION:
3985
+				$args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
3986
+
3987
+				return "$value[1]($args)";
3988
+
3989
+			case Type::T_FUNCTION_REFERENCE:
3990
+				$name = ! empty($value[2]) ? $value[2] : '';
3991
+
3992
+				return "get-function(\"$name\")";
3993
+
3994
+			case Type::T_LIST:
3995
+				$value = $this->extractInterpolation($value);
3996
+
3997
+				if ($value[0] !== Type::T_LIST) {
3998
+					return $this->compileValue($value);
3999
+				}
4000 4000
 
4001
-                list(, $delim, $items) = $value;
4002
-                $pre = $post = '';
4001
+				list(, $delim, $items) = $value;
4002
+				$pre = $post = '';
4003 4003
 
4004
-                if (! empty($value['enclosing'])) {
4005
-                    switch ($value['enclosing']) {
4006
-                        case 'parent':
4007
-                            //$pre = '(';
4008
-                            //$post = ')';
4009
-                            break;
4010
-                        case 'forced_parent':
4011
-                            $pre = '(';
4012
-                            $post = ')';
4013
-                            break;
4014
-                        case 'bracket':
4015
-                        case 'forced_bracket':
4016
-                            $pre = '[';
4017
-                            $post = ']';
4018
-                            break;
4019
-                    }
4020
-                }
4004
+				if (! empty($value['enclosing'])) {
4005
+					switch ($value['enclosing']) {
4006
+						case 'parent':
4007
+							//$pre = '(';
4008
+							//$post = ')';
4009
+							break;
4010
+						case 'forced_parent':
4011
+							$pre = '(';
4012
+							$post = ')';
4013
+							break;
4014
+						case 'bracket':
4015
+						case 'forced_bracket':
4016
+							$pre = '[';
4017
+							$post = ']';
4018
+							break;
4019
+					}
4020
+				}
4021 4021
 
4022
-                $prefix_value = '';
4022
+				$prefix_value = '';
4023 4023
 
4024
-                if ($delim !== ' ') {
4025
-                    $prefix_value = ' ';
4026
-                }
4024
+				if ($delim !== ' ') {
4025
+					$prefix_value = ' ';
4026
+				}
4027 4027
 
4028
-                $filtered = [];
4028
+				$filtered = [];
4029 4029
 
4030
-                foreach ($items as $item) {
4031
-                    if ($item[0] === Type::T_NULL) {
4032
-                        continue;
4033
-                    }
4030
+				foreach ($items as $item) {
4031
+					if ($item[0] === Type::T_NULL) {
4032
+						continue;
4033
+					}
4034 4034
 
4035
-                    $compiled = $this->compileValue($item);
4035
+					$compiled = $this->compileValue($item);
4036 4036
 
4037
-                    if ($prefix_value && \strlen($compiled)) {
4038
-                        $compiled = $prefix_value . $compiled;
4039
-                    }
4037
+					if ($prefix_value && \strlen($compiled)) {
4038
+						$compiled = $prefix_value . $compiled;
4039
+					}
4040 4040
 
4041
-                    $filtered[] = $compiled;
4042
-                }
4041
+					$filtered[] = $compiled;
4042
+				}
4043 4043
 
4044
-                return $pre . substr(implode("$delim", $filtered), \strlen($prefix_value)) . $post;
4044
+				return $pre . substr(implode("$delim", $filtered), \strlen($prefix_value)) . $post;
4045 4045
 
4046
-            case Type::T_MAP:
4047
-                $keys     = $value[1];
4048
-                $values   = $value[2];
4049
-                $filtered = [];
4046
+			case Type::T_MAP:
4047
+				$keys     = $value[1];
4048
+				$values   = $value[2];
4049
+				$filtered = [];
4050 4050
 
4051
-                for ($i = 0, $s = \count($keys); $i < $s; $i++) {
4052
-                    $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
4053
-                }
4051
+				for ($i = 0, $s = \count($keys); $i < $s; $i++) {
4052
+					$filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
4053
+				}
4054 4054
 
4055
-                array_walk($filtered, function (&$value, $key) {
4056
-                    $value = $key . ': ' . $value;
4057
-                });
4055
+				array_walk($filtered, function (&$value, $key) {
4056
+					$value = $key . ': ' . $value;
4057
+				});
4058 4058
 
4059
-                return '(' . implode(', ', $filtered) . ')';
4059
+				return '(' . implode(', ', $filtered) . ')';
4060 4060
 
4061
-            case Type::T_INTERPOLATED:
4062
-                // node created by extractInterpolation
4063
-                list(, $interpolate, $left, $right) = $value;
4064
-                list(,, $whiteLeft, $whiteRight) = $interpolate;
4061
+			case Type::T_INTERPOLATED:
4062
+				// node created by extractInterpolation
4063
+				list(, $interpolate, $left, $right) = $value;
4064
+				list(,, $whiteLeft, $whiteRight) = $interpolate;
4065 4065
 
4066
-                $delim = $left[1];
4067
-
4068
-                if ($delim && $delim !== ' ' && ! $whiteLeft) {
4069
-                    $delim .= ' ';
4070
-                }
4071
-
4072
-                $left = \count($left[2]) > 0
4073
-                    ?  $this->compileValue($left) . $delim . $whiteLeft
4074
-                    : '';
4075
-
4076
-                $delim = $right[1];
4066
+				$delim = $left[1];
4067
+
4068
+				if ($delim && $delim !== ' ' && ! $whiteLeft) {
4069
+					$delim .= ' ';
4070
+				}
4071
+
4072
+				$left = \count($left[2]) > 0
4073
+					?  $this->compileValue($left) . $delim . $whiteLeft
4074
+					: '';
4075
+
4076
+				$delim = $right[1];
4077 4077
 
4078
-                if ($delim && $delim !== ' ') {
4079
-                    $delim .= ' ';
4080
-                }
4078
+				if ($delim && $delim !== ' ') {
4079
+					$delim .= ' ';
4080
+				}
4081 4081
 
4082
-                $right = \count($right[2]) > 0 ?
4083
-                    $whiteRight . $delim . $this->compileValue($right) : '';
4084
-
4085
-                return $left . $this->compileValue($interpolate) . $right;
4086
-
4087
-            case Type::T_INTERPOLATE:
4088
-                // strip quotes if it's a string
4089
-                $reduced = $this->reduce($value[1]);
4090
-
4091
-                switch ($reduced[0]) {
4092
-                    case Type::T_LIST:
4093
-                        $reduced = $this->extractInterpolation($reduced);
4094
-
4095
-                        if ($reduced[0] !== Type::T_LIST) {
4096
-                            break;
4097
-                        }
4098
-
4099
-                        list(, $delim, $items) = $reduced;
4100
-
4101
-                        if ($delim !== ' ') {
4102
-                            $delim .= ' ';
4103
-                        }
4104
-
4105
-                        $filtered = [];
4106
-
4107
-                        foreach ($items as $item) {
4108
-                            if ($item[0] === Type::T_NULL) {
4109
-                                continue;
4110
-                            }
4111
-
4112
-                            $temp = $this->compileValue([Type::T_KEYWORD, $item]);
4113
-
4114
-                            if ($temp[0] === Type::T_STRING) {
4115
-                                $filtered[] = $this->compileStringContent($temp);
4116
-                            } elseif ($temp[0] === Type::T_KEYWORD) {
4117
-                                $filtered[] = $temp[1];
4118
-                            } else {
4119
-                                $filtered[] = $this->compileValue($temp);
4120
-                            }
4121
-                        }
4122
-
4123
-                        $reduced = [Type::T_KEYWORD, implode("$delim", $filtered)];
4124
-                        break;
4125
-
4126
-                    case Type::T_STRING:
4127
-                        $reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
4128
-                        break;
4129
-
4130
-                    case Type::T_NULL:
4131
-                        $reduced = [Type::T_KEYWORD, ''];
4132
-                }
4133
-
4134
-                return $this->compileValue($reduced);
4135
-
4136
-            case Type::T_NULL:
4137
-                return 'null';
4138
-
4139
-            case Type::T_COMMENT:
4140
-                return $this->compileCommentValue($value);
4141
-
4142
-            default:
4143
-                throw $this->error('unknown value type: ' . json_encode($value));
4144
-        }
4145
-    }
4146
-
4147
-    /**
4148
-     * @param array $value
4149
-     *
4150
-     * @return array|string
4151
-     */
4152
-    protected function compileDebugValue($value)
4153
-    {
4154
-        $value = $this->reduce($value, true);
4155
-
4156
-        switch ($value[0]) {
4157
-            case Type::T_STRING:
4158
-                return $this->compileStringContent($value);
4159
-
4160
-            default:
4161
-                return $this->compileValue($value);
4162
-        }
4163
-    }
4164
-
4165
-    /**
4166
-     * Flatten list
4167
-     *
4168
-     * @param array $list
4169
-     *
4170
-     * @return string
4171
-     */
4172
-    protected function flattenList($list)
4173
-    {
4174
-        return $this->compileValue($list);
4175
-    }
4176
-
4177
-    /**
4178
-     * Compile string content
4179
-     *
4180
-     * @param array $string
4181
-     *
4182
-     * @return string
4183
-     */
4184
-    protected function compileStringContent($string)
4185
-    {
4186
-        $parts = [];
4187
-
4188
-        foreach ($string[2] as $part) {
4189
-            if (\is_array($part) || $part instanceof \ArrayAccess) {
4190
-                $parts[] = $this->compileValue($part);
4191
-            } else {
4192
-                $parts[] = $part;
4193
-            }
4194
-        }
4195
-
4196
-        return implode($parts);
4197
-    }
4198
-
4199
-    /**
4200
-     * Extract interpolation; it doesn't need to be recursive, compileValue will handle that
4201
-     *
4202
-     * @param array $list
4203
-     *
4204
-     * @return array
4205
-     */
4206
-    protected function extractInterpolation($list)
4207
-    {
4208
-        $items = $list[2];
4209
-
4210
-        foreach ($items as $i => $item) {
4211
-            if ($item[0] === Type::T_INTERPOLATE) {
4212
-                $before = [Type::T_LIST, $list[1], \array_slice($items, 0, $i)];
4213
-                $after  = [Type::T_LIST, $list[1], \array_slice($items, $i + 1)];
4214
-
4215
-                return [Type::T_INTERPOLATED, $item, $before, $after];
4216
-            }
4217
-        }
4218
-
4219
-        return $list;
4220
-    }
4221
-
4222
-    /**
4223
-     * Find the final set of selectors
4224
-     *
4225
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4226
-     * @param \ScssPhp\ScssPhp\Block                $selfParent
4227
-     *
4228
-     * @return array
4229
-     */
4230
-    protected function multiplySelectors(Environment $env, $selfParent = null)
4231
-    {
4232
-        $envs            = $this->compactEnv($env);
4233
-        $selectors       = [];
4234
-        $parentSelectors = [[]];
4235
-
4236
-        $selfParentSelectors = null;
4237
-
4238
-        if (! \is_null($selfParent) && $selfParent->selectors) {
4239
-            $selfParentSelectors = $this->evalSelectors($selfParent->selectors);
4240
-        }
4241
-
4242
-        while ($env = array_pop($envs)) {
4243
-            if (empty($env->selectors)) {
4244
-                continue;
4245
-            }
4246
-
4247
-            $selectors = $env->selectors;
4248
-
4249
-            do {
4250
-                $stillHasSelf  = false;
4251
-                $prevSelectors = $selectors;
4252
-                $selectors     = [];
4253
-
4254
-                foreach ($parentSelectors as $parent) {
4255
-                    foreach ($prevSelectors as $selector) {
4256
-                        if ($selfParentSelectors) {
4257
-                            foreach ($selfParentSelectors as $selfParent) {
4258
-                                // if no '&' in the selector, each call will give same result, only add once
4259
-                                $s = $this->joinSelectors($parent, $selector, $stillHasSelf, $selfParent);
4260
-                                $selectors[serialize($s)] = $s;
4261
-                            }
4262
-                        } else {
4263
-                            $s = $this->joinSelectors($parent, $selector, $stillHasSelf);
4264
-                            $selectors[serialize($s)] = $s;
4265
-                        }
4266
-                    }
4267
-                }
4268
-            } while ($stillHasSelf);
4269
-
4270
-            $parentSelectors = $selectors;
4271
-        }
4272
-
4273
-        $selectors = array_values($selectors);
4274
-
4275
-        // case we are just starting a at-root : nothing to multiply but parentSelectors
4276
-        if (! $selectors && $selfParentSelectors) {
4277
-            $selectors = $selfParentSelectors;
4278
-        }
4279
-
4280
-        return $selectors;
4281
-    }
4282
-
4283
-    /**
4284
-     * Join selectors; looks for & to replace, or append parent before child
4285
-     *
4286
-     * @param array   $parent
4287
-     * @param array   $child
4288
-     * @param boolean $stillHasSelf
4289
-     * @param array   $selfParentSelectors
4290
-
4291
-     * @return array
4292
-     */
4293
-    protected function joinSelectors($parent, $child, &$stillHasSelf, $selfParentSelectors = null)
4294
-    {
4295
-        $setSelf = false;
4296
-        $out = [];
4297
-
4298
-        foreach ($child as $part) {
4299
-            $newPart = [];
4300
-
4301
-            foreach ($part as $p) {
4302
-                // only replace & once and should be recalled to be able to make combinations
4303
-                if ($p === static::$selfSelector && $setSelf) {
4304
-                    $stillHasSelf = true;
4305
-                }
4306
-
4307
-                if ($p === static::$selfSelector && ! $setSelf) {
4308
-                    $setSelf = true;
4309
-
4310
-                    if (\is_null($selfParentSelectors)) {
4311
-                        $selfParentSelectors = $parent;
4312
-                    }
4313
-
4314
-                    foreach ($selfParentSelectors as $i => $parentPart) {
4315
-                        if ($i > 0) {
4316
-                            $out[] = $newPart;
4317
-                            $newPart = [];
4318
-                        }
4319
-
4320
-                        foreach ($parentPart as $pp) {
4321
-                            if (\is_array($pp)) {
4322
-                                $flatten = [];
4323
-
4324
-                                array_walk_recursive($pp, function ($a) use (&$flatten) {
4325
-                                    $flatten[] = $a;
4326
-                                });
4327
-
4328
-                                $pp = implode($flatten);
4329
-                            }
4330
-
4331
-                            $newPart[] = $pp;
4332
-                        }
4333
-                    }
4334
-                } else {
4335
-                    $newPart[] = $p;
4336
-                }
4337
-            }
4338
-
4339
-            $out[] = $newPart;
4340
-        }
4341
-
4342
-        return $setSelf ? $out : array_merge($parent, $child);
4343
-    }
4344
-
4345
-    /**
4346
-     * Multiply media
4347
-     *
4348
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4349
-     * @param array                                 $childQueries
4350
-     *
4351
-     * @return array
4352
-     */
4353
-    protected function multiplyMedia(Environment $env = null, $childQueries = null)
4354
-    {
4355
-        if (
4356
-            ! isset($env) ||
4357
-            ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4358
-        ) {
4359
-            return $childQueries;
4360
-        }
4361
-
4362
-        // plain old block, skip
4363
-        if (empty($env->block->type)) {
4364
-            return $this->multiplyMedia($env->parent, $childQueries);
4365
-        }
4366
-
4367
-        $parentQueries = isset($env->block->queryList)
4368
-            ? $env->block->queryList
4369
-            : [[[Type::T_MEDIA_VALUE, $env->block->value]]];
4370
-
4371
-        $store = [$this->env, $this->storeEnv];
4372
-
4373
-        $this->env      = $env;
4374
-        $this->storeEnv = null;
4375
-        $parentQueries  = $this->evaluateMediaQuery($parentQueries);
4376
-
4377
-        list($this->env, $this->storeEnv) = $store;
4378
-
4379
-        if (\is_null($childQueries)) {
4380
-            $childQueries = $parentQueries;
4381
-        } else {
4382
-            $originalQueries = $childQueries;
4383
-            $childQueries = [];
4384
-
4385
-            foreach ($parentQueries as $parentQuery) {
4386
-                foreach ($originalQueries as $childQuery) {
4387
-                    $childQueries[] = array_merge(
4388
-                        $parentQuery,
4389
-                        [[Type::T_MEDIA_TYPE, [Type::T_KEYWORD, 'all']]],
4390
-                        $childQuery
4391
-                    );
4392
-                }
4393
-            }
4394
-        }
4395
-
4396
-        return $this->multiplyMedia($env->parent, $childQueries);
4397
-    }
4398
-
4399
-    /**
4400
-     * Convert env linked list to stack
4401
-     *
4402
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4403
-     *
4404
-     * @return array
4405
-     */
4406
-    protected function compactEnv(Environment $env)
4407
-    {
4408
-        for ($envs = []; $env; $env = $env->parent) {
4409
-            $envs[] = $env;
4410
-        }
4411
-
4412
-        return $envs;
4413
-    }
4414
-
4415
-    /**
4416
-     * Convert env stack to singly linked list
4417
-     *
4418
-     * @param array $envs
4419
-     *
4420
-     * @return \ScssPhp\ScssPhp\Compiler\Environment
4421
-     */
4422
-    protected function extractEnv($envs)
4423
-    {
4424
-        for ($env = null; $e = array_pop($envs);) {
4425
-            $e->parent = $env;
4426
-            $env = $e;
4427
-        }
4428
-
4429
-        return $env;
4430
-    }
4431
-
4432
-    /**
4433
-     * Push environment
4434
-     *
4435
-     * @param \ScssPhp\ScssPhp\Block $block
4436
-     *
4437
-     * @return \ScssPhp\ScssPhp\Compiler\Environment
4438
-     */
4439
-    protected function pushEnv(Block $block = null)
4440
-    {
4441
-        $env = new Environment();
4442
-        $env->parent = $this->env;
4443
-        $env->parentStore = $this->storeEnv;
4444
-        $env->store  = [];
4445
-        $env->block  = $block;
4446
-        $env->depth  = isset($this->env->depth) ? $this->env->depth + 1 : 0;
4447
-
4448
-        $this->env = $env;
4449
-        $this->storeEnv = null;
4450
-
4451
-        return $env;
4452
-    }
4453
-
4454
-    /**
4455
-     * Pop environment
4456
-     */
4457
-    protected function popEnv()
4458
-    {
4459
-        $this->storeEnv = $this->env->parentStore;
4460
-        $this->env = $this->env->parent;
4461
-    }
4462
-
4463
-    /**
4464
-     * Propagate vars from a just poped Env (used in @each and @for)
4465
-     *
4466
-     * @param array      $store
4467
-     * @param null|array $excludedVars
4468
-     */
4469
-    protected function backPropagateEnv($store, $excludedVars = null)
4470
-    {
4471
-        foreach ($store as $key => $value) {
4472
-            if (empty($excludedVars) || ! \in_array($key, $excludedVars)) {
4473
-                $this->set($key, $value, true);
4474
-            }
4475
-        }
4476
-    }
4477
-
4478
-    /**
4479
-     * Get store environment
4480
-     *
4481
-     * @return \ScssPhp\ScssPhp\Compiler\Environment
4482
-     */
4483
-    protected function getStoreEnv()
4484
-    {
4485
-        return isset($this->storeEnv) ? $this->storeEnv : $this->env;
4486
-    }
4487
-
4488
-    /**
4489
-     * Set variable
4490
-     *
4491
-     * @param string                                $name
4492
-     * @param mixed                                 $value
4493
-     * @param boolean                               $shadow
4494
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4495
-     * @param mixed                                 $valueUnreduced
4496
-     */
4497
-    protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
4498
-    {
4499
-        $name = $this->normalizeName($name);
4500
-
4501
-        if (! isset($env)) {
4502
-            $env = $this->getStoreEnv();
4503
-        }
4504
-
4505
-        if ($shadow) {
4506
-            $this->setRaw($name, $value, $env, $valueUnreduced);
4507
-        } else {
4508
-            $this->setExisting($name, $value, $env, $valueUnreduced);
4509
-        }
4510
-    }
4511
-
4512
-    /**
4513
-     * Set existing variable
4514
-     *
4515
-     * @param string                                $name
4516
-     * @param mixed                                 $value
4517
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4518
-     * @param mixed                                 $valueUnreduced
4519
-     */
4520
-    protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
4521
-    {
4522
-        $storeEnv = $env;
4523
-        $specialContentKey = static::$namespaces['special'] . 'content';
4524
-
4525
-        $hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%';
4526
-
4527
-        $maxDepth = 10000;
4528
-
4529
-        for (;;) {
4530
-            if ($maxDepth-- <= 0) {
4531
-                break;
4532
-            }
4533
-
4534
-            if (\array_key_exists($name, $env->store)) {
4535
-                break;
4536
-            }
4537
-
4538
-            if (! $hasNamespace && isset($env->marker)) {
4539
-                if (! empty($env->store[$specialContentKey])) {
4540
-                    $env = $env->store[$specialContentKey]->scope;
4541
-                    continue;
4542
-                }
4543
-
4544
-                if (! empty($env->declarationScopeParent)) {
4545
-                    $env = $env->declarationScopeParent;
4546
-                    continue;
4547
-                } else {
4548
-                    $env = $storeEnv;
4549
-                    break;
4550
-                }
4551
-            }
4552
-
4553
-            if (isset($env->parentStore)) {
4554
-                $env = $env->parentStore;
4555
-            } elseif (isset($env->parent)) {
4556
-                $env = $env->parent;
4557
-            } else {
4558
-                $env = $storeEnv;
4559
-                break;
4560
-            }
4561
-        }
4562
-
4563
-        $env->store[$name] = $value;
4564
-
4565
-        if ($valueUnreduced) {
4566
-            $env->storeUnreduced[$name] = $valueUnreduced;
4567
-        }
4568
-    }
4569
-
4570
-    /**
4571
-     * Set raw variable
4572
-     *
4573
-     * @param string                                $name
4574
-     * @param mixed                                 $value
4575
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4576
-     * @param mixed                                 $valueUnreduced
4577
-     */
4578
-    protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
4579
-    {
4580
-        $env->store[$name] = $value;
4581
-
4582
-        if ($valueUnreduced) {
4583
-            $env->storeUnreduced[$name] = $valueUnreduced;
4584
-        }
4585
-    }
4586
-
4587
-    /**
4588
-     * Get variable
4589
-     *
4590
-     * @api
4591
-     *
4592
-     * @param string                                $name
4593
-     * @param boolean                               $shouldThrow
4594
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4595
-     * @param boolean                               $unreduced
4596
-     *
4597
-     * @return mixed|null
4598
-     */
4599
-    public function get($name, $shouldThrow = true, Environment $env = null, $unreduced = false)
4600
-    {
4601
-        $normalizedName = $this->normalizeName($name);
4602
-        $specialContentKey = static::$namespaces['special'] . 'content';
4603
-
4604
-        if (! isset($env)) {
4605
-            $env = $this->getStoreEnv();
4606
-        }
4607
-
4608
-        $hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%';
4609
-
4610
-        $maxDepth = 10000;
4611
-
4612
-        for (;;) {
4613
-            if ($maxDepth-- <= 0) {
4614
-                break;
4615
-            }
4616
-
4617
-            if (\array_key_exists($normalizedName, $env->store)) {
4618
-                if ($unreduced && isset($env->storeUnreduced[$normalizedName])) {
4619
-                    return $env->storeUnreduced[$normalizedName];
4620
-                }
4621
-
4622
-                return $env->store[$normalizedName];
4623
-            }
4624
-
4625
-            if (! $hasNamespace && isset($env->marker)) {
4626
-                if (! empty($env->store[$specialContentKey])) {
4627
-                    $env = $env->store[$specialContentKey]->scope;
4628
-                    continue;
4629
-                }
4630
-
4631
-                if (! empty($env->declarationScopeParent)) {
4632
-                    $env = $env->declarationScopeParent;
4633
-                } else {
4634
-                    $env = $this->rootEnv;
4635
-                }
4636
-                continue;
4637
-            }
4638
-
4639
-            if (isset($env->parentStore)) {
4640
-                $env = $env->parentStore;
4641
-            } elseif (isset($env->parent)) {
4642
-                $env = $env->parent;
4643
-            } else {
4644
-                break;
4645
-            }
4646
-        }
4647
-
4648
-        if ($shouldThrow) {
4649
-            throw $this->error("Undefined variable \$$name" . ($maxDepth <= 0 ? ' (infinite recursion)' : ''));
4650
-        }
4651
-
4652
-        // found nothing
4653
-        return null;
4654
-    }
4655
-
4656
-    /**
4657
-     * Has variable?
4658
-     *
4659
-     * @param string                                $name
4660
-     * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4661
-     *
4662
-     * @return boolean
4663
-     */
4664
-    protected function has($name, Environment $env = null)
4665
-    {
4666
-        return ! \is_null($this->get($name, false, $env));
4667
-    }
4668
-
4669
-    /**
4670
-     * Inject variables
4671
-     *
4672
-     * @param array $args
4673
-     */
4674
-    protected function injectVariables(array $args)
4675
-    {
4676
-        if (empty($args)) {
4677
-            return;
4678
-        }
4679
-
4680
-        $parser = $this->parserFactory(__METHOD__);
4681
-
4682
-        foreach ($args as $name => $strValue) {
4683
-            if ($name[0] === '$') {
4684
-                $name = substr($name, 1);
4685
-            }
4686
-
4687
-            if (! $parser->parseValue($strValue, $value)) {
4688
-                $value = $this->coerceValue($strValue);
4689
-            }
4690
-
4691
-            $this->set($name, $value);
4692
-        }
4693
-    }
4694
-
4695
-    /**
4696
-     * Set variables
4697
-     *
4698
-     * @api
4699
-     *
4700
-     * @param array $variables
4701
-     */
4702
-    public function setVariables(array $variables)
4703
-    {
4704
-        $this->registeredVars = array_merge($this->registeredVars, $variables);
4705
-    }
4706
-
4707
-    /**
4708
-     * Unset variable
4709
-     *
4710
-     * @api
4711
-     *
4712
-     * @param string $name
4713
-     */
4714
-    public function unsetVariable($name)
4715
-    {
4716
-        unset($this->registeredVars[$name]);
4717
-    }
4718
-
4719
-    /**
4720
-     * Returns list of variables
4721
-     *
4722
-     * @api
4723
-     *
4724
-     * @return array
4725
-     */
4726
-    public function getVariables()
4727
-    {
4728
-        return $this->registeredVars;
4729
-    }
4730
-
4731
-    /**
4732
-     * Adds to list of parsed files
4733
-     *
4734
-     * @api
4735
-     *
4736
-     * @param string $path
4737
-     */
4738
-    public function addParsedFile($path)
4739
-    {
4740
-        if (isset($path) && is_file($path)) {
4741
-            $this->parsedFiles[realpath($path)] = filemtime($path);
4742
-        }
4743
-    }
4744
-
4745
-    /**
4746
-     * Returns list of parsed files
4747
-     *
4748
-     * @api
4749
-     *
4750
-     * @return array
4751
-     */
4752
-    public function getParsedFiles()
4753
-    {
4754
-        return $this->parsedFiles;
4755
-    }
4756
-
4757
-    /**
4758
-     * Add import path
4759
-     *
4760
-     * @api
4761
-     *
4762
-     * @param string|callable $path
4763
-     */
4764
-    public function addImportPath($path)
4765
-    {
4766
-        if (! \in_array($path, $this->importPaths)) {
4767
-            $this->importPaths[] = $path;
4768
-        }
4769
-    }
4770
-
4771
-    /**
4772
-     * Set import paths
4773
-     *
4774
-     * @api
4775
-     *
4776
-     * @param string|array $path
4777
-     */
4778
-    public function setImportPaths($path)
4779
-    {
4780
-        $this->importPaths = (array) $path;
4781
-    }
4782
-
4783
-    /**
4784
-     * Set number precision
4785
-     *
4786
-     * @api
4787
-     *
4788
-     * @param integer $numberPrecision
4789
-     *
4790
-     * @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
4791
-     */
4792
-    public function setNumberPrecision($numberPrecision)
4793
-    {
4794
-        @trigger_error('The number precision is not configurable anymore. '
4795
-            . 'The default is enough for all browsers.', E_USER_DEPRECATED);
4796
-    }
4797
-
4798
-    /**
4799
-     * Set formatter
4800
-     *
4801
-     * @api
4802
-     *
4803
-     * @param string $formatterName
4804
-     */
4805
-    public function setFormatter($formatterName)
4806
-    {
4807
-        $this->formatter = $formatterName;
4808
-    }
4809
-
4810
-    /**
4811
-     * Set line number style
4812
-     *
4813
-     * @api
4814
-     *
4815
-     * @param string $lineNumberStyle
4816
-     */
4817
-    public function setLineNumberStyle($lineNumberStyle)
4818
-    {
4819
-        $this->lineNumberStyle = $lineNumberStyle;
4820
-    }
4821
-
4822
-    /**
4823
-     * Enable/disable source maps
4824
-     *
4825
-     * @api
4826
-     *
4827
-     * @param integer $sourceMap
4828
-     */
4829
-    public function setSourceMap($sourceMap)
4830
-    {
4831
-        $this->sourceMap = $sourceMap;
4832
-    }
4833
-
4834
-    /**
4835
-     * Set source map options
4836
-     *
4837
-     * @api
4838
-     *
4839
-     * @param array $sourceMapOptions
4840
-     */
4841
-    public function setSourceMapOptions($sourceMapOptions)
4842
-    {
4843
-        $this->sourceMapOptions = $sourceMapOptions;
4844
-    }
4845
-
4846
-    /**
4847
-     * Register function
4848
-     *
4849
-     * @api
4850
-     *
4851
-     * @param string   $name
4852
-     * @param callable $func
4853
-     * @param array    $prototype
4854
-     */
4855
-    public function registerFunction($name, $func, $prototype = null)
4856
-    {
4857
-        $this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
4858
-    }
4859
-
4860
-    /**
4861
-     * Unregister function
4862
-     *
4863
-     * @api
4864
-     *
4865
-     * @param string $name
4866
-     */
4867
-    public function unregisterFunction($name)
4868
-    {
4869
-        unset($this->userFunctions[$this->normalizeName($name)]);
4870
-    }
4871
-
4872
-    /**
4873
-     * Add feature
4874
-     *
4875
-     * @api
4876
-     *
4877
-     * @param string $name
4878
-     */
4879
-    public function addFeature($name)
4880
-    {
4881
-        $this->registeredFeatures[$name] = true;
4882
-    }
4883
-
4884
-    /**
4885
-     * Import file
4886
-     *
4887
-     * @param string                                 $path
4888
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
4889
-     */
4890
-    protected function importFile($path, OutputBlock $out)
4891
-    {
4892
-        $this->pushCallStack('import ' . $path);
4893
-        // see if tree is cached
4894
-        $realPath = realpath($path);
4895
-
4896
-        if (isset($this->importCache[$realPath])) {
4897
-            $this->handleImportLoop($realPath);
4898
-
4899
-            $tree = $this->importCache[$realPath];
4900
-        } else {
4901
-            $code   = file_get_contents($path);
4902
-            $parser = $this->parserFactory($path);
4903
-            $tree   = $parser->parse($code);
4904
-
4905
-            $this->importCache[$realPath] = $tree;
4906
-        }
4907
-
4908
-        $pi = pathinfo($path);
4909
-
4910
-        array_unshift($this->importPaths, $pi['dirname']);
4911
-        $this->compileChildrenNoReturn($tree->children, $out);
4912
-        array_shift($this->importPaths);
4913
-        $this->popCallStack();
4914
-    }
4915
-
4916
-    /**
4917
-     * Return the file path for an import url if it exists
4918
-     *
4919
-     * @api
4920
-     *
4921
-     * @param string $url
4922
-     *
4923
-     * @return string|null
4924
-     */
4925
-    public function findImport($url)
4926
-    {
4927
-        $urls = [];
4928
-
4929
-        $hasExtension = preg_match('/[.]s?css$/', $url);
4930
-
4931
-        // for "normal" scss imports (ignore vanilla css and external requests)
4932
-        if (! preg_match('~\.css$|^https?://|^//~', $url)) {
4933
-            $isPartial = (strpos(basename($url), '_') === 0);
4934
-
4935
-            // try both normal and the _partial filename
4936
-            $urls = [$url . ($hasExtension ? '' : '.scss')];
4937
-
4938
-            if (! $isPartial) {
4939
-                $urls[] = preg_replace('~[^/]+$~', '_\0', $url) . ($hasExtension ? '' : '.scss');
4940
-            }
4941
-
4942
-            if (! $hasExtension) {
4943
-                $urls[] = "$url/index.scss";
4944
-                $urls[] = "$url/_index.scss";
4945
-                // allow to find a plain css file, *if* no scss or partial scss is found
4946
-                $urls[] .= $url . '.css';
4947
-            }
4948
-        }
4949
-
4950
-        foreach ($this->importPaths as $dir) {
4951
-            if (\is_string($dir)) {
4952
-                // check urls for normal import paths
4953
-                foreach ($urls as $full) {
4954
-                    $separator = (
4955
-                        ! empty($dir) &&
4956
-                        substr($dir, -1) !== '/' &&
4957
-                        substr($full, 0, 1) !== '/'
4958
-                    ) ? '/' : '';
4959
-                    $full = $dir . $separator . $full;
4960
-
4961
-                    if (is_file($file = $full)) {
4962
-                        return $file;
4963
-                    }
4964
-                }
4965
-            } elseif (\is_callable($dir)) {
4966
-                // check custom callback for import path
4967
-                $file = \call_user_func($dir, $url);
4968
-
4969
-                if (! \is_null($file)) {
4970
-                    return $file;
4971
-                }
4972
-            }
4973
-        }
4974
-
4975
-        if ($urls) {
4976
-            if (! $hasExtension || preg_match('/[.]scss$/', $url)) {
4977
-                throw $this->error("`$url` file not found for @import");
4978
-            }
4979
-        }
4980
-
4981
-        return null;
4982
-    }
4983
-
4984
-    /**
4985
-     * Set encoding
4986
-     *
4987
-     * @api
4988
-     *
4989
-     * @param string $encoding
4990
-     */
4991
-    public function setEncoding($encoding)
4992
-    {
4993
-        $this->encoding = $encoding;
4994
-    }
4995
-
4996
-    /**
4997
-     * Ignore errors?
4998
-     *
4999
-     * @api
5000
-     *
5001
-     * @param boolean $ignoreErrors
5002
-     *
5003
-     * @return \ScssPhp\ScssPhp\Compiler
5004
-     *
5005
-     * @deprecated Ignoring Sass errors is not longer supported.
5006
-     */
5007
-    public function setIgnoreErrors($ignoreErrors)
5008
-    {
5009
-        @trigger_error('Ignoring Sass errors is not longer supported.', E_USER_DEPRECATED);
5010
-
5011
-        return $this;
5012
-    }
5013
-
5014
-    /**
5015
-     * Get source position
5016
-     *
5017
-     * @api
5018
-     *
5019
-     * @return array
5020
-     */
5021
-    public function getSourcePosition()
5022
-    {
5023
-        $sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
5024
-
5025
-        return [$sourceFile, $this->sourceLine, $this->sourceColumn];
5026
-    }
5027
-
5028
-    /**
5029
-     * Throw error (exception)
5030
-     *
5031
-     * @api
5032
-     *
5033
-     * @param string $msg Message with optional sprintf()-style vararg parameters
5034
-     *
5035
-     * @throws \ScssPhp\ScssPhp\Exception\CompilerException
5036
-     *
5037
-     * @deprecated use "error" and throw the exception in the caller instead.
5038
-     */
5039
-    public function throwError($msg)
5040
-    {
5041
-        @trigger_error(
5042
-            'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead',
5043
-            E_USER_DEPRECATED
5044
-        );
5045
-
5046
-        throw $this->error(...func_get_args());
5047
-    }
5048
-
5049
-    /**
5050
-     * Build an error (exception)
5051
-     *
5052
-     * @api
5053
-     *
5054
-     * @param string $msg Message with optional sprintf()-style vararg parameters
5055
-     *
5056
-     * @return CompilerException
5057
-     */
5058
-    public function error($msg, ...$args)
5059
-    {
5060
-        if ($args) {
5061
-            $msg = sprintf($msg, ...$args);
5062
-        }
5063
-
5064
-        if (! $this->ignoreCallStackMessage) {
5065
-            $line   = $this->sourceLine;
5066
-            $column = $this->sourceColumn;
5067
-
5068
-            $loc = isset($this->sourceNames[$this->sourceIndex])
5069
-                ? $this->sourceNames[$this->sourceIndex] . " on line $line, at column $column"
5070
-                : "line: $line, column: $column";
5071
-
5072
-            $msg = "$msg: $loc";
5073
-
5074
-            $callStackMsg = $this->callStackMessage();
5075
-
5076
-            if ($callStackMsg) {
5077
-                $msg .= "\nCall Stack:\n" . $callStackMsg;
5078
-            }
5079
-        }
5080
-
5081
-        return new CompilerException($msg);
5082
-    }
5083
-
5084
-    /**
5085
-     * @param string $functionName
5086
-     * @param array $ExpectedArgs
5087
-     * @param int $nbActual
5088
-     * @return CompilerException
5089
-     */
5090
-    public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
5091
-    {
5092
-        $nbExpected = \count($ExpectedArgs);
5093
-
5094
-        if ($nbActual > $nbExpected) {
5095
-            return $this->error(
5096
-                'Error: Only %d arguments allowed in %s(), but %d were passed.',
5097
-                $nbExpected,
5098
-                $functionName,
5099
-                $nbActual
5100
-            );
5101
-        } else {
5102
-            $missing = [];
5103
-
5104
-            while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) {
5105
-                array_unshift($missing, array_pop($ExpectedArgs));
5106
-            }
5107
-
5108
-            return $this->error(
5109
-                'Error: %s() argument%s %s missing.',
5110
-                $functionName,
5111
-                count($missing) > 1 ? 's' : '',
5112
-                implode(', ', $missing)
5113
-            );
5114
-        }
5115
-    }
5116
-
5117
-    /**
5118
-     * Beautify call stack for output
5119
-     *
5120
-     * @param boolean $all
5121
-     * @param null    $limit
5122
-     *
5123
-     * @return string
5124
-     */
5125
-    protected function callStackMessage($all = false, $limit = null)
5126
-    {
5127
-        $callStackMsg = [];
5128
-        $ncall = 0;
5129
-
5130
-        if ($this->callStack) {
5131
-            foreach (array_reverse($this->callStack) as $call) {
5132
-                if ($all || (isset($call['n']) && $call['n'])) {
5133
-                    $msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
5134
-                    $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
5135
-                          ? $this->sourceNames[$call[Parser::SOURCE_INDEX]]
5136
-                          : '(unknown file)');
5137
-                    $msg .= ' on line ' . $call[Parser::SOURCE_LINE];
5138
-
5139
-                    $callStackMsg[] = $msg;
5140
-
5141
-                    if (! \is_null($limit) && $ncall > $limit) {
5142
-                        break;
5143
-                    }
5144
-                }
5145
-            }
5146
-        }
5147
-
5148
-        return implode("\n", $callStackMsg);
5149
-    }
5150
-
5151
-    /**
5152
-     * Handle import loop
5153
-     *
5154
-     * @param string $name
5155
-     *
5156
-     * @throws \Exception
5157
-     */
5158
-    protected function handleImportLoop($name)
5159
-    {
5160
-        for ($env = $this->env; $env; $env = $env->parent) {
5161
-            if (! $env->block) {
5162
-                continue;
5163
-            }
5164
-
5165
-            $file = $this->sourceNames[$env->block->sourceIndex];
5166
-
5167
-            if (realpath($file) === $name) {
5168
-                throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
5169
-            }
5170
-        }
5171
-    }
5172
-
5173
-    /**
5174
-     * Call SCSS @function
5175
-     *
5176
-     * @param Object $func
5177
-     * @param array  $argValues
5178
-     *
5179
-     * @return array $returnValue
5180
-     */
5181
-    protected function callScssFunction($func, $argValues)
5182
-    {
5183
-        if (! $func) {
5184
-            return static::$defaultValue;
5185
-        }
5186
-        $name = $func->name;
5187
-
5188
-        $this->pushEnv();
5189
-
5190
-        // set the args
5191
-        if (isset($func->args)) {
5192
-            $this->applyArguments($func->args, $argValues);
5193
-        }
5194
-
5195
-        // throw away lines and children
5196
-        $tmp = new OutputBlock();
5197
-        $tmp->lines    = [];
5198
-        $tmp->children = [];
5199
-
5200
-        $this->env->marker = 'function';
5201
-
5202
-        if (! empty($func->parentEnv)) {
5203
-            $this->env->declarationScopeParent = $func->parentEnv;
5204
-        } else {
5205
-            throw $this->error("@function $name() without parentEnv");
5206
-        }
5207
-
5208
-        $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . ' ' . $name);
5209
-
5210
-        $this->popEnv();
5211
-
5212
-        return ! isset($ret) ? static::$defaultValue : $ret;
5213
-    }
5214
-
5215
-    /**
5216
-     * Call built-in and registered (PHP) functions
5217
-     *
5218
-     * @param string $name
5219
-     * @param string|array $function
5220
-     * @param array  $prototype
5221
-     * @param array  $args
5222
-     *
5223
-     * @return array
5224
-     */
5225
-    protected function callNativeFunction($name, $function, $prototype, $args)
5226
-    {
5227
-        $libName = (is_array($function) ? end($function) : null);
5228
-        $sorted_kwargs = $this->sortNativeFunctionArgs($libName, $prototype, $args);
5229
-
5230
-        if (\is_null($sorted_kwargs)) {
5231
-            return null;
5232
-        }
5233
-        @list($sorted, $kwargs) = $sorted_kwargs;
5234
-
5235
-        if ($name !== 'if' && $name !== 'call') {
5236
-            $inExp = true;
5237
-
5238
-            if ($name === 'join') {
5239
-                $inExp = false;
5240
-            }
5241
-
5242
-            foreach ($sorted as &$val) {
5243
-                $val = $this->reduce($val, $inExp);
5244
-            }
5245
-        }
5246
-
5247
-        $returnValue = \call_user_func($function, $sorted, $kwargs);
5248
-
5249
-        if (! isset($returnValue)) {
5250
-            return null;
5251
-        }
5252
-
5253
-        return $this->coerceValue($returnValue);
5254
-    }
5255
-
5256
-    /**
5257
-     * Get built-in function
5258
-     *
5259
-     * @param string $name Normalized name
5260
-     *
5261
-     * @return array
5262
-     */
5263
-    protected function getBuiltinFunction($name)
5264
-    {
5265
-        $libName = 'lib' . preg_replace_callback(
5266
-            '/_(.)/',
5267
-            function ($m) {
5268
-                return ucfirst($m[1]);
5269
-            },
5270
-            ucfirst($name)
5271
-        );
5272
-
5273
-        return [$this, $libName];
5274
-    }
5275
-
5276
-    /**
5277
-     * Sorts keyword arguments
5278
-     *
5279
-     * @param string $functionName
5280
-     * @param array  $prototypes
5281
-     * @param array  $args
5282
-     *
5283
-     * @return array|null
5284
-     */
5285
-    protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
5286
-    {
5287
-        static $parser = null;
5288
-
5289
-        if (! isset($prototypes)) {
5290
-            $keyArgs = [];
5291
-            $posArgs = [];
5292
-
5293
-            if (\is_array($args) && \count($args) && \end($args) === static::$null) {
5294
-                array_pop($args);
5295
-            }
5296
-
5297
-            // separate positional and keyword arguments
5298
-            foreach ($args as $arg) {
5299
-                list($key, $value) = $arg;
5300
-
5301
-                if (empty($key) or empty($key[1])) {
5302
-                    $posArgs[] = empty($arg[2]) ? $value : $arg;
5303
-                } else {
5304
-                    $keyArgs[$key[1]] = $value;
5305
-                }
5306
-            }
5307
-
5308
-            return [$posArgs, $keyArgs];
5309
-        }
5310
-
5311
-        // specific cases ?
5312
-        if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5313
-            // notation 100 127 255 / 0 is in fact a simple list of 4 values
5314
-            foreach ($args as $k => $arg) {
5315
-                if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
5316
-                    $last = end($arg[1][2]);
5317
-
5318
-                    if ($last[0] === Type::T_EXPRESSION && $last[1] === '/') {
5319
-                        array_pop($arg[1][2]);
5320
-                        $arg[1][2][] = $last[2];
5321
-                        $arg[1][2][] = $last[3];
5322
-                        $args[$k] = $arg;
5323
-                    }
5324
-                }
5325
-            }
5326
-        }
5327
-
5328
-        $finalArgs = [];
5329
-
5330
-        if (! \is_array(reset($prototypes))) {
5331
-            $prototypes = [$prototypes];
5332
-        }
5333
-
5334
-        $keyArgs = [];
5335
-
5336
-        // trying each prototypes
5337
-        $prototypeHasMatch = false;
5338
-        $exceptionMessage = '';
5339
-
5340
-        foreach ($prototypes as $prototype) {
5341
-            $argDef = [];
5342
-
5343
-            foreach ($prototype as $i => $p) {
5344
-                $default = null;
5345
-                $p       = explode(':', $p, 2);
5346
-                $name    = array_shift($p);
5347
-
5348
-                if (\count($p)) {
5349
-                    $p = trim(reset($p));
5350
-
5351
-                    if ($p === 'null') {
5352
-                        // differentiate this null from the static::$null
5353
-                        $default = [Type::T_KEYWORD, 'null'];
5354
-                    } else {
5355
-                        if (\is_null($parser)) {
5356
-                            $parser = $this->parserFactory(__METHOD__);
5357
-                        }
5358
-
5359
-                        $parser->parseValue($p, $default);
5360
-                    }
5361
-                }
5362
-
5363
-                $isVariable = false;
5364
-
5365
-                if (substr($name, -3) === '...') {
5366
-                    $isVariable = true;
5367
-                    $name = substr($name, 0, -3);
5368
-                }
5369
-
5370
-                $argDef[] = [$name, $default, $isVariable];
5371
-            }
5372
-
5373
-            $ignoreCallStackMessage = $this->ignoreCallStackMessage;
5374
-            $this->ignoreCallStackMessage = true;
5375
-
5376
-            try {
5377
-                if (\count($args) > \count($argDef)) {
5378
-                    $lastDef = end($argDef);
5379
-
5380
-                    // check that last arg is not a ...
5381
-                    if (empty($lastDef[2])) {
5382
-                        throw $this->errorArgsNumber($functionName, $argDef, \count($args));
5383
-                    }
5384
-                }
5385
-                $vars = $this->applyArguments($argDef, $args, false, false);
5386
-
5387
-                // ensure all args are populated
5388
-                foreach ($prototype as $i => $p) {
5389
-                    $name = explode(':', $p)[0];
5390
-
5391
-                    if (! isset($finalArgs[$i])) {
5392
-                        $finalArgs[$i] = null;
5393
-                    }
5394
-                }
5395
-
5396
-                // apply positional args
5397
-                foreach (array_values($vars) as $i => $val) {
5398
-                    $finalArgs[$i] = $val;
5399
-                }
5400
-
5401
-                $keyArgs = array_merge($keyArgs, $vars);
5402
-                $prototypeHasMatch = true;
5403
-
5404
-                // overwrite positional args with keyword args
5405
-                foreach ($prototype as $i => $p) {
5406
-                    $name = explode(':', $p)[0];
5407
-
5408
-                    if (isset($keyArgs[$name])) {
5409
-                        $finalArgs[$i] = $keyArgs[$name];
5410
-                    }
5411
-
5412
-                    // special null value as default: translate to real null here
5413
-                    if ($finalArgs[$i] === [Type::T_KEYWORD, 'null']) {
5414
-                        $finalArgs[$i] = null;
5415
-                    }
5416
-                }
5417
-                // should we break if this prototype seems fulfilled?
5418
-            } catch (CompilerException $e) {
5419
-                $exceptionMessage = $e->getMessage();
5420
-            }
5421
-            $this->ignoreCallStackMessage = $ignoreCallStackMessage;
5422
-        }
5423
-
5424
-        if ($exceptionMessage && ! $prototypeHasMatch) {
5425
-            if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5426
-                // if var() or calc() is used as an argument, return as a css function
5427
-                foreach ($args as $arg) {
5428
-                    if ($arg[1][0] === Type::T_FUNCTION_CALL && in_array($arg[1][1], ['var'])) {
5429
-                        return null;
5430
-                    }
5431
-                }
5432
-            }
5433
-
5434
-            throw $this->error($exceptionMessage);
5435
-        }
5436
-
5437
-        return [$finalArgs, $keyArgs];
5438
-    }
5439
-
5440
-    /**
5441
-     * Apply argument values per definition
5442
-     *
5443
-     * @param array   $argDef
5444
-     * @param array   $argValues
5445
-     * @param boolean $storeInEnv
5446
-     * @param boolean $reduce
5447
-     *   only used if $storeInEnv = false
5448
-     *
5449
-     * @return array
5450
-     *
5451
-     * @throws \Exception
5452
-     */
5453
-    protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true)
5454
-    {
5455
-        $output = [];
5456
-
5457
-        if (\is_array($argValues) && \count($argValues) && end($argValues) === static::$null) {
5458
-            array_pop($argValues);
5459
-        }
5460
-
5461
-        if ($storeInEnv) {
5462
-            $storeEnv = $this->getStoreEnv();
5463
-
5464
-            $env = new Environment();
5465
-            $env->store = $storeEnv->store;
5466
-        }
5467
-
5468
-        $hasVariable = false;
5469
-        $args = [];
5470
-
5471
-        foreach ($argDef as $i => $arg) {
5472
-            list($name, $default, $isVariable) = $argDef[$i];
5473
-
5474
-            $args[$name] = [$i, $name, $default, $isVariable];
5475
-            $hasVariable |= $isVariable;
5476
-        }
5477
-
5478
-        $splatSeparator      = null;
5479
-        $keywordArgs         = [];
5480
-        $deferredKeywordArgs = [];
5481
-        $deferredNamedKeywordArgs = [];
5482
-        $remaining           = [];
5483
-        $hasKeywordArgument  = false;
5484
-
5485
-        // assign the keyword args
5486
-        foreach ((array) $argValues as $arg) {
5487
-            if (! empty($arg[0])) {
5488
-                $hasKeywordArgument = true;
5489
-
5490
-                $name = $arg[0][1];
5491
-
5492
-                if (! isset($args[$name])) {
5493
-                    foreach (array_keys($args) as $an) {
5494
-                        if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5495
-                            $name = $an;
5496
-                            break;
5497
-                        }
5498
-                    }
5499
-                }
5500
-
5501
-                if (! isset($args[$name]) || $args[$name][3]) {
5502
-                    if ($hasVariable) {
5503
-                        $deferredNamedKeywordArgs[$name] = $arg[1];
5504
-                    } else {
5505
-                        throw $this->error("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
5506
-                    }
5507
-                } elseif ($args[$name][0] < \count($remaining)) {
5508
-                    throw $this->error("The argument $%s was passed both by position and by name.", $arg[0][1]);
5509
-                } else {
5510
-                    $keywordArgs[$name] = $arg[1];
5511
-                }
5512
-            } elseif (! empty($arg[2])) {
5513
-                // $arg[2] means a var followed by ... in the arg ($list... )
5514
-                $val = $this->reduce($arg[1], true);
5515
-
5516
-                if ($val[0] === Type::T_LIST) {
5517
-                    foreach ($val[2] as $name => $item) {
5518
-                        if (! is_numeric($name)) {
5519
-                            if (! isset($args[$name])) {
5520
-                                foreach (array_keys($args) as $an) {
5521
-                                    if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5522
-                                        $name = $an;
5523
-                                        break;
5524
-                                    }
5525
-                                }
5526
-                            }
5527
-
5528
-                            if ($hasVariable) {
5529
-                                $deferredKeywordArgs[$name] = $item;
5530
-                            } else {
5531
-                                $keywordArgs[$name] = $item;
5532
-                            }
5533
-                        } else {
5534
-                            if (\is_null($splatSeparator)) {
5535
-                                $splatSeparator = $val[1];
5536
-                            }
5537
-
5538
-                            $remaining[] = $item;
5539
-                        }
5540
-                    }
5541
-                } elseif ($val[0] === Type::T_MAP) {
5542
-                    foreach ($val[1] as $i => $name) {
5543
-                        $name = $this->compileStringContent($this->coerceString($name));
5544
-                        $item = $val[2][$i];
5545
-
5546
-                        if (! is_numeric($name)) {
5547
-                            if (! isset($args[$name])) {
5548
-                                foreach (array_keys($args) as $an) {
5549
-                                    if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5550
-                                        $name = $an;
5551
-                                        break;
5552
-                                    }
5553
-                                }
5554
-                            }
5555
-
5556
-                            if ($hasVariable) {
5557
-                                $deferredKeywordArgs[$name] = $item;
5558
-                            } else {
5559
-                                $keywordArgs[$name] = $item;
5560
-                            }
5561
-                        } else {
5562
-                            if (\is_null($splatSeparator)) {
5563
-                                $splatSeparator = $val[1];
5564
-                            }
5565
-
5566
-                            $remaining[] = $item;
5567
-                        }
5568
-                    }
5569
-                } else {
5570
-                    $remaining[] = $val;
5571
-                }
5572
-            } elseif ($hasKeywordArgument) {
5573
-                throw $this->error('Positional arguments must come before keyword arguments.');
5574
-            } else {
5575
-                $remaining[] = $arg[1];
5576
-            }
5577
-        }
5578
-
5579
-        foreach ($args as $arg) {
5580
-            list($i, $name, $default, $isVariable) = $arg;
5581
-
5582
-            if ($isVariable) {
5583
-                // only if more than one arg : can not be passed as position and value
5584
-                // see https://github.com/sass/libsass/issues/2927
5585
-                if (count($args) > 1) {
5586
-                    if (isset($remaining[$i]) && isset($deferredNamedKeywordArgs[$name])) {
5587
-                        throw $this->error("The argument $%s was passed both by position and by name.", $name);
5588
-                    }
5589
-                }
5590
-
5591
-                $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , [], $isVariable];
5592
-
5593
-                for ($count = \count($remaining); $i < $count; $i++) {
5594
-                    $val[2][] = $remaining[$i];
5595
-                }
5596
-
5597
-                foreach ($deferredKeywordArgs as $itemName => $item) {
5598
-                    $val[2][$itemName] = $item;
5599
-                }
5600
-
5601
-                foreach ($deferredNamedKeywordArgs as $itemName => $item) {
5602
-                    $val[2][$itemName] = $item;
5603
-                }
5604
-            } elseif (isset($remaining[$i])) {
5605
-                $val = $remaining[$i];
5606
-            } elseif (isset($keywordArgs[$name])) {
5607
-                $val = $keywordArgs[$name];
5608
-            } elseif (! empty($default)) {
5609
-                continue;
5610
-            } else {
5611
-                throw $this->error("Missing argument $name");
5612
-            }
5613
-
5614
-            if ($storeInEnv) {
5615
-                $this->set($name, $this->reduce($val, true), true, $env);
5616
-            } else {
5617
-                $output[$name] = ($reduce ? $this->reduce($val, true) : $val);
5618
-            }
5619
-        }
5620
-
5621
-        if ($storeInEnv) {
5622
-            $storeEnv->store = $env->store;
5623
-        }
5624
-
5625
-        foreach ($args as $arg) {
5626
-            list($i, $name, $default, $isVariable) = $arg;
5627
-
5628
-            if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
5629
-                continue;
5630
-            }
5631
-
5632
-            if ($storeInEnv) {
5633
-                $this->set($name, $this->reduce($default, true), true);
5634
-            } else {
5635
-                $output[$name] = ($reduce ? $this->reduce($default, true) : $default);
5636
-            }
5637
-        }
5638
-
5639
-        return $output;
5640
-    }
5641
-
5642
-    /**
5643
-     * Coerce a php value into a scss one
5644
-     *
5645
-     * @param mixed $value
5646
-     *
5647
-     * @return array|\ScssPhp\ScssPhp\Node\Number
5648
-     */
5649
-    protected function coerceValue($value)
5650
-    {
5651
-        if (\is_array($value) || $value instanceof \ArrayAccess) {
5652
-            return $value;
5653
-        }
5654
-
5655
-        if (\is_bool($value)) {
5656
-            return $this->toBool($value);
5657
-        }
5658
-
5659
-        if (\is_null($value)) {
5660
-            return static::$null;
5661
-        }
5662
-
5663
-        if (is_numeric($value)) {
5664
-            return new Node\Number($value, '');
5665
-        }
5666
-
5667
-        if ($value === '') {
5668
-            return static::$emptyString;
5669
-        }
5670
-
5671
-        $value = [Type::T_KEYWORD, $value];
5672
-        $color = $this->coerceColor($value);
5673
-
5674
-        if ($color) {
5675
-            return $color;
5676
-        }
5677
-
5678
-        return $value;
5679
-    }
5680
-
5681
-    /**
5682
-     * Coerce something to map
5683
-     *
5684
-     * @param array $item
5685
-     *
5686
-     * @return array
5687
-     */
5688
-    protected function coerceMap($item)
5689
-    {
5690
-        if ($item[0] === Type::T_MAP) {
5691
-            return $item;
5692
-        }
5693
-
5694
-        if (
5695
-            $item[0] === static::$emptyList[0] &&
5696
-            $item[1] === static::$emptyList[1] &&
5697
-            $item[2] === static::$emptyList[2]
5698
-        ) {
5699
-            return static::$emptyMap;
5700
-        }
5701
-
5702
-        return $item;
5703
-    }
5704
-
5705
-    /**
5706
-     * Coerce something to list
5707
-     *
5708
-     * @param array   $item
5709
-     * @param string  $delim
5710
-     * @param boolean $removeTrailingNull
5711
-     *
5712
-     * @return array
5713
-     */
5714
-    protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
5715
-    {
5716
-        if (isset($item) && $item[0] === Type::T_LIST) {
5717
-            // remove trailing null from the list
5718
-            if ($removeTrailingNull && end($item[2]) === static::$null) {
5719
-                array_pop($item[2]);
5720
-            }
5721
-
5722
-            return $item;
5723
-        }
5724
-
5725
-        if (isset($item) && $item[0] === Type::T_MAP) {
5726
-            $keys = $item[1];
5727
-            $values = $item[2];
5728
-            $list = [];
5729
-
5730
-            for ($i = 0, $s = \count($keys); $i < $s; $i++) {
5731
-                $key = $keys[$i];
5732
-                $value = $values[$i];
5733
-
5734
-                switch ($key[0]) {
5735
-                    case Type::T_LIST:
5736
-                    case Type::T_MAP:
5737
-                    case Type::T_STRING:
5738
-                    case Type::T_NULL:
5739
-                        break;
5740
-
5741
-                    default:
5742
-                        $key = [Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))];
5743
-                        break;
5744
-                }
5745
-
5746
-                $list[] = [
5747
-                    Type::T_LIST,
5748
-                    '',
5749
-                    [$key, $value]
5750
-                ];
5751
-            }
5752
-
5753
-            return [Type::T_LIST, ',', $list];
5754
-        }
5755
-
5756
-        return [Type::T_LIST, $delim, ! isset($item) ? [] : [$item]];
5757
-    }
5758
-
5759
-    /**
5760
-     * Coerce color for expression
5761
-     *
5762
-     * @param array $value
5763
-     *
5764
-     * @return array|null
5765
-     */
5766
-    protected function coerceForExpression($value)
5767
-    {
5768
-        if ($color = $this->coerceColor($value)) {
5769
-            return $color;
5770
-        }
5771
-
5772
-        return $value;
5773
-    }
5774
-
5775
-    /**
5776
-     * Coerce value to color
5777
-     *
5778
-     * @param array $value
5779
-     *
5780
-     * @return array|null
5781
-     */
5782
-    protected function coerceColor($value, $inRGBFunction = false)
5783
-    {
5784
-        switch ($value[0]) {
5785
-            case Type::T_COLOR:
5786
-                for ($i = 1; $i <= 3; $i++) {
5787
-                    if (! is_numeric($value[$i])) {
5788
-                        $cv = $this->compileRGBAValue($value[$i]);
5789
-
5790
-                        if (! is_numeric($cv)) {
5791
-                            return null;
5792
-                        }
5793
-
5794
-                        $value[$i] = $cv;
5795
-                    }
5796
-
5797
-                    if (isset($value[4])) {
5798
-                        if (! is_numeric($value[4])) {
5799
-                            $cv = $this->compileRGBAValue($value[4], true);
5800
-
5801
-                            if (! is_numeric($cv)) {
5802
-                                return null;
5803
-                            }
5804
-
5805
-                            $value[4] = $cv;
5806
-                        }
5807
-                    }
5808
-                }
5809
-
5810
-                return $value;
5811
-
5812
-            case Type::T_LIST:
5813
-                if ($inRGBFunction) {
5814
-                    if (\count($value[2]) == 3 || \count($value[2]) == 4) {
5815
-                        $color = $value[2];
5816
-                        array_unshift($color, Type::T_COLOR);
5817
-
5818
-                        return $this->coerceColor($color);
5819
-                    }
5820
-                }
5821
-
5822
-                return null;
5823
-
5824
-            case Type::T_KEYWORD:
5825
-                if (! \is_string($value[1])) {
5826
-                    return null;
5827
-                }
5828
-
5829
-                $name = strtolower($value[1]);
5830
-
5831
-                // hexa color?
5832
-                if (preg_match('/^#([0-9a-f]+)$/i', $name, $m)) {
5833
-                    $nofValues = \strlen($m[1]);
5834
-
5835
-                    if (\in_array($nofValues, [3, 4, 6, 8])) {
5836
-                        $nbChannels = 3;
5837
-                        $color      = [];
5838
-                        $num        = hexdec($m[1]);
5839
-
5840
-                        switch ($nofValues) {
5841
-                            case 4:
5842
-                                $nbChannels = 4;
5843
-                                // then continuing with the case 3:
5844
-                            case 3:
5845
-                                for ($i = 0; $i < $nbChannels; $i++) {
5846
-                                    $t = $num & 0xf;
5847
-                                    array_unshift($color, $t << 4 | $t);
5848
-                                    $num >>= 4;
5849
-                                }
5850
-
5851
-                                break;
5852
-
5853
-                            case 8:
5854
-                                $nbChannels = 4;
5855
-                                // then continuing with the case 6:
5856
-                            case 6:
5857
-                                for ($i = 0; $i < $nbChannels; $i++) {
5858
-                                    array_unshift($color, $num & 0xff);
5859
-                                    $num >>= 8;
5860
-                                }
5861
-
5862
-                                break;
5863
-                        }
5864
-
5865
-                        if ($nbChannels === 4) {
5866
-                            if ($color[3] === 255) {
5867
-                                $color[3] = 1; // fully opaque
5868
-                            } else {
5869
-                                $color[3] = round($color[3] / 255, Node\Number::PRECISION);
5870
-                            }
5871
-                        }
5872
-
5873
-                        array_unshift($color, Type::T_COLOR);
5874
-
5875
-                        return $color;
5876
-                    }
5877
-                }
5878
-
5879
-                if ($rgba = Colors::colorNameToRGBa($name)) {
5880
-                    return isset($rgba[3])
5881
-                        ? [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2], $rgba[3]]
5882
-                        : [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2]];
5883
-                }
5884
-
5885
-                return null;
5886
-        }
5887
-
5888
-        return null;
5889
-    }
5890
-
5891
-    /**
5892
-     * @param integer|\ScssPhp\ScssPhp\Node\Number $value
5893
-     * @param boolean                              $isAlpha
5894
-     *
5895
-     * @return integer|mixed
5896
-     */
5897
-    protected function compileRGBAValue($value, $isAlpha = false)
5898
-    {
5899
-        if ($isAlpha) {
5900
-            return $this->compileColorPartValue($value, 0, 1, false);
5901
-        }
5902
-
5903
-        return $this->compileColorPartValue($value, 0, 255, true);
5904
-    }
5905
-
5906
-    /**
5907
-     * @param mixed         $value
5908
-     * @param integer|float $min
5909
-     * @param integer|float $max
5910
-     * @param boolean       $isInt
5911
-     * @param boolean       $clamp
5912
-     * @param boolean       $modulo
5913
-     *
5914
-     * @return integer|mixed
5915
-     */
5916
-    protected function compileColorPartValue($value, $min, $max, $isInt = true, $clamp = true, $modulo = false)
5917
-    {
5918
-        if (! is_numeric($value)) {
5919
-            if (\is_array($value)) {
5920
-                $reduced = $this->reduce($value);
5921
-
5922
-                if (\is_object($reduced) && $value->type === Type::T_NUMBER) {
5923
-                    $value = $reduced;
5924
-                }
5925
-            }
5926
-
5927
-            if (\is_object($value) && $value->type === Type::T_NUMBER) {
5928
-                $num = $value->dimension;
5929
-
5930
-                if (\count($value->units)) {
5931
-                    $unit = array_keys($value->units);
5932
-                    $unit = reset($unit);
5933
-
5934
-                    switch ($unit) {
5935
-                        case '%':
5936
-                            $num *= $max / 100;
5937
-                            break;
5938
-                        default:
5939
-                            break;
5940
-                    }
5941
-                }
5942
-
5943
-                $value = $num;
5944
-            } elseif (\is_array($value)) {
5945
-                $value = $this->compileValue($value);
5946
-            }
5947
-        }
5948
-
5949
-        if (is_numeric($value)) {
5950
-            if ($isInt) {
5951
-                $value = round($value);
5952
-            }
5953
-
5954
-            if ($clamp) {
5955
-                $value = min($max, max($min, $value));
5956
-            }
5957
-
5958
-            if ($modulo) {
5959
-                $value = $value % $max;
5960
-
5961
-                // still negative?
5962
-                while ($value < $min) {
5963
-                    $value += $max;
5964
-                }
5965
-            }
5966
-
5967
-            return $value;
5968
-        }
5969
-
5970
-        return $value;
5971
-    }
5972
-
5973
-    /**
5974
-     * Coerce value to string
5975
-     *
5976
-     * @param array $value
5977
-     *
5978
-     * @return array|null
5979
-     */
5980
-    protected function coerceString($value)
5981
-    {
5982
-        if ($value[0] === Type::T_STRING) {
5983
-            return $value;
5984
-        }
5985
-
5986
-        return [Type::T_STRING, '', [$this->compileValue($value)]];
5987
-    }
5988
-
5989
-    /**
5990
-     * Coerce value to a percentage
5991
-     *
5992
-     * @param array $value
5993
-     *
5994
-     * @return integer|float
5995
-     */
5996
-    protected function coercePercent($value)
5997
-    {
5998
-        if ($value[0] === Type::T_NUMBER) {
5999
-            if (! empty($value[2]['%'])) {
6000
-                return $value[1] / 100;
6001
-            }
6002
-
6003
-            return $value[1];
6004
-        }
6005
-
6006
-        return 0;
6007
-    }
6008
-
6009
-    /**
6010
-     * Assert value is a map
6011
-     *
6012
-     * @api
6013
-     *
6014
-     * @param array $value
6015
-     *
6016
-     * @return array
6017
-     *
6018
-     * @throws \Exception
6019
-     */
6020
-    public function assertMap($value)
6021
-    {
6022
-        $value = $this->coerceMap($value);
6023
-
6024
-        if ($value[0] !== Type::T_MAP) {
6025
-            throw $this->error('expecting map, %s received', $value[0]);
6026
-        }
6027
-
6028
-        return $value;
6029
-    }
6030
-
6031
-    /**
6032
-     * Assert value is a list
6033
-     *
6034
-     * @api
6035
-     *
6036
-     * @param array $value
6037
-     *
6038
-     * @return array
6039
-     *
6040
-     * @throws \Exception
6041
-     */
6042
-    public function assertList($value)
6043
-    {
6044
-        if ($value[0] !== Type::T_LIST) {
6045
-            throw $this->error('expecting list, %s received', $value[0]);
6046
-        }
6047
-
6048
-        return $value;
6049
-    }
6050
-
6051
-    /**
6052
-     * Assert value is a color
6053
-     *
6054
-     * @api
6055
-     *
6056
-     * @param array $value
6057
-     *
6058
-     * @return array
6059
-     *
6060
-     * @throws \Exception
6061
-     */
6062
-    public function assertColor($value)
6063
-    {
6064
-        if ($color = $this->coerceColor($value)) {
6065
-            return $color;
6066
-        }
6067
-
6068
-        throw $this->error('expecting color, %s received', $value[0]);
6069
-    }
6070
-
6071
-    /**
6072
-     * Assert value is a number
6073
-     *
6074
-     * @api
6075
-     *
6076
-     * @param array $value
6077
-     *
6078
-     * @return integer|float
6079
-     *
6080
-     * @throws \Exception
6081
-     */
6082
-    public function assertNumber($value)
6083
-    {
6084
-        if ($value[0] !== Type::T_NUMBER) {
6085
-            throw $this->error('expecting number, %s received', $value[0]);
6086
-        }
6087
-
6088
-        return $value[1];
6089
-    }
6090
-
6091
-    /**
6092
-     * Make sure a color's components don't go out of bounds
6093
-     *
6094
-     * @param array $c
6095
-     *
6096
-     * @return array
6097
-     */
6098
-    protected function fixColor($c)
6099
-    {
6100
-        foreach ([1, 2, 3] as $i) {
6101
-            if ($c[$i] < 0) {
6102
-                $c[$i] = 0;
6103
-            }
6104
-
6105
-            if ($c[$i] > 255) {
6106
-                $c[$i] = 255;
6107
-            }
6108
-        }
6109
-
6110
-        return $c;
6111
-    }
6112
-
6113
-    /**
6114
-     * Convert RGB to HSL
6115
-     *
6116
-     * @api
6117
-     *
6118
-     * @param integer $red
6119
-     * @param integer $green
6120
-     * @param integer $blue
6121
-     *
6122
-     * @return array
6123
-     */
6124
-    public function toHSL($red, $green, $blue)
6125
-    {
6126
-        $min = min($red, $green, $blue);
6127
-        $max = max($red, $green, $blue);
6128
-
6129
-        $l = $min + $max;
6130
-        $d = $max - $min;
6131
-
6132
-        if ((int) $d === 0) {
6133
-            $h = $s = 0;
6134
-        } else {
6135
-            if ($l < 255) {
6136
-                $s = $d / $l;
6137
-            } else {
6138
-                $s = $d / (510 - $l);
6139
-            }
6140
-
6141
-            if ($red == $max) {
6142
-                $h = 60 * ($green - $blue) / $d;
6143
-            } elseif ($green == $max) {
6144
-                $h = 60 * ($blue - $red) / $d + 120;
6145
-            } elseif ($blue == $max) {
6146
-                $h = 60 * ($red - $green) / $d + 240;
6147
-            }
6148
-        }
6149
-
6150
-        return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
6151
-    }
6152
-
6153
-    /**
6154
-     * Hue to RGB helper
6155
-     *
6156
-     * @param float $m1
6157
-     * @param float $m2
6158
-     * @param float $h
6159
-     *
6160
-     * @return float
6161
-     */
6162
-    protected function hueToRGB($m1, $m2, $h)
6163
-    {
6164
-        if ($h < 0) {
6165
-            $h += 1;
6166
-        } elseif ($h > 1) {
6167
-            $h -= 1;
6168
-        }
6169
-
6170
-        if ($h * 6 < 1) {
6171
-            return $m1 + ($m2 - $m1) * $h * 6;
6172
-        }
6173
-
6174
-        if ($h * 2 < 1) {
6175
-            return $m2;
6176
-        }
6177
-
6178
-        if ($h * 3 < 2) {
6179
-            return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
6180
-        }
6181
-
6182
-        return $m1;
6183
-    }
6184
-
6185
-    /**
6186
-     * Convert HSL to RGB
6187
-     *
6188
-     * @api
6189
-     *
6190
-     * @param integer $hue        H from 0 to 360
6191
-     * @param integer $saturation S from 0 to 100
6192
-     * @param integer $lightness  L from 0 to 100
6193
-     *
6194
-     * @return array
6195
-     */
6196
-    public function toRGB($hue, $saturation, $lightness)
6197
-    {
6198
-        if ($hue < 0) {
6199
-            $hue += 360;
6200
-        }
6201
-
6202
-        $h = $hue / 360;
6203
-        $s = min(100, max(0, $saturation)) / 100;
6204
-        $l = min(100, max(0, $lightness)) / 100;
6205
-
6206
-        $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
6207
-        $m1 = $l * 2 - $m2;
6208
-
6209
-        $r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255;
6210
-        $g = $this->hueToRGB($m1, $m2, $h) * 255;
6211
-        $b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255;
6212
-
6213
-        $out = [Type::T_COLOR, $r, $g, $b];
6214
-
6215
-        return $out;
6216
-    }
6217
-
6218
-    // Built in functions
6219
-
6220
-    protected static $libCall = ['name', 'args...'];
6221
-    protected function libCall($args, $kwargs)
6222
-    {
6223
-        $functionReference = $this->reduce(array_shift($args), true);
6224
-
6225
-        if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
6226
-            $name = $this->compileStringContent($this->coerceString($this->reduce($functionReference, true)));
6227
-            $warning = "DEPRECATION WARNING: Passing a string to call() is deprecated and will be illegal\n"
6228
-                . "in Sass 4.0. Use call(function-reference($name)) instead.";
6229
-            fwrite($this->stderr, "$warning\n\n");
6230
-            $functionReference = $this->libGetFunction([$functionReference]);
6231
-        }
6232
-
6233
-        if ($functionReference === static::$null) {
6234
-            return static::$null;
6235
-        }
6236
-
6237
-        if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
6238
-            throw $this->error('Function reference expected, got ' . $functionReference[0]);
6239
-        }
6240
-
6241
-        $callArgs = [];
6242
-
6243
-        // $kwargs['args'] is [Type::T_LIST, ',', [..]]
6244
-        foreach ($kwargs['args'][2] as $varname => $arg) {
6245
-            if (is_numeric($varname)) {
6246
-                $varname = null;
6247
-            } else {
6248
-                $varname = [ 'var', $varname];
6249
-            }
6250
-
6251
-            $callArgs[] = [$varname, $arg, false];
6252
-        }
6253
-
6254
-        return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
6255
-    }
6256
-
6257
-
6258
-    protected static $libGetFunction = [
6259
-        ['name'],
6260
-        ['name', 'css']
6261
-    ];
6262
-    protected function libGetFunction($args)
6263
-    {
6264
-        $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
6265
-        $isCss = false;
6266
-
6267
-        if (count($args)) {
6268
-            $isCss = $this->reduce(array_shift($args), true);
6269
-            $isCss = (($isCss === static::$true) ? true : false);
6270
-        }
6271
-
6272
-        if ($isCss) {
6273
-            return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
6274
-        }
6275
-
6276
-        return $this->getFunctionReference($name, true);
6277
-    }
6278
-
6279
-    protected static $libIf = ['condition', 'if-true', 'if-false:'];
6280
-    protected function libIf($args)
6281
-    {
6282
-        list($cond, $t, $f) = $args;
6283
-
6284
-        if (! $this->isTruthy($this->reduce($cond, true))) {
6285
-            return $this->reduce($f, true);
6286
-        }
6287
-
6288
-        return $this->reduce($t, true);
6289
-    }
6290
-
6291
-    protected static $libIndex = ['list', 'value'];
6292
-    protected function libIndex($args)
6293
-    {
6294
-        list($list, $value) = $args;
6295
-
6296
-        if (
6297
-            $list[0] === Type::T_MAP ||
6298
-            $list[0] === Type::T_STRING ||
6299
-            $list[0] === Type::T_KEYWORD ||
6300
-            $list[0] === Type::T_INTERPOLATE
6301
-        ) {
6302
-            $list = $this->coerceList($list, ' ');
6303
-        }
6304
-
6305
-        if ($list[0] !== Type::T_LIST) {
6306
-            return static::$null;
6307
-        }
6308
-
6309
-        $values = [];
6310
-
6311
-        foreach ($list[2] as $item) {
6312
-            $values[] = $this->normalizeValue($item);
6313
-        }
6314
-
6315
-        $key = array_search($this->normalizeValue($value), $values);
6316
-
6317
-        return false === $key ? static::$null : $key + 1;
6318
-    }
6319
-
6320
-    protected static $libRgb = [
6321
-        ['color'],
6322
-        ['color', 'alpha'],
6323
-        ['channels'],
6324
-        ['red', 'green', 'blue'],
6325
-        ['red', 'green', 'blue', 'alpha'] ];
6326
-    protected function libRgb($args, $kwargs, $funcName = 'rgb')
6327
-    {
6328
-        switch (\count($args)) {
6329
-            case 1:
6330
-                if (! $color = $this->coerceColor($args[0], true)) {
6331
-                    $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6332
-                }
6333
-                break;
6334
-
6335
-            case 3:
6336
-                $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
6337
-
6338
-                if (! $color = $this->coerceColor($color)) {
6339
-                    $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6340
-                }
6341
-
6342
-                return $color;
6343
-
6344
-            case 2:
6345
-                if ($color = $this->coerceColor($args[0], true)) {
6346
-                    $alpha = $this->compileRGBAValue($args[1], true);
6347
-
6348
-                    if (is_numeric($alpha)) {
6349
-                        $color[4] = $alpha;
6350
-                    } else {
6351
-                        $color = [Type::T_STRING, '',
6352
-                            [$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
6353
-                    }
6354
-                } else {
6355
-                    $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6356
-                }
6357
-                break;
6358
-
6359
-            case 4:
6360
-            default:
6361
-                $color = [Type::T_COLOR, $args[0], $args[1], $args[2], $args[3]];
6362
-
6363
-                if (! $color = $this->coerceColor($color)) {
6364
-                    $color = [Type::T_STRING, '',
6365
-                        [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6366
-                }
6367
-                break;
6368
-        }
6369
-
6370
-        return $color;
6371
-    }
6372
-
6373
-    protected static $libRgba = [
6374
-        ['color'],
6375
-        ['color', 'alpha'],
6376
-        ['channels'],
6377
-        ['red', 'green', 'blue'],
6378
-        ['red', 'green', 'blue', 'alpha'] ];
6379
-    protected function libRgba($args, $kwargs)
6380
-    {
6381
-        return $this->libRgb($args, $kwargs, 'rgba');
6382
-    }
6383
-
6384
-    // helper function for adjust_color, change_color, and scale_color
6385
-    protected function alterColor($args, $fn)
6386
-    {
6387
-        $color = $this->assertColor($args[0]);
6388
-
6389
-        foreach ([1 => 1, 2 => 2, 3 => 3, 7 => 4] as $iarg => $irgba) {
6390
-            if (isset($args[$iarg])) {
6391
-                $val = $this->assertNumber($args[$iarg]);
6392
-
6393
-                if (! isset($color[$irgba])) {
6394
-                    $color[$irgba] = (($irgba < 4) ? 0 : 1);
6395
-                }
6396
-
6397
-                $color[$irgba] = \call_user_func($fn, $color[$irgba], $val, $iarg);
6398
-            }
6399
-        }
6400
-
6401
-        if (! empty($args[4]) || ! empty($args[5]) || ! empty($args[6])) {
6402
-            $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6403
-
6404
-            foreach ([4 => 1, 5 => 2, 6 => 3] as $iarg => $ihsl) {
6405
-                if (! empty($args[$iarg])) {
6406
-                    $val = $this->assertNumber($args[$iarg]);
6407
-                    $hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
6408
-                }
6409
-            }
6410
-
6411
-            $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6412
-
6413
-            if (isset($color[4])) {
6414
-                $rgb[4] = $color[4];
6415
-            }
6416
-
6417
-            $color = $rgb;
6418
-        }
6419
-
6420
-        return $color;
6421
-    }
6422
-
6423
-    protected static $libAdjustColor = [
6424
-        'color', 'red:null', 'green:null', 'blue:null',
6425
-        'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6426
-    ];
6427
-    protected function libAdjustColor($args)
6428
-    {
6429
-        return $this->alterColor($args, function ($base, $alter, $i) {
6430
-            return $base + $alter;
6431
-        });
6432
-    }
6433
-
6434
-    protected static $libChangeColor = [
6435
-        'color', 'red:null', 'green:null', 'blue:null',
6436
-        'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6437
-    ];
6438
-    protected function libChangeColor($args)
6439
-    {
6440
-        return $this->alterColor($args, function ($base, $alter, $i) {
6441
-            return $alter;
6442
-        });
6443
-    }
6444
-
6445
-    protected static $libScaleColor = [
6446
-        'color', 'red:null', 'green:null', 'blue:null',
6447
-        'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6448
-    ];
6449
-    protected function libScaleColor($args)
6450
-    {
6451
-        return $this->alterColor($args, function ($base, $scale, $i) {
6452
-            // 1, 2, 3 - rgb
6453
-            // 4, 5, 6 - hsl
6454
-            // 7 - a
6455
-            switch ($i) {
6456
-                case 1:
6457
-                case 2:
6458
-                case 3:
6459
-                    $max = 255;
6460
-                    break;
6461
-
6462
-                case 4:
6463
-                    $max = 360;
6464
-                    break;
6465
-
6466
-                case 7:
6467
-                    $max = 1;
6468
-                    break;
6469
-
6470
-                default:
6471
-                    $max = 100;
6472
-            }
6473
-
6474
-            $scale = $scale / 100;
6475
-
6476
-            if ($scale < 0) {
6477
-                return $base * $scale + $base;
6478
-            }
6479
-
6480
-            return ($max - $base) * $scale + $base;
6481
-        });
6482
-    }
6483
-
6484
-    protected static $libIeHexStr = ['color'];
6485
-    protected function libIeHexStr($args)
6486
-    {
6487
-        $color = $this->coerceColor($args[0]);
6488
-
6489
-        if (\is_null($color)) {
6490
-            $this->throwError('Error: argument `$color` of `ie-hex-str($color)` must be a color');
6491
-        }
6492
-
6493
-        $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
6494
-
6495
-        return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
6496
-    }
6497
-
6498
-    protected static $libRed = ['color'];
6499
-    protected function libRed($args)
6500
-    {
6501
-        $color = $this->coerceColor($args[0]);
6502
-
6503
-        if (\is_null($color)) {
6504
-            $this->throwError('Error: argument `$color` of `red($color)` must be a color');
6505
-        }
6506
-
6507
-        return $color[1];
6508
-    }
6509
-
6510
-    protected static $libGreen = ['color'];
6511
-    protected function libGreen($args)
6512
-    {
6513
-        $color = $this->coerceColor($args[0]);
6514
-
6515
-        if (\is_null($color)) {
6516
-            $this->throwError('Error: argument `$color` of `green($color)` must be a color');
6517
-        }
6518
-
6519
-        return $color[2];
6520
-    }
6521
-
6522
-    protected static $libBlue = ['color'];
6523
-    protected function libBlue($args)
6524
-    {
6525
-        $color = $this->coerceColor($args[0]);
6526
-
6527
-        if (\is_null($color)) {
6528
-            $this->throwError('Error: argument `$color` of `blue($color)` must be a color');
6529
-        }
6530
-
6531
-        return $color[3];
6532
-    }
6533
-
6534
-    protected static $libAlpha = ['color'];
6535
-    protected function libAlpha($args)
6536
-    {
6537
-        if ($color = $this->coerceColor($args[0])) {
6538
-            return isset($color[4]) ? $color[4] : 1;
6539
-        }
6540
-
6541
-        // this might be the IE function, so return value unchanged
6542
-        return null;
6543
-    }
6544
-
6545
-    protected static $libOpacity = ['color'];
6546
-    protected function libOpacity($args)
6547
-    {
6548
-        $value = $args[0];
6549
-
6550
-        if ($value[0] === Type::T_NUMBER) {
6551
-            return null;
6552
-        }
6553
-
6554
-        return $this->libAlpha($args);
6555
-    }
6556
-
6557
-    // mix two colors
6558
-    protected static $libMix = [
6559
-        ['color1', 'color2', 'weight:0.5'],
6560
-        ['color-1', 'color-2', 'weight:0.5']
6561
-        ];
6562
-    protected function libMix($args)
6563
-    {
6564
-        list($first, $second, $weight) = $args;
6565
-
6566
-        $first = $this->assertColor($first);
6567
-        $second = $this->assertColor($second);
6568
-
6569
-        if (! isset($weight)) {
6570
-            $weight = 0.5;
6571
-        } else {
6572
-            $weight = $this->coercePercent($weight);
6573
-        }
6574
-
6575
-        $firstAlpha = isset($first[4]) ? $first[4] : 1;
6576
-        $secondAlpha = isset($second[4]) ? $second[4] : 1;
6577
-
6578
-        $w = $weight * 2 - 1;
6579
-        $a = $firstAlpha - $secondAlpha;
6580
-
6581
-        $w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
6582
-        $w2 = 1.0 - $w1;
6583
-
6584
-        $new = [Type::T_COLOR,
6585
-            $w1 * $first[1] + $w2 * $second[1],
6586
-            $w1 * $first[2] + $w2 * $second[2],
6587
-            $w1 * $first[3] + $w2 * $second[3],
6588
-        ];
6589
-
6590
-        if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
6591
-            $new[] = $firstAlpha * $weight + $secondAlpha * (1 - $weight);
6592
-        }
6593
-
6594
-        return $this->fixColor($new);
6595
-    }
6596
-
6597
-    protected static $libHsl = [
6598
-        ['channels'],
6599
-        ['hue', 'saturation', 'lightness'],
6600
-        ['hue', 'saturation', 'lightness', 'alpha'] ];
6601
-    protected function libHsl($args, $kwargs, $funcName = 'hsl')
6602
-    {
6603
-        $args_to_check = $args;
6604
-
6605
-        if (\count($args) == 1) {
6606
-            if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
6607
-                return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6608
-            }
6609
-
6610
-            $args = $args[0][2];
6611
-            $args_to_check = $kwargs['channels'][2];
6612
-        }
6613
-
6614
-        $hue = $this->compileColorPartValue($args[0], 0, 360, false, false, true);
6615
-        $saturation = $this->compileColorPartValue($args[1], 0, 100, false);
6616
-        $lightness = $this->compileColorPartValue($args[2], 0, 100, false);
6617
-
6618
-        foreach ($kwargs as $k => $arg) {
6619
-            if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
6620
-                return null;
6621
-            }
6622
-        }
6623
-
6624
-        foreach ($args_to_check as $k => $arg) {
6625
-            if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
6626
-                if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
6627
-                    return null;
6628
-                }
6629
-
6630
-                $args[$k] = $this->stringifyFncallArgs($arg);
6631
-                $hue = '';
6632
-            }
6633
-
6634
-            if (
6635
-                $k >= 2 && count($args) === 4 &&
6636
-                in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
6637
-                in_array($arg[1], ['calc','env'])
6638
-            ) {
6639
-                return null;
6640
-            }
6641
-        }
6642
-
6643
-        $alpha = null;
6644
-
6645
-        if (\count($args) === 4) {
6646
-            $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
6647
-
6648
-            if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness) || ! is_numeric($alpha)) {
6649
-                return [Type::T_STRING, '',
6650
-                    [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6651
-            }
6652
-        } else {
6653
-            if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness)) {
6654
-                return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6655
-            }
6656
-        }
6657
-
6658
-        $color = $this->toRGB($hue, $saturation, $lightness);
6659
-
6660
-        if (! \is_null($alpha)) {
6661
-            $color[4] = $alpha;
6662
-        }
6663
-
6664
-        return $color;
6665
-    }
6666
-
6667
-    protected static $libHsla = [
6668
-            ['channels'],
6669
-            ['hue', 'saturation', 'lightness'],
6670
-            ['hue', 'saturation', 'lightness', 'alpha']];
6671
-    protected function libHsla($args, $kwargs)
6672
-    {
6673
-        return $this->libHsl($args, $kwargs, 'hsla');
6674
-    }
6675
-
6676
-    protected static $libHue = ['color'];
6677
-    protected function libHue($args)
6678
-    {
6679
-        $color = $this->assertColor($args[0]);
6680
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6681
-
6682
-        return new Node\Number($hsl[1], 'deg');
6683
-    }
6684
-
6685
-    protected static $libSaturation = ['color'];
6686
-    protected function libSaturation($args)
6687
-    {
6688
-        $color = $this->assertColor($args[0]);
6689
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6690
-
6691
-        return new Node\Number($hsl[2], '%');
6692
-    }
6693
-
6694
-    protected static $libLightness = ['color'];
6695
-    protected function libLightness($args)
6696
-    {
6697
-        $color = $this->assertColor($args[0]);
6698
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6699
-
6700
-        return new Node\Number($hsl[3], '%');
6701
-    }
6702
-
6703
-    protected function adjustHsl($color, $idx, $amount)
6704
-    {
6705
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6706
-        $hsl[$idx] += $amount;
6707
-        $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6708
-
6709
-        if (isset($color[4])) {
6710
-            $out[4] = $color[4];
6711
-        }
6712
-
6713
-        return $out;
6714
-    }
6715
-
6716
-    protected static $libAdjustHue = ['color', 'degrees'];
6717
-    protected function libAdjustHue($args)
6718
-    {
6719
-        $color = $this->assertColor($args[0]);
6720
-        $degrees = $this->assertNumber($args[1]);
6721
-
6722
-        return $this->adjustHsl($color, 1, $degrees);
6723
-    }
6724
-
6725
-    protected static $libLighten = ['color', 'amount'];
6726
-    protected function libLighten($args)
6727
-    {
6728
-        $color = $this->assertColor($args[0]);
6729
-        $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6730
-
6731
-        return $this->adjustHsl($color, 3, $amount);
6732
-    }
6733
-
6734
-    protected static $libDarken = ['color', 'amount'];
6735
-    protected function libDarken($args)
6736
-    {
6737
-        $color = $this->assertColor($args[0]);
6738
-        $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6739
-
6740
-        return $this->adjustHsl($color, 3, -$amount);
6741
-    }
6742
-
6743
-    protected static $libSaturate = [['color', 'amount'], ['amount']];
6744
-    protected function libSaturate($args)
6745
-    {
6746
-        $value = $args[0];
6747
-
6748
-        if ($value[0] === Type::T_NUMBER) {
6749
-            return null;
6750
-        }
6751
-
6752
-        if (count($args) === 1) {
6753
-            $val = $this->compileValue($value);
6754
-            throw $this->error("\$amount: $val is not a number");
6755
-        }
6756
-
6757
-        $color = $this->assertColor($value);
6758
-        $amount = 100 * $this->coercePercent($args[1]);
6759
-
6760
-        return $this->adjustHsl($color, 2, $amount);
6761
-    }
6762
-
6763
-    protected static $libDesaturate = ['color', 'amount'];
6764
-    protected function libDesaturate($args)
6765
-    {
6766
-        $color = $this->assertColor($args[0]);
6767
-        $amount = 100 * $this->coercePercent($args[1]);
6768
-
6769
-        return $this->adjustHsl($color, 2, -$amount);
6770
-    }
6771
-
6772
-    protected static $libGrayscale = ['color'];
6773
-    protected function libGrayscale($args)
6774
-    {
6775
-        $value = $args[0];
6776
-
6777
-        if ($value[0] === Type::T_NUMBER) {
6778
-            return null;
6779
-        }
6780
-
6781
-        return $this->adjustHsl($this->assertColor($value), 2, -100);
6782
-    }
6783
-
6784
-    protected static $libComplement = ['color'];
6785
-    protected function libComplement($args)
6786
-    {
6787
-        return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
6788
-    }
6789
-
6790
-    protected static $libInvert = ['color', 'weight:1'];
6791
-    protected function libInvert($args)
6792
-    {
6793
-        list($value, $weight) = $args;
6794
-
6795
-        if (! isset($weight)) {
6796
-            $weight = 1;
6797
-        } else {
6798
-            $weight = $this->coercePercent($weight);
6799
-        }
6800
-
6801
-        if ($value[0] === Type::T_NUMBER) {
6802
-            return null;
6803
-        }
6804
-
6805
-        $color = $this->assertColor($value);
6806
-        $inverted = $color;
6807
-        $inverted[1] = 255 - $inverted[1];
6808
-        $inverted[2] = 255 - $inverted[2];
6809
-        $inverted[3] = 255 - $inverted[3];
6810
-
6811
-        if ($weight < 1) {
6812
-            return $this->libMix([$inverted, $color, [Type::T_NUMBER, $weight]]);
6813
-        }
6814
-
6815
-        return $inverted;
6816
-    }
6817
-
6818
-    // increases opacity by amount
6819
-    protected static $libOpacify = ['color', 'amount'];
6820
-    protected function libOpacify($args)
6821
-    {
6822
-        $color = $this->assertColor($args[0]);
6823
-        $amount = $this->coercePercent($args[1]);
6824
-
6825
-        $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
6826
-        $color[4] = min(1, max(0, $color[4]));
6827
-
6828
-        return $color;
6829
-    }
6830
-
6831
-    protected static $libFadeIn = ['color', 'amount'];
6832
-    protected function libFadeIn($args)
6833
-    {
6834
-        return $this->libOpacify($args);
6835
-    }
6836
-
6837
-    // decreases opacity by amount
6838
-    protected static $libTransparentize = ['color', 'amount'];
6839
-    protected function libTransparentize($args)
6840
-    {
6841
-        $color = $this->assertColor($args[0]);
6842
-        $amount = $this->coercePercent($args[1]);
6843
-
6844
-        $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
6845
-        $color[4] = min(1, max(0, $color[4]));
6846
-
6847
-        return $color;
6848
-    }
6849
-
6850
-    protected static $libFadeOut = ['color', 'amount'];
6851
-    protected function libFadeOut($args)
6852
-    {
6853
-        return $this->libTransparentize($args);
6854
-    }
6855
-
6856
-    protected static $libUnquote = ['string'];
6857
-    protected function libUnquote($args)
6858
-    {
6859
-        $str = $args[0];
6860
-
6861
-        if ($str[0] === Type::T_STRING) {
6862
-            $str[1] = '';
6863
-        }
6864
-
6865
-        return $str;
6866
-    }
6867
-
6868
-    protected static $libQuote = ['string'];
6869
-    protected function libQuote($args)
6870
-    {
6871
-        $value = $args[0];
6872
-
6873
-        if ($value[0] === Type::T_STRING && ! empty($value[1])) {
6874
-            return $value;
6875
-        }
6876
-
6877
-        return [Type::T_STRING, '"', [$value]];
6878
-    }
6879
-
6880
-    protected static $libPercentage = ['number'];
6881
-    protected function libPercentage($args)
6882
-    {
6883
-        return new Node\Number($this->coercePercent($args[0]) * 100, '%');
6884
-    }
6885
-
6886
-    protected static $libRound = ['number'];
6887
-    protected function libRound($args)
6888
-    {
6889
-        $num = $args[0];
6890
-
6891
-        return new Node\Number(round($num[1]), $num[2]);
6892
-    }
6893
-
6894
-    protected static $libFloor = ['number'];
6895
-    protected function libFloor($args)
6896
-    {
6897
-        $num = $args[0];
6898
-
6899
-        return new Node\Number(floor($num[1]), $num[2]);
6900
-    }
6901
-
6902
-    protected static $libCeil = ['number'];
6903
-    protected function libCeil($args)
6904
-    {
6905
-        $num = $args[0];
6906
-
6907
-        return new Node\Number(ceil($num[1]), $num[2]);
6908
-    }
6909
-
6910
-    protected static $libAbs = ['number'];
6911
-    protected function libAbs($args)
6912
-    {
6913
-        $num = $args[0];
6914
-
6915
-        return new Node\Number(abs($num[1]), $num[2]);
6916
-    }
6917
-
6918
-    protected function libMin($args)
6919
-    {
6920
-        $numbers = $this->getNormalizedNumbers($args);
6921
-        $minOriginal = null;
6922
-        $minNormalized = null;
6923
-
6924
-        foreach ($numbers as $key => $pair) {
6925
-            list($original, $normalized) = $pair;
6926
-
6927
-            if (\is_null($normalized) || \is_null($minNormalized)) {
6928
-                if (\is_null($minOriginal) || $original[1] <= $minOriginal[1]) {
6929
-                    $minOriginal = $original;
6930
-                    $minNormalized = $normalized;
6931
-                }
6932
-            } elseif ($normalized[1] <= $minNormalized[1]) {
6933
-                $minOriginal = $original;
6934
-                $minNormalized = $normalized;
6935
-            }
6936
-        }
6937
-
6938
-        return $minOriginal;
6939
-    }
6940
-
6941
-    protected function libMax($args)
6942
-    {
6943
-        $numbers = $this->getNormalizedNumbers($args);
6944
-        $maxOriginal = null;
6945
-        $maxNormalized = null;
6946
-
6947
-        foreach ($numbers as $key => $pair) {
6948
-            list($original, $normalized) = $pair;
6949
-
6950
-            if (\is_null($normalized) || \is_null($maxNormalized)) {
6951
-                if (\is_null($maxOriginal) || $original[1] >= $maxOriginal[1]) {
6952
-                    $maxOriginal = $original;
6953
-                    $maxNormalized = $normalized;
6954
-                }
6955
-            } elseif ($normalized[1] >= $maxNormalized[1]) {
6956
-                $maxOriginal = $original;
6957
-                $maxNormalized = $normalized;
6958
-            }
6959
-        }
6960
-
6961
-        return $maxOriginal;
6962
-    }
6963
-
6964
-    /**
6965
-     * Helper to normalize args containing numbers
6966
-     *
6967
-     * @param array $args
6968
-     *
6969
-     * @return array
6970
-     */
6971
-    protected function getNormalizedNumbers($args)
6972
-    {
6973
-        $unit         = null;
6974
-        $originalUnit = null;
6975
-        $numbers      = [];
6976
-
6977
-        foreach ($args as $key => $item) {
6978
-            if ($item[0] !== Type::T_NUMBER) {
6979
-                throw $this->error('%s is not a number', $item[0]);
6980
-            }
6981
-
6982
-            $number = $item->normalize();
6983
-
6984
-            if (empty($unit)) {
6985
-                $unit = $number[2];
6986
-                $originalUnit = $item->unitStr();
6987
-            } elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
6988
-                throw $this->error('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
6989
-            }
6990
-
6991
-            $numbers[$key] = [$args[$key], empty($number[2]) ? null : $number];
6992
-        }
6993
-
6994
-        return $numbers;
6995
-    }
6996
-
6997
-    protected static $libLength = ['list'];
6998
-    protected function libLength($args)
6999
-    {
7000
-        $list = $this->coerceList($args[0], ',', true);
7001
-
7002
-        return \count($list[2]);
7003
-    }
7004
-
7005
-    //protected static $libListSeparator = ['list...'];
7006
-    protected function libListSeparator($args)
7007
-    {
7008
-        if (\count($args) > 1) {
7009
-            return 'comma';
7010
-        }
7011
-
7012
-        $list = $this->coerceList($args[0]);
7013
-
7014
-        if (\count($list[2]) <= 1) {
7015
-            return 'space';
7016
-        }
7017
-
7018
-        if ($list[1] === ',') {
7019
-            return 'comma';
7020
-        }
7021
-
7022
-        return 'space';
7023
-    }
7024
-
7025
-    protected static $libNth = ['list', 'n'];
7026
-    protected function libNth($args)
7027
-    {
7028
-        $list = $this->coerceList($args[0], ',', false);
7029
-        $n = $this->assertNumber($args[1]);
7030
-
7031
-        if ($n > 0) {
7032
-            $n--;
7033
-        } elseif ($n < 0) {
7034
-            $n += \count($list[2]);
7035
-        }
7036
-
7037
-        return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue;
7038
-    }
7039
-
7040
-    protected static $libSetNth = ['list', 'n', 'value'];
7041
-    protected function libSetNth($args)
7042
-    {
7043
-        $list = $this->coerceList($args[0]);
7044
-        $n = $this->assertNumber($args[1]);
7045
-
7046
-        if ($n > 0) {
7047
-            $n--;
7048
-        } elseif ($n < 0) {
7049
-            $n += \count($list[2]);
7050
-        }
7051
-
7052
-        if (! isset($list[2][$n])) {
7053
-            throw $this->error('Invalid argument for "n"');
7054
-        }
7055
-
7056
-        $list[2][$n] = $args[2];
7057
-
7058
-        return $list;
7059
-    }
7060
-
7061
-    protected static $libMapGet = ['map', 'key'];
7062
-    protected function libMapGet($args)
7063
-    {
7064
-        $map = $this->assertMap($args[0]);
7065
-        $key = $args[1];
7066
-
7067
-        if (! \is_null($key)) {
7068
-            $key = $this->compileStringContent($this->coerceString($key));
7069
-
7070
-            for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7071
-                if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
7072
-                    return $map[2][$i];
7073
-                }
7074
-            }
7075
-        }
7076
-
7077
-        return static::$null;
7078
-    }
7079
-
7080
-    protected static $libMapKeys = ['map'];
7081
-    protected function libMapKeys($args)
7082
-    {
7083
-        $map = $this->assertMap($args[0]);
7084
-        $keys = $map[1];
7085
-
7086
-        return [Type::T_LIST, ',', $keys];
7087
-    }
7088
-
7089
-    protected static $libMapValues = ['map'];
7090
-    protected function libMapValues($args)
7091
-    {
7092
-        $map = $this->assertMap($args[0]);
7093
-        $values = $map[2];
7094
-
7095
-        return [Type::T_LIST, ',', $values];
7096
-    }
7097
-
7098
-    protected static $libMapRemove = ['map', 'key...'];
7099
-    protected function libMapRemove($args)
7100
-    {
7101
-        $map = $this->assertMap($args[0]);
7102
-        $keyList = $this->assertList($args[1]);
7103
-
7104
-        $keys = [];
7105
-
7106
-        foreach ($keyList[2] as $key) {
7107
-            $keys[] = $this->compileStringContent($this->coerceString($key));
7108
-        }
7109
-
7110
-        for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7111
-            if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) {
7112
-                array_splice($map[1], $i, 1);
7113
-                array_splice($map[2], $i, 1);
7114
-            }
7115
-        }
7116
-
7117
-        return $map;
7118
-    }
7119
-
7120
-    protected static $libMapHasKey = ['map', 'key'];
7121
-    protected function libMapHasKey($args)
7122
-    {
7123
-        $map = $this->assertMap($args[0]);
7124
-        $key = $this->compileStringContent($this->coerceString($args[1]));
7125
-
7126
-        for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7127
-            if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
7128
-                return true;
7129
-            }
7130
-        }
7131
-
7132
-        return false;
7133
-    }
7134
-
7135
-    protected static $libMapMerge = [
7136
-        ['map1', 'map2'],
7137
-        ['map-1', 'map-2']
7138
-    ];
7139
-    protected function libMapMerge($args)
7140
-    {
7141
-        $map1 = $this->assertMap($args[0]);
7142
-        $map2 = $this->assertMap($args[1]);
7143
-
7144
-        foreach ($map2[1] as $i2 => $key2) {
7145
-            $key = $this->compileStringContent($this->coerceString($key2));
7146
-
7147
-            foreach ($map1[1] as $i1 => $key1) {
7148
-                if ($key === $this->compileStringContent($this->coerceString($key1))) {
7149
-                    $map1[2][$i1] = $map2[2][$i2];
7150
-                    continue 2;
7151
-                }
7152
-            }
7153
-
7154
-            $map1[1][] = $map2[1][$i2];
7155
-            $map1[2][] = $map2[2][$i2];
7156
-        }
7157
-
7158
-        return $map1;
7159
-    }
7160
-
7161
-    protected static $libKeywords = ['args'];
7162
-    protected function libKeywords($args)
7163
-    {
7164
-        $this->assertList($args[0]);
7165
-
7166
-        $keys = [];
7167
-        $values = [];
7168
-
7169
-        foreach ($args[0][2] as $name => $arg) {
7170
-            $keys[] = [Type::T_KEYWORD, $name];
7171
-            $values[] = $arg;
7172
-        }
7173
-
7174
-        return [Type::T_MAP, $keys, $values];
7175
-    }
7176
-
7177
-    protected static $libIsBracketed = ['list'];
7178
-    protected function libIsBracketed($args)
7179
-    {
7180
-        $list = $args[0];
7181
-        $this->coerceList($list, ' ');
7182
-
7183
-        if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
7184
-            return true;
7185
-        }
7186
-
7187
-        return false;
7188
-    }
7189
-
7190
-    protected function listSeparatorForJoin($list1, $sep)
7191
-    {
7192
-        if (! isset($sep)) {
7193
-            return $list1[1];
7194
-        }
7195
-
7196
-        switch ($this->compileValue($sep)) {
7197
-            case 'comma':
7198
-                return ',';
7199
-
7200
-            case 'space':
7201
-                return ' ';
7202
-
7203
-            default:
7204
-                return $list1[1];
7205
-        }
7206
-    }
7207
-
7208
-    protected static $libJoin = ['list1', 'list2', 'separator:null', 'bracketed:auto'];
7209
-    protected function libJoin($args)
7210
-    {
7211
-        list($list1, $list2, $sep, $bracketed) = $args;
7212
-
7213
-        $list1 = $this->coerceList($list1, ' ', true);
7214
-        $list2 = $this->coerceList($list2, ' ', true);
7215
-        $sep   = $this->listSeparatorForJoin($list1, $sep);
7216
-
7217
-        if ($bracketed === static::$true) {
7218
-            $bracketed = true;
7219
-        } elseif ($bracketed === static::$false) {
7220
-            $bracketed = false;
7221
-        } elseif ($bracketed === [Type::T_KEYWORD, 'auto']) {
7222
-            $bracketed = 'auto';
7223
-        } elseif ($bracketed === static::$null) {
7224
-            $bracketed = false;
7225
-        } else {
7226
-            $bracketed = $this->compileValue($bracketed);
7227
-            $bracketed = ! ! $bracketed;
7228
-
7229
-            if ($bracketed === true) {
7230
-                $bracketed = true;
7231
-            }
7232
-        }
7233
-
7234
-        if ($bracketed === 'auto') {
7235
-            $bracketed = false;
7236
-
7237
-            if (! empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') {
7238
-                $bracketed = true;
7239
-            }
7240
-        }
7241
-
7242
-        $res = [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])];
7243
-
7244
-        if (isset($list1['enclosing'])) {
7245
-            $res['enlcosing'] = $list1['enclosing'];
7246
-        }
7247
-
7248
-        if ($bracketed) {
7249
-            $res['enclosing'] = 'bracket';
7250
-        }
7251
-
7252
-        return $res;
7253
-    }
7254
-
7255
-    protected static $libAppend = ['list', 'val', 'separator:null'];
7256
-    protected function libAppend($args)
7257
-    {
7258
-        list($list1, $value, $sep) = $args;
7259
-
7260
-        $list1 = $this->coerceList($list1, ' ', true);
7261
-        $sep   = $this->listSeparatorForJoin($list1, $sep);
7262
-        $res   = [Type::T_LIST, $sep, array_merge($list1[2], [$value])];
7263
-
7264
-        if (isset($list1['enclosing'])) {
7265
-            $res['enclosing'] = $list1['enclosing'];
7266
-        }
7267
-
7268
-        return $res;
7269
-    }
7270
-
7271
-    protected function libZip($args)
7272
-    {
7273
-        foreach ($args as $key => $arg) {
7274
-            $args[$key] = $this->coerceList($arg);
7275
-        }
7276
-
7277
-        $lists = [];
7278
-        $firstList = array_shift($args);
7279
-
7280
-        foreach ($firstList[2] as $key => $item) {
7281
-            $list = [Type::T_LIST, '', [$item]];
7282
-
7283
-            foreach ($args as $arg) {
7284
-                if (isset($arg[2][$key])) {
7285
-                    $list[2][] = $arg[2][$key];
7286
-                } else {
7287
-                    break 2;
7288
-                }
7289
-            }
7290
-
7291
-            $lists[] = $list;
7292
-        }
7293
-
7294
-        return [Type::T_LIST, ',', $lists];
7295
-    }
7296
-
7297
-    protected static $libTypeOf = ['value'];
7298
-    protected function libTypeOf($args)
7299
-    {
7300
-        $value = $args[0];
7301
-
7302
-        switch ($value[0]) {
7303
-            case Type::T_KEYWORD:
7304
-                if ($value === static::$true || $value === static::$false) {
7305
-                    return 'bool';
7306
-                }
7307
-
7308
-                if ($this->coerceColor($value)) {
7309
-                    return 'color';
7310
-                }
7311
-
7312
-                // fall-thru
7313
-            case Type::T_FUNCTION:
7314
-                return 'string';
7315
-
7316
-            case Type::T_FUNCTION_REFERENCE:
7317
-                return 'function';
7318
-
7319
-            case Type::T_LIST:
7320
-                if (isset($value[3]) && $value[3]) {
7321
-                    return 'arglist';
7322
-                }
7323
-
7324
-                // fall-thru
7325
-            default:
7326
-                return $value[0];
7327
-        }
7328
-    }
7329
-
7330
-    protected static $libUnit = ['number'];
7331
-    protected function libUnit($args)
7332
-    {
7333
-        $num = $args[0];
7334
-
7335
-        if ($num[0] === Type::T_NUMBER) {
7336
-            return [Type::T_STRING, '"', [$num->unitStr()]];
7337
-        }
7338
-
7339
-        return '';
7340
-    }
4082
+				$right = \count($right[2]) > 0 ?
4083
+					$whiteRight . $delim . $this->compileValue($right) : '';
4084
+
4085
+				return $left . $this->compileValue($interpolate) . $right;
4086
+
4087
+			case Type::T_INTERPOLATE:
4088
+				// strip quotes if it's a string
4089
+				$reduced = $this->reduce($value[1]);
4090
+
4091
+				switch ($reduced[0]) {
4092
+					case Type::T_LIST:
4093
+						$reduced = $this->extractInterpolation($reduced);
4094
+
4095
+						if ($reduced[0] !== Type::T_LIST) {
4096
+							break;
4097
+						}
4098
+
4099
+						list(, $delim, $items) = $reduced;
4100
+
4101
+						if ($delim !== ' ') {
4102
+							$delim .= ' ';
4103
+						}
4104
+
4105
+						$filtered = [];
4106
+
4107
+						foreach ($items as $item) {
4108
+							if ($item[0] === Type::T_NULL) {
4109
+								continue;
4110
+							}
4111
+
4112
+							$temp = $this->compileValue([Type::T_KEYWORD, $item]);
4113
+
4114
+							if ($temp[0] === Type::T_STRING) {
4115
+								$filtered[] = $this->compileStringContent($temp);
4116
+							} elseif ($temp[0] === Type::T_KEYWORD) {
4117
+								$filtered[] = $temp[1];
4118
+							} else {
4119
+								$filtered[] = $this->compileValue($temp);
4120
+							}
4121
+						}
4122
+
4123
+						$reduced = [Type::T_KEYWORD, implode("$delim", $filtered)];
4124
+						break;
4125
+
4126
+					case Type::T_STRING:
4127
+						$reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
4128
+						break;
4129
+
4130
+					case Type::T_NULL:
4131
+						$reduced = [Type::T_KEYWORD, ''];
4132
+				}
4133
+
4134
+				return $this->compileValue($reduced);
4135
+
4136
+			case Type::T_NULL:
4137
+				return 'null';
4138
+
4139
+			case Type::T_COMMENT:
4140
+				return $this->compileCommentValue($value);
4141
+
4142
+			default:
4143
+				throw $this->error('unknown value type: ' . json_encode($value));
4144
+		}
4145
+	}
4146
+
4147
+	/**
4148
+	 * @param array $value
4149
+	 *
4150
+	 * @return array|string
4151
+	 */
4152
+	protected function compileDebugValue($value)
4153
+	{
4154
+		$value = $this->reduce($value, true);
4155
+
4156
+		switch ($value[0]) {
4157
+			case Type::T_STRING:
4158
+				return $this->compileStringContent($value);
4159
+
4160
+			default:
4161
+				return $this->compileValue($value);
4162
+		}
4163
+	}
4164
+
4165
+	/**
4166
+	 * Flatten list
4167
+	 *
4168
+	 * @param array $list
4169
+	 *
4170
+	 * @return string
4171
+	 */
4172
+	protected function flattenList($list)
4173
+	{
4174
+		return $this->compileValue($list);
4175
+	}
4176
+
4177
+	/**
4178
+	 * Compile string content
4179
+	 *
4180
+	 * @param array $string
4181
+	 *
4182
+	 * @return string
4183
+	 */
4184
+	protected function compileStringContent($string)
4185
+	{
4186
+		$parts = [];
4187
+
4188
+		foreach ($string[2] as $part) {
4189
+			if (\is_array($part) || $part instanceof \ArrayAccess) {
4190
+				$parts[] = $this->compileValue($part);
4191
+			} else {
4192
+				$parts[] = $part;
4193
+			}
4194
+		}
4195
+
4196
+		return implode($parts);
4197
+	}
4198
+
4199
+	/**
4200
+	 * Extract interpolation; it doesn't need to be recursive, compileValue will handle that
4201
+	 *
4202
+	 * @param array $list
4203
+	 *
4204
+	 * @return array
4205
+	 */
4206
+	protected function extractInterpolation($list)
4207
+	{
4208
+		$items = $list[2];
4209
+
4210
+		foreach ($items as $i => $item) {
4211
+			if ($item[0] === Type::T_INTERPOLATE) {
4212
+				$before = [Type::T_LIST, $list[1], \array_slice($items, 0, $i)];
4213
+				$after  = [Type::T_LIST, $list[1], \array_slice($items, $i + 1)];
4214
+
4215
+				return [Type::T_INTERPOLATED, $item, $before, $after];
4216
+			}
4217
+		}
4218
+
4219
+		return $list;
4220
+	}
4221
+
4222
+	/**
4223
+	 * Find the final set of selectors
4224
+	 *
4225
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4226
+	 * @param \ScssPhp\ScssPhp\Block                $selfParent
4227
+	 *
4228
+	 * @return array
4229
+	 */
4230
+	protected function multiplySelectors(Environment $env, $selfParent = null)
4231
+	{
4232
+		$envs            = $this->compactEnv($env);
4233
+		$selectors       = [];
4234
+		$parentSelectors = [[]];
4235
+
4236
+		$selfParentSelectors = null;
4237
+
4238
+		if (! \is_null($selfParent) && $selfParent->selectors) {
4239
+			$selfParentSelectors = $this->evalSelectors($selfParent->selectors);
4240
+		}
4241
+
4242
+		while ($env = array_pop($envs)) {
4243
+			if (empty($env->selectors)) {
4244
+				continue;
4245
+			}
4246
+
4247
+			$selectors = $env->selectors;
4248
+
4249
+			do {
4250
+				$stillHasSelf  = false;
4251
+				$prevSelectors = $selectors;
4252
+				$selectors     = [];
4253
+
4254
+				foreach ($parentSelectors as $parent) {
4255
+					foreach ($prevSelectors as $selector) {
4256
+						if ($selfParentSelectors) {
4257
+							foreach ($selfParentSelectors as $selfParent) {
4258
+								// if no '&' in the selector, each call will give same result, only add once
4259
+								$s = $this->joinSelectors($parent, $selector, $stillHasSelf, $selfParent);
4260
+								$selectors[serialize($s)] = $s;
4261
+							}
4262
+						} else {
4263
+							$s = $this->joinSelectors($parent, $selector, $stillHasSelf);
4264
+							$selectors[serialize($s)] = $s;
4265
+						}
4266
+					}
4267
+				}
4268
+			} while ($stillHasSelf);
4269
+
4270
+			$parentSelectors = $selectors;
4271
+		}
4272
+
4273
+		$selectors = array_values($selectors);
4274
+
4275
+		// case we are just starting a at-root : nothing to multiply but parentSelectors
4276
+		if (! $selectors && $selfParentSelectors) {
4277
+			$selectors = $selfParentSelectors;
4278
+		}
4279
+
4280
+		return $selectors;
4281
+	}
4282
+
4283
+	/**
4284
+	 * Join selectors; looks for & to replace, or append parent before child
4285
+	 *
4286
+	 * @param array   $parent
4287
+	 * @param array   $child
4288
+	 * @param boolean $stillHasSelf
4289
+	 * @param array   $selfParentSelectors
4290
+	 * @return array
4291
+	 */
4292
+	protected function joinSelectors($parent, $child, &$stillHasSelf, $selfParentSelectors = null)
4293
+	{
4294
+		$setSelf = false;
4295
+		$out = [];
4296
+
4297
+		foreach ($child as $part) {
4298
+			$newPart = [];
4299
+
4300
+			foreach ($part as $p) {
4301
+				// only replace & once and should be recalled to be able to make combinations
4302
+				if ($p === static::$selfSelector && $setSelf) {
4303
+					$stillHasSelf = true;
4304
+				}
4305
+
4306
+				if ($p === static::$selfSelector && ! $setSelf) {
4307
+					$setSelf = true;
4308
+
4309
+					if (\is_null($selfParentSelectors)) {
4310
+						$selfParentSelectors = $parent;
4311
+					}
4312
+
4313
+					foreach ($selfParentSelectors as $i => $parentPart) {
4314
+						if ($i > 0) {
4315
+							$out[] = $newPart;
4316
+							$newPart = [];
4317
+						}
4318
+
4319
+						foreach ($parentPart as $pp) {
4320
+							if (\is_array($pp)) {
4321
+								$flatten = [];
4322
+
4323
+								array_walk_recursive($pp, function ($a) use (&$flatten) {
4324
+									$flatten[] = $a;
4325
+								});
4326
+
4327
+								$pp = implode($flatten);
4328
+							}
4329
+
4330
+							$newPart[] = $pp;
4331
+						}
4332
+					}
4333
+				} else {
4334
+					$newPart[] = $p;
4335
+				}
4336
+			}
4337
+
4338
+			$out[] = $newPart;
4339
+		}
4340
+
4341
+		return $setSelf ? $out : array_merge($parent, $child);
4342
+	}
4343
+
4344
+	/**
4345
+	 * Multiply media
4346
+	 *
4347
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4348
+	 * @param array                                 $childQueries
4349
+	 *
4350
+	 * @return array
4351
+	 */
4352
+	protected function multiplyMedia(Environment $env = null, $childQueries = null)
4353
+	{
4354
+		if (
4355
+			! isset($env) ||
4356
+			! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4357
+		) {
4358
+			return $childQueries;
4359
+		}
4360
+
4361
+		// plain old block, skip
4362
+		if (empty($env->block->type)) {
4363
+			return $this->multiplyMedia($env->parent, $childQueries);
4364
+		}
4365
+
4366
+		$parentQueries = isset($env->block->queryList)
4367
+			? $env->block->queryList
4368
+			: [[[Type::T_MEDIA_VALUE, $env->block->value]]];
4369
+
4370
+		$store = [$this->env, $this->storeEnv];
4371
+
4372
+		$this->env      = $env;
4373
+		$this->storeEnv = null;
4374
+		$parentQueries  = $this->evaluateMediaQuery($parentQueries);
4375
+
4376
+		list($this->env, $this->storeEnv) = $store;
4377
+
4378
+		if (\is_null($childQueries)) {
4379
+			$childQueries = $parentQueries;
4380
+		} else {
4381
+			$originalQueries = $childQueries;
4382
+			$childQueries = [];
4383
+
4384
+			foreach ($parentQueries as $parentQuery) {
4385
+				foreach ($originalQueries as $childQuery) {
4386
+					$childQueries[] = array_merge(
4387
+						$parentQuery,
4388
+						[[Type::T_MEDIA_TYPE, [Type::T_KEYWORD, 'all']]],
4389
+						$childQuery
4390
+					);
4391
+				}
4392
+			}
4393
+		}
4394
+
4395
+		return $this->multiplyMedia($env->parent, $childQueries);
4396
+	}
4397
+
4398
+	/**
4399
+	 * Convert env linked list to stack
4400
+	 *
4401
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4402
+	 *
4403
+	 * @return array
4404
+	 */
4405
+	protected function compactEnv(Environment $env)
4406
+	{
4407
+		for ($envs = []; $env; $env = $env->parent) {
4408
+			$envs[] = $env;
4409
+		}
4410
+
4411
+		return $envs;
4412
+	}
4413
+
4414
+	/**
4415
+	 * Convert env stack to singly linked list
4416
+	 *
4417
+	 * @param array $envs
4418
+	 *
4419
+	 * @return \ScssPhp\ScssPhp\Compiler\Environment
4420
+	 */
4421
+	protected function extractEnv($envs)
4422
+	{
4423
+		for ($env = null; $e = array_pop($envs);) {
4424
+			$e->parent = $env;
4425
+			$env = $e;
4426
+		}
4427
+
4428
+		return $env;
4429
+	}
4430
+
4431
+	/**
4432
+	 * Push environment
4433
+	 *
4434
+	 * @param \ScssPhp\ScssPhp\Block $block
4435
+	 *
4436
+	 * @return \ScssPhp\ScssPhp\Compiler\Environment
4437
+	 */
4438
+	protected function pushEnv(Block $block = null)
4439
+	{
4440
+		$env = new Environment();
4441
+		$env->parent = $this->env;
4442
+		$env->parentStore = $this->storeEnv;
4443
+		$env->store  = [];
4444
+		$env->block  = $block;
4445
+		$env->depth  = isset($this->env->depth) ? $this->env->depth + 1 : 0;
4446
+
4447
+		$this->env = $env;
4448
+		$this->storeEnv = null;
4449
+
4450
+		return $env;
4451
+	}
4452
+
4453
+	/**
4454
+	 * Pop environment
4455
+	 */
4456
+	protected function popEnv()
4457
+	{
4458
+		$this->storeEnv = $this->env->parentStore;
4459
+		$this->env = $this->env->parent;
4460
+	}
4461
+
4462
+	/**
4463
+	 * Propagate vars from a just poped Env (used in @each and @for)
4464
+	 *
4465
+	 * @param array      $store
4466
+	 * @param null|array $excludedVars
4467
+	 */
4468
+	protected function backPropagateEnv($store, $excludedVars = null)
4469
+	{
4470
+		foreach ($store as $key => $value) {
4471
+			if (empty($excludedVars) || ! \in_array($key, $excludedVars)) {
4472
+				$this->set($key, $value, true);
4473
+			}
4474
+		}
4475
+	}
4476
+
4477
+	/**
4478
+	 * Get store environment
4479
+	 *
4480
+	 * @return \ScssPhp\ScssPhp\Compiler\Environment
4481
+	 */
4482
+	protected function getStoreEnv()
4483
+	{
4484
+		return isset($this->storeEnv) ? $this->storeEnv : $this->env;
4485
+	}
4486
+
4487
+	/**
4488
+	 * Set variable
4489
+	 *
4490
+	 * @param string                                $name
4491
+	 * @param mixed                                 $value
4492
+	 * @param boolean                               $shadow
4493
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4494
+	 * @param mixed                                 $valueUnreduced
4495
+	 */
4496
+	protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
4497
+	{
4498
+		$name = $this->normalizeName($name);
4499
+
4500
+		if (! isset($env)) {
4501
+			$env = $this->getStoreEnv();
4502
+		}
4503
+
4504
+		if ($shadow) {
4505
+			$this->setRaw($name, $value, $env, $valueUnreduced);
4506
+		} else {
4507
+			$this->setExisting($name, $value, $env, $valueUnreduced);
4508
+		}
4509
+	}
4510
+
4511
+	/**
4512
+	 * Set existing variable
4513
+	 *
4514
+	 * @param string                                $name
4515
+	 * @param mixed                                 $value
4516
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4517
+	 * @param mixed                                 $valueUnreduced
4518
+	 */
4519
+	protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
4520
+	{
4521
+		$storeEnv = $env;
4522
+		$specialContentKey = static::$namespaces['special'] . 'content';
4523
+
4524
+		$hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%';
4525
+
4526
+		$maxDepth = 10000;
4527
+
4528
+		for (;;) {
4529
+			if ($maxDepth-- <= 0) {
4530
+				break;
4531
+			}
4532
+
4533
+			if (\array_key_exists($name, $env->store)) {
4534
+				break;
4535
+			}
4536
+
4537
+			if (! $hasNamespace && isset($env->marker)) {
4538
+				if (! empty($env->store[$specialContentKey])) {
4539
+					$env = $env->store[$specialContentKey]->scope;
4540
+					continue;
4541
+				}
4542
+
4543
+				if (! empty($env->declarationScopeParent)) {
4544
+					$env = $env->declarationScopeParent;
4545
+					continue;
4546
+				} else {
4547
+					$env = $storeEnv;
4548
+					break;
4549
+				}
4550
+			}
4551
+
4552
+			if (isset($env->parentStore)) {
4553
+				$env = $env->parentStore;
4554
+			} elseif (isset($env->parent)) {
4555
+				$env = $env->parent;
4556
+			} else {
4557
+				$env = $storeEnv;
4558
+				break;
4559
+			}
4560
+		}
4561
+
4562
+		$env->store[$name] = $value;
4563
+
4564
+		if ($valueUnreduced) {
4565
+			$env->storeUnreduced[$name] = $valueUnreduced;
4566
+		}
4567
+	}
4568
+
4569
+	/**
4570
+	 * Set raw variable
4571
+	 *
4572
+	 * @param string                                $name
4573
+	 * @param mixed                                 $value
4574
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4575
+	 * @param mixed                                 $valueUnreduced
4576
+	 */
4577
+	protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
4578
+	{
4579
+		$env->store[$name] = $value;
4580
+
4581
+		if ($valueUnreduced) {
4582
+			$env->storeUnreduced[$name] = $valueUnreduced;
4583
+		}
4584
+	}
4585
+
4586
+	/**
4587
+	 * Get variable
4588
+	 *
4589
+	 * @api
4590
+	 *
4591
+	 * @param string                                $name
4592
+	 * @param boolean                               $shouldThrow
4593
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4594
+	 * @param boolean                               $unreduced
4595
+	 *
4596
+	 * @return mixed|null
4597
+	 */
4598
+	public function get($name, $shouldThrow = true, Environment $env = null, $unreduced = false)
4599
+	{
4600
+		$normalizedName = $this->normalizeName($name);
4601
+		$specialContentKey = static::$namespaces['special'] . 'content';
4602
+
4603
+		if (! isset($env)) {
4604
+			$env = $this->getStoreEnv();
4605
+		}
4606
+
4607
+		$hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%';
4608
+
4609
+		$maxDepth = 10000;
4610
+
4611
+		for (;;) {
4612
+			if ($maxDepth-- <= 0) {
4613
+				break;
4614
+			}
4615
+
4616
+			if (\array_key_exists($normalizedName, $env->store)) {
4617
+				if ($unreduced && isset($env->storeUnreduced[$normalizedName])) {
4618
+					return $env->storeUnreduced[$normalizedName];
4619
+				}
4620
+
4621
+				return $env->store[$normalizedName];
4622
+			}
4623
+
4624
+			if (! $hasNamespace && isset($env->marker)) {
4625
+				if (! empty($env->store[$specialContentKey])) {
4626
+					$env = $env->store[$specialContentKey]->scope;
4627
+					continue;
4628
+				}
4629
+
4630
+				if (! empty($env->declarationScopeParent)) {
4631
+					$env = $env->declarationScopeParent;
4632
+				} else {
4633
+					$env = $this->rootEnv;
4634
+				}
4635
+				continue;
4636
+			}
4637
+
4638
+			if (isset($env->parentStore)) {
4639
+				$env = $env->parentStore;
4640
+			} elseif (isset($env->parent)) {
4641
+				$env = $env->parent;
4642
+			} else {
4643
+				break;
4644
+			}
4645
+		}
4646
+
4647
+		if ($shouldThrow) {
4648
+			throw $this->error("Undefined variable \$$name" . ($maxDepth <= 0 ? ' (infinite recursion)' : ''));
4649
+		}
4650
+
4651
+		// found nothing
4652
+		return null;
4653
+	}
4654
+
4655
+	/**
4656
+	 * Has variable?
4657
+	 *
4658
+	 * @param string                                $name
4659
+	 * @param \ScssPhp\ScssPhp\Compiler\Environment $env
4660
+	 *
4661
+	 * @return boolean
4662
+	 */
4663
+	protected function has($name, Environment $env = null)
4664
+	{
4665
+		return ! \is_null($this->get($name, false, $env));
4666
+	}
4667
+
4668
+	/**
4669
+	 * Inject variables
4670
+	 *
4671
+	 * @param array $args
4672
+	 */
4673
+	protected function injectVariables(array $args)
4674
+	{
4675
+		if (empty($args)) {
4676
+			return;
4677
+		}
4678
+
4679
+		$parser = $this->parserFactory(__METHOD__);
4680
+
4681
+		foreach ($args as $name => $strValue) {
4682
+			if ($name[0] === '$') {
4683
+				$name = substr($name, 1);
4684
+			}
4685
+
4686
+			if (! $parser->parseValue($strValue, $value)) {
4687
+				$value = $this->coerceValue($strValue);
4688
+			}
4689
+
4690
+			$this->set($name, $value);
4691
+		}
4692
+	}
4693
+
4694
+	/**
4695
+	 * Set variables
4696
+	 *
4697
+	 * @api
4698
+	 *
4699
+	 * @param array $variables
4700
+	 */
4701
+	public function setVariables(array $variables)
4702
+	{
4703
+		$this->registeredVars = array_merge($this->registeredVars, $variables);
4704
+	}
4705
+
4706
+	/**
4707
+	 * Unset variable
4708
+	 *
4709
+	 * @api
4710
+	 *
4711
+	 * @param string $name
4712
+	 */
4713
+	public function unsetVariable($name)
4714
+	{
4715
+		unset($this->registeredVars[$name]);
4716
+	}
4717
+
4718
+	/**
4719
+	 * Returns list of variables
4720
+	 *
4721
+	 * @api
4722
+	 *
4723
+	 * @return array
4724
+	 */
4725
+	public function getVariables()
4726
+	{
4727
+		return $this->registeredVars;
4728
+	}
4729
+
4730
+	/**
4731
+	 * Adds to list of parsed files
4732
+	 *
4733
+	 * @api
4734
+	 *
4735
+	 * @param string $path
4736
+	 */
4737
+	public function addParsedFile($path)
4738
+	{
4739
+		if (isset($path) && is_file($path)) {
4740
+			$this->parsedFiles[realpath($path)] = filemtime($path);
4741
+		}
4742
+	}
4743
+
4744
+	/**
4745
+	 * Returns list of parsed files
4746
+	 *
4747
+	 * @api
4748
+	 *
4749
+	 * @return array
4750
+	 */
4751
+	public function getParsedFiles()
4752
+	{
4753
+		return $this->parsedFiles;
4754
+	}
4755
+
4756
+	/**
4757
+	 * Add import path
4758
+	 *
4759
+	 * @api
4760
+	 *
4761
+	 * @param string|callable $path
4762
+	 */
4763
+	public function addImportPath($path)
4764
+	{
4765
+		if (! \in_array($path, $this->importPaths)) {
4766
+			$this->importPaths[] = $path;
4767
+		}
4768
+	}
4769
+
4770
+	/**
4771
+	 * Set import paths
4772
+	 *
4773
+	 * @api
4774
+	 *
4775
+	 * @param string|array $path
4776
+	 */
4777
+	public function setImportPaths($path)
4778
+	{
4779
+		$this->importPaths = (array) $path;
4780
+	}
4781
+
4782
+	/**
4783
+	 * Set number precision
4784
+	 *
4785
+	 * @api
4786
+	 *
4787
+	 * @param integer $numberPrecision
4788
+	 *
4789
+	 * @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
4790
+	 */
4791
+	public function setNumberPrecision($numberPrecision)
4792
+	{
4793
+		@trigger_error('The number precision is not configurable anymore. '
4794
+			. 'The default is enough for all browsers.', E_USER_DEPRECATED);
4795
+	}
4796
+
4797
+	/**
4798
+	 * Set formatter
4799
+	 *
4800
+	 * @api
4801
+	 *
4802
+	 * @param string $formatterName
4803
+	 */
4804
+	public function setFormatter($formatterName)
4805
+	{
4806
+		$this->formatter = $formatterName;
4807
+	}
4808
+
4809
+	/**
4810
+	 * Set line number style
4811
+	 *
4812
+	 * @api
4813
+	 *
4814
+	 * @param string $lineNumberStyle
4815
+	 */
4816
+	public function setLineNumberStyle($lineNumberStyle)
4817
+	{
4818
+		$this->lineNumberStyle = $lineNumberStyle;
4819
+	}
4820
+
4821
+	/**
4822
+	 * Enable/disable source maps
4823
+	 *
4824
+	 * @api
4825
+	 *
4826
+	 * @param integer $sourceMap
4827
+	 */
4828
+	public function setSourceMap($sourceMap)
4829
+	{
4830
+		$this->sourceMap = $sourceMap;
4831
+	}
4832
+
4833
+	/**
4834
+	 * Set source map options
4835
+	 *
4836
+	 * @api
4837
+	 *
4838
+	 * @param array $sourceMapOptions
4839
+	 */
4840
+	public function setSourceMapOptions($sourceMapOptions)
4841
+	{
4842
+		$this->sourceMapOptions = $sourceMapOptions;
4843
+	}
4844
+
4845
+	/**
4846
+	 * Register function
4847
+	 *
4848
+	 * @api
4849
+	 *
4850
+	 * @param string   $name
4851
+	 * @param callable $func
4852
+	 * @param array    $prototype
4853
+	 */
4854
+	public function registerFunction($name, $func, $prototype = null)
4855
+	{
4856
+		$this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
4857
+	}
4858
+
4859
+	/**
4860
+	 * Unregister function
4861
+	 *
4862
+	 * @api
4863
+	 *
4864
+	 * @param string $name
4865
+	 */
4866
+	public function unregisterFunction($name)
4867
+	{
4868
+		unset($this->userFunctions[$this->normalizeName($name)]);
4869
+	}
4870
+
4871
+	/**
4872
+	 * Add feature
4873
+	 *
4874
+	 * @api
4875
+	 *
4876
+	 * @param string $name
4877
+	 */
4878
+	public function addFeature($name)
4879
+	{
4880
+		$this->registeredFeatures[$name] = true;
4881
+	}
4882
+
4883
+	/**
4884
+	 * Import file
4885
+	 *
4886
+	 * @param string                                 $path
4887
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
4888
+	 */
4889
+	protected function importFile($path, OutputBlock $out)
4890
+	{
4891
+		$this->pushCallStack('import ' . $path);
4892
+		// see if tree is cached
4893
+		$realPath = realpath($path);
4894
+
4895
+		if (isset($this->importCache[$realPath])) {
4896
+			$this->handleImportLoop($realPath);
4897
+
4898
+			$tree = $this->importCache[$realPath];
4899
+		} else {
4900
+			$code   = file_get_contents($path);
4901
+			$parser = $this->parserFactory($path);
4902
+			$tree   = $parser->parse($code);
4903
+
4904
+			$this->importCache[$realPath] = $tree;
4905
+		}
4906
+
4907
+		$pi = pathinfo($path);
4908
+
4909
+		array_unshift($this->importPaths, $pi['dirname']);
4910
+		$this->compileChildrenNoReturn($tree->children, $out);
4911
+		array_shift($this->importPaths);
4912
+		$this->popCallStack();
4913
+	}
4914
+
4915
+	/**
4916
+	 * Return the file path for an import url if it exists
4917
+	 *
4918
+	 * @api
4919
+	 *
4920
+	 * @param string $url
4921
+	 *
4922
+	 * @return string|null
4923
+	 */
4924
+	public function findImport($url)
4925
+	{
4926
+		$urls = [];
4927
+
4928
+		$hasExtension = preg_match('/[.]s?css$/', $url);
4929
+
4930
+		// for "normal" scss imports (ignore vanilla css and external requests)
4931
+		if (! preg_match('~\.css$|^https?://|^//~', $url)) {
4932
+			$isPartial = (strpos(basename($url), '_') === 0);
4933
+
4934
+			// try both normal and the _partial filename
4935
+			$urls = [$url . ($hasExtension ? '' : '.scss')];
4936
+
4937
+			if (! $isPartial) {
4938
+				$urls[] = preg_replace('~[^/]+$~', '_\0', $url) . ($hasExtension ? '' : '.scss');
4939
+			}
4940
+
4941
+			if (! $hasExtension) {
4942
+				$urls[] = "$url/index.scss";
4943
+				$urls[] = "$url/_index.scss";
4944
+				// allow to find a plain css file, *if* no scss or partial scss is found
4945
+				$urls[] .= $url . '.css';
4946
+			}
4947
+		}
4948
+
4949
+		foreach ($this->importPaths as $dir) {
4950
+			if (\is_string($dir)) {
4951
+				// check urls for normal import paths
4952
+				foreach ($urls as $full) {
4953
+					$separator = (
4954
+						! empty($dir) &&
4955
+						substr($dir, -1) !== '/' &&
4956
+						substr($full, 0, 1) !== '/'
4957
+					) ? '/' : '';
4958
+					$full = $dir . $separator . $full;
4959
+
4960
+					if (is_file($file = $full)) {
4961
+						return $file;
4962
+					}
4963
+				}
4964
+			} elseif (\is_callable($dir)) {
4965
+				// check custom callback for import path
4966
+				$file = \call_user_func($dir, $url);
4967
+
4968
+				if (! \is_null($file)) {
4969
+					return $file;
4970
+				}
4971
+			}
4972
+		}
4973
+
4974
+		if ($urls) {
4975
+			if (! $hasExtension || preg_match('/[.]scss$/', $url)) {
4976
+				throw $this->error("`$url` file not found for @import");
4977
+			}
4978
+		}
4979
+
4980
+		return null;
4981
+	}
4982
+
4983
+	/**
4984
+	 * Set encoding
4985
+	 *
4986
+	 * @api
4987
+	 *
4988
+	 * @param string $encoding
4989
+	 */
4990
+	public function setEncoding($encoding)
4991
+	{
4992
+		$this->encoding = $encoding;
4993
+	}
4994
+
4995
+	/**
4996
+	 * Ignore errors?
4997
+	 *
4998
+	 * @api
4999
+	 *
5000
+	 * @param boolean $ignoreErrors
5001
+	 *
5002
+	 * @return \ScssPhp\ScssPhp\Compiler
5003
+	 *
5004
+	 * @deprecated Ignoring Sass errors is not longer supported.
5005
+	 */
5006
+	public function setIgnoreErrors($ignoreErrors)
5007
+	{
5008
+		@trigger_error('Ignoring Sass errors is not longer supported.', E_USER_DEPRECATED);
5009
+
5010
+		return $this;
5011
+	}
5012
+
5013
+	/**
5014
+	 * Get source position
5015
+	 *
5016
+	 * @api
5017
+	 *
5018
+	 * @return array
5019
+	 */
5020
+	public function getSourcePosition()
5021
+	{
5022
+		$sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
5023
+
5024
+		return [$sourceFile, $this->sourceLine, $this->sourceColumn];
5025
+	}
5026
+
5027
+	/**
5028
+	 * Throw error (exception)
5029
+	 *
5030
+	 * @api
5031
+	 *
5032
+	 * @param string $msg Message with optional sprintf()-style vararg parameters
5033
+	 *
5034
+	 * @throws \ScssPhp\ScssPhp\Exception\CompilerException
5035
+	 *
5036
+	 * @deprecated use "error" and throw the exception in the caller instead.
5037
+	 */
5038
+	public function throwError($msg)
5039
+	{
5040
+		@trigger_error(
5041
+			'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead',
5042
+			E_USER_DEPRECATED
5043
+		);
5044
+
5045
+		throw $this->error(...func_get_args());
5046
+	}
5047
+
5048
+	/**
5049
+	 * Build an error (exception)
5050
+	 *
5051
+	 * @api
5052
+	 *
5053
+	 * @param string $msg Message with optional sprintf()-style vararg parameters
5054
+	 *
5055
+	 * @return CompilerException
5056
+	 */
5057
+	public function error($msg, ...$args)
5058
+	{
5059
+		if ($args) {
5060
+			$msg = sprintf($msg, ...$args);
5061
+		}
5062
+
5063
+		if (! $this->ignoreCallStackMessage) {
5064
+			$line   = $this->sourceLine;
5065
+			$column = $this->sourceColumn;
5066
+
5067
+			$loc = isset($this->sourceNames[$this->sourceIndex])
5068
+				? $this->sourceNames[$this->sourceIndex] . " on line $line, at column $column"
5069
+				: "line: $line, column: $column";
5070
+
5071
+			$msg = "$msg: $loc";
5072
+
5073
+			$callStackMsg = $this->callStackMessage();
5074
+
5075
+			if ($callStackMsg) {
5076
+				$msg .= "\nCall Stack:\n" . $callStackMsg;
5077
+			}
5078
+		}
5079
+
5080
+		return new CompilerException($msg);
5081
+	}
5082
+
5083
+	/**
5084
+	 * @param string $functionName
5085
+	 * @param array $ExpectedArgs
5086
+	 * @param int $nbActual
5087
+	 * @return CompilerException
5088
+	 */
5089
+	public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
5090
+	{
5091
+		$nbExpected = \count($ExpectedArgs);
5092
+
5093
+		if ($nbActual > $nbExpected) {
5094
+			return $this->error(
5095
+				'Error: Only %d arguments allowed in %s(), but %d were passed.',
5096
+				$nbExpected,
5097
+				$functionName,
5098
+				$nbActual
5099
+			);
5100
+		} else {
5101
+			$missing = [];
5102
+
5103
+			while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) {
5104
+				array_unshift($missing, array_pop($ExpectedArgs));
5105
+			}
5106
+
5107
+			return $this->error(
5108
+				'Error: %s() argument%s %s missing.',
5109
+				$functionName,
5110
+				count($missing) > 1 ? 's' : '',
5111
+				implode(', ', $missing)
5112
+			);
5113
+		}
5114
+	}
5115
+
5116
+	/**
5117
+	 * Beautify call stack for output
5118
+	 *
5119
+	 * @param boolean $all
5120
+	 * @param null    $limit
5121
+	 *
5122
+	 * @return string
5123
+	 */
5124
+	protected function callStackMessage($all = false, $limit = null)
5125
+	{
5126
+		$callStackMsg = [];
5127
+		$ncall = 0;
5128
+
5129
+		if ($this->callStack) {
5130
+			foreach (array_reverse($this->callStack) as $call) {
5131
+				if ($all || (isset($call['n']) && $call['n'])) {
5132
+					$msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
5133
+					$msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
5134
+						  ? $this->sourceNames[$call[Parser::SOURCE_INDEX]]
5135
+						  : '(unknown file)');
5136
+					$msg .= ' on line ' . $call[Parser::SOURCE_LINE];
5137
+
5138
+					$callStackMsg[] = $msg;
5139
+
5140
+					if (! \is_null($limit) && $ncall > $limit) {
5141
+						break;
5142
+					}
5143
+				}
5144
+			}
5145
+		}
5146
+
5147
+		return implode("\n", $callStackMsg);
5148
+	}
5149
+
5150
+	/**
5151
+	 * Handle import loop
5152
+	 *
5153
+	 * @param string $name
5154
+	 *
5155
+	 * @throws \Exception
5156
+	 */
5157
+	protected function handleImportLoop($name)
5158
+	{
5159
+		for ($env = $this->env; $env; $env = $env->parent) {
5160
+			if (! $env->block) {
5161
+				continue;
5162
+			}
5163
+
5164
+			$file = $this->sourceNames[$env->block->sourceIndex];
5165
+
5166
+			if (realpath($file) === $name) {
5167
+				throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
5168
+			}
5169
+		}
5170
+	}
5171
+
5172
+	/**
5173
+	 * Call SCSS @function
5174
+	 *
5175
+	 * @param Object $func
5176
+	 * @param array  $argValues
5177
+	 *
5178
+	 * @return array $returnValue
5179
+	 */
5180
+	protected function callScssFunction($func, $argValues)
5181
+	{
5182
+		if (! $func) {
5183
+			return static::$defaultValue;
5184
+		}
5185
+		$name = $func->name;
5186
+
5187
+		$this->pushEnv();
5188
+
5189
+		// set the args
5190
+		if (isset($func->args)) {
5191
+			$this->applyArguments($func->args, $argValues);
5192
+		}
5193
+
5194
+		// throw away lines and children
5195
+		$tmp = new OutputBlock();
5196
+		$tmp->lines    = [];
5197
+		$tmp->children = [];
5198
+
5199
+		$this->env->marker = 'function';
5200
+
5201
+		if (! empty($func->parentEnv)) {
5202
+			$this->env->declarationScopeParent = $func->parentEnv;
5203
+		} else {
5204
+			throw $this->error("@function $name() without parentEnv");
5205
+		}
5206
+
5207
+		$ret = $this->compileChildren($func->children, $tmp, $this->env->marker . ' ' . $name);
5208
+
5209
+		$this->popEnv();
5210
+
5211
+		return ! isset($ret) ? static::$defaultValue : $ret;
5212
+	}
5213
+
5214
+	/**
5215
+	 * Call built-in and registered (PHP) functions
5216
+	 *
5217
+	 * @param string $name
5218
+	 * @param string|array $function
5219
+	 * @param array  $prototype
5220
+	 * @param array  $args
5221
+	 *
5222
+	 * @return array
5223
+	 */
5224
+	protected function callNativeFunction($name, $function, $prototype, $args)
5225
+	{
5226
+		$libName = (is_array($function) ? end($function) : null);
5227
+		$sorted_kwargs = $this->sortNativeFunctionArgs($libName, $prototype, $args);
5228
+
5229
+		if (\is_null($sorted_kwargs)) {
5230
+			return null;
5231
+		}
5232
+		@list($sorted, $kwargs) = $sorted_kwargs;
5233
+
5234
+		if ($name !== 'if' && $name !== 'call') {
5235
+			$inExp = true;
5236
+
5237
+			if ($name === 'join') {
5238
+				$inExp = false;
5239
+			}
5240
+
5241
+			foreach ($sorted as &$val) {
5242
+				$val = $this->reduce($val, $inExp);
5243
+			}
5244
+		}
5245
+
5246
+		$returnValue = \call_user_func($function, $sorted, $kwargs);
5247
+
5248
+		if (! isset($returnValue)) {
5249
+			return null;
5250
+		}
5251
+
5252
+		return $this->coerceValue($returnValue);
5253
+	}
5254
+
5255
+	/**
5256
+	 * Get built-in function
5257
+	 *
5258
+	 * @param string $name Normalized name
5259
+	 *
5260
+	 * @return array
5261
+	 */
5262
+	protected function getBuiltinFunction($name)
5263
+	{
5264
+		$libName = 'lib' . preg_replace_callback(
5265
+			'/_(.)/',
5266
+			function ($m) {
5267
+				return ucfirst($m[1]);
5268
+			},
5269
+			ucfirst($name)
5270
+		);
5271
+
5272
+		return [$this, $libName];
5273
+	}
5274
+
5275
+	/**
5276
+	 * Sorts keyword arguments
5277
+	 *
5278
+	 * @param string $functionName
5279
+	 * @param array  $prototypes
5280
+	 * @param array  $args
5281
+	 *
5282
+	 * @return array|null
5283
+	 */
5284
+	protected function sortNativeFunctionArgs($functionName, $prototypes, $args)
5285
+	{
5286
+		static $parser = null;
5287
+
5288
+		if (! isset($prototypes)) {
5289
+			$keyArgs = [];
5290
+			$posArgs = [];
5291
+
5292
+			if (\is_array($args) && \count($args) && \end($args) === static::$null) {
5293
+				array_pop($args);
5294
+			}
5295
+
5296
+			// separate positional and keyword arguments
5297
+			foreach ($args as $arg) {
5298
+				list($key, $value) = $arg;
5299
+
5300
+				if (empty($key) or empty($key[1])) {
5301
+					$posArgs[] = empty($arg[2]) ? $value : $arg;
5302
+				} else {
5303
+					$keyArgs[$key[1]] = $value;
5304
+				}
5305
+			}
5306
+
5307
+			return [$posArgs, $keyArgs];
5308
+		}
5309
+
5310
+		// specific cases ?
5311
+		if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5312
+			// notation 100 127 255 / 0 is in fact a simple list of 4 values
5313
+			foreach ($args as $k => $arg) {
5314
+				if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
5315
+					$last = end($arg[1][2]);
5316
+
5317
+					if ($last[0] === Type::T_EXPRESSION && $last[1] === '/') {
5318
+						array_pop($arg[1][2]);
5319
+						$arg[1][2][] = $last[2];
5320
+						$arg[1][2][] = $last[3];
5321
+						$args[$k] = $arg;
5322
+					}
5323
+				}
5324
+			}
5325
+		}
5326
+
5327
+		$finalArgs = [];
5328
+
5329
+		if (! \is_array(reset($prototypes))) {
5330
+			$prototypes = [$prototypes];
5331
+		}
5332
+
5333
+		$keyArgs = [];
5334
+
5335
+		// trying each prototypes
5336
+		$prototypeHasMatch = false;
5337
+		$exceptionMessage = '';
5338
+
5339
+		foreach ($prototypes as $prototype) {
5340
+			$argDef = [];
5341
+
5342
+			foreach ($prototype as $i => $p) {
5343
+				$default = null;
5344
+				$p       = explode(':', $p, 2);
5345
+				$name    = array_shift($p);
5346
+
5347
+				if (\count($p)) {
5348
+					$p = trim(reset($p));
5349
+
5350
+					if ($p === 'null') {
5351
+						// differentiate this null from the static::$null
5352
+						$default = [Type::T_KEYWORD, 'null'];
5353
+					} else {
5354
+						if (\is_null($parser)) {
5355
+							$parser = $this->parserFactory(__METHOD__);
5356
+						}
5357
+
5358
+						$parser->parseValue($p, $default);
5359
+					}
5360
+				}
5361
+
5362
+				$isVariable = false;
5363
+
5364
+				if (substr($name, -3) === '...') {
5365
+					$isVariable = true;
5366
+					$name = substr($name, 0, -3);
5367
+				}
5368
+
5369
+				$argDef[] = [$name, $default, $isVariable];
5370
+			}
5371
+
5372
+			$ignoreCallStackMessage = $this->ignoreCallStackMessage;
5373
+			$this->ignoreCallStackMessage = true;
5374
+
5375
+			try {
5376
+				if (\count($args) > \count($argDef)) {
5377
+					$lastDef = end($argDef);
5378
+
5379
+					// check that last arg is not a ...
5380
+					if (empty($lastDef[2])) {
5381
+						throw $this->errorArgsNumber($functionName, $argDef, \count($args));
5382
+					}
5383
+				}
5384
+				$vars = $this->applyArguments($argDef, $args, false, false);
5385
+
5386
+				// ensure all args are populated
5387
+				foreach ($prototype as $i => $p) {
5388
+					$name = explode(':', $p)[0];
5389
+
5390
+					if (! isset($finalArgs[$i])) {
5391
+						$finalArgs[$i] = null;
5392
+					}
5393
+				}
5394
+
5395
+				// apply positional args
5396
+				foreach (array_values($vars) as $i => $val) {
5397
+					$finalArgs[$i] = $val;
5398
+				}
5399
+
5400
+				$keyArgs = array_merge($keyArgs, $vars);
5401
+				$prototypeHasMatch = true;
5402
+
5403
+				// overwrite positional args with keyword args
5404
+				foreach ($prototype as $i => $p) {
5405
+					$name = explode(':', $p)[0];
5406
+
5407
+					if (isset($keyArgs[$name])) {
5408
+						$finalArgs[$i] = $keyArgs[$name];
5409
+					}
5410
+
5411
+					// special null value as default: translate to real null here
5412
+					if ($finalArgs[$i] === [Type::T_KEYWORD, 'null']) {
5413
+						$finalArgs[$i] = null;
5414
+					}
5415
+				}
5416
+				// should we break if this prototype seems fulfilled?
5417
+			} catch (CompilerException $e) {
5418
+				$exceptionMessage = $e->getMessage();
5419
+			}
5420
+			$this->ignoreCallStackMessage = $ignoreCallStackMessage;
5421
+		}
5422
+
5423
+		if ($exceptionMessage && ! $prototypeHasMatch) {
5424
+			if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5425
+				// if var() or calc() is used as an argument, return as a css function
5426
+				foreach ($args as $arg) {
5427
+					if ($arg[1][0] === Type::T_FUNCTION_CALL && in_array($arg[1][1], ['var'])) {
5428
+						return null;
5429
+					}
5430
+				}
5431
+			}
5432
+
5433
+			throw $this->error($exceptionMessage);
5434
+		}
5435
+
5436
+		return [$finalArgs, $keyArgs];
5437
+	}
5438
+
5439
+	/**
5440
+	 * Apply argument values per definition
5441
+	 *
5442
+	 * @param array   $argDef
5443
+	 * @param array   $argValues
5444
+	 * @param boolean $storeInEnv
5445
+	 * @param boolean $reduce
5446
+	 *   only used if $storeInEnv = false
5447
+	 *
5448
+	 * @return array
5449
+	 *
5450
+	 * @throws \Exception
5451
+	 */
5452
+	protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true)
5453
+	{
5454
+		$output = [];
5455
+
5456
+		if (\is_array($argValues) && \count($argValues) && end($argValues) === static::$null) {
5457
+			array_pop($argValues);
5458
+		}
5459
+
5460
+		if ($storeInEnv) {
5461
+			$storeEnv = $this->getStoreEnv();
5462
+
5463
+			$env = new Environment();
5464
+			$env->store = $storeEnv->store;
5465
+		}
5466
+
5467
+		$hasVariable = false;
5468
+		$args = [];
5469
+
5470
+		foreach ($argDef as $i => $arg) {
5471
+			list($name, $default, $isVariable) = $argDef[$i];
5472
+
5473
+			$args[$name] = [$i, $name, $default, $isVariable];
5474
+			$hasVariable |= $isVariable;
5475
+		}
5476
+
5477
+		$splatSeparator      = null;
5478
+		$keywordArgs         = [];
5479
+		$deferredKeywordArgs = [];
5480
+		$deferredNamedKeywordArgs = [];
5481
+		$remaining           = [];
5482
+		$hasKeywordArgument  = false;
5483
+
5484
+		// assign the keyword args
5485
+		foreach ((array) $argValues as $arg) {
5486
+			if (! empty($arg[0])) {
5487
+				$hasKeywordArgument = true;
5488
+
5489
+				$name = $arg[0][1];
5490
+
5491
+				if (! isset($args[$name])) {
5492
+					foreach (array_keys($args) as $an) {
5493
+						if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5494
+							$name = $an;
5495
+							break;
5496
+						}
5497
+					}
5498
+				}
5499
+
5500
+				if (! isset($args[$name]) || $args[$name][3]) {
5501
+					if ($hasVariable) {
5502
+						$deferredNamedKeywordArgs[$name] = $arg[1];
5503
+					} else {
5504
+						throw $this->error("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
5505
+					}
5506
+				} elseif ($args[$name][0] < \count($remaining)) {
5507
+					throw $this->error("The argument $%s was passed both by position and by name.", $arg[0][1]);
5508
+				} else {
5509
+					$keywordArgs[$name] = $arg[1];
5510
+				}
5511
+			} elseif (! empty($arg[2])) {
5512
+				// $arg[2] means a var followed by ... in the arg ($list... )
5513
+				$val = $this->reduce($arg[1], true);
5514
+
5515
+				if ($val[0] === Type::T_LIST) {
5516
+					foreach ($val[2] as $name => $item) {
5517
+						if (! is_numeric($name)) {
5518
+							if (! isset($args[$name])) {
5519
+								foreach (array_keys($args) as $an) {
5520
+									if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5521
+										$name = $an;
5522
+										break;
5523
+									}
5524
+								}
5525
+							}
5526
+
5527
+							if ($hasVariable) {
5528
+								$deferredKeywordArgs[$name] = $item;
5529
+							} else {
5530
+								$keywordArgs[$name] = $item;
5531
+							}
5532
+						} else {
5533
+							if (\is_null($splatSeparator)) {
5534
+								$splatSeparator = $val[1];
5535
+							}
5536
+
5537
+							$remaining[] = $item;
5538
+						}
5539
+					}
5540
+				} elseif ($val[0] === Type::T_MAP) {
5541
+					foreach ($val[1] as $i => $name) {
5542
+						$name = $this->compileStringContent($this->coerceString($name));
5543
+						$item = $val[2][$i];
5544
+
5545
+						if (! is_numeric($name)) {
5546
+							if (! isset($args[$name])) {
5547
+								foreach (array_keys($args) as $an) {
5548
+									if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5549
+										$name = $an;
5550
+										break;
5551
+									}
5552
+								}
5553
+							}
5554
+
5555
+							if ($hasVariable) {
5556
+								$deferredKeywordArgs[$name] = $item;
5557
+							} else {
5558
+								$keywordArgs[$name] = $item;
5559
+							}
5560
+						} else {
5561
+							if (\is_null($splatSeparator)) {
5562
+								$splatSeparator = $val[1];
5563
+							}
5564
+
5565
+							$remaining[] = $item;
5566
+						}
5567
+					}
5568
+				} else {
5569
+					$remaining[] = $val;
5570
+				}
5571
+			} elseif ($hasKeywordArgument) {
5572
+				throw $this->error('Positional arguments must come before keyword arguments.');
5573
+			} else {
5574
+				$remaining[] = $arg[1];
5575
+			}
5576
+		}
5577
+
5578
+		foreach ($args as $arg) {
5579
+			list($i, $name, $default, $isVariable) = $arg;
5580
+
5581
+			if ($isVariable) {
5582
+				// only if more than one arg : can not be passed as position and value
5583
+				// see https://github.com/sass/libsass/issues/2927
5584
+				if (count($args) > 1) {
5585
+					if (isset($remaining[$i]) && isset($deferredNamedKeywordArgs[$name])) {
5586
+						throw $this->error("The argument $%s was passed both by position and by name.", $name);
5587
+					}
5588
+				}
5589
+
5590
+				$val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , [], $isVariable];
5591
+
5592
+				for ($count = \count($remaining); $i < $count; $i++) {
5593
+					$val[2][] = $remaining[$i];
5594
+				}
5595
+
5596
+				foreach ($deferredKeywordArgs as $itemName => $item) {
5597
+					$val[2][$itemName] = $item;
5598
+				}
5599
+
5600
+				foreach ($deferredNamedKeywordArgs as $itemName => $item) {
5601
+					$val[2][$itemName] = $item;
5602
+				}
5603
+			} elseif (isset($remaining[$i])) {
5604
+				$val = $remaining[$i];
5605
+			} elseif (isset($keywordArgs[$name])) {
5606
+				$val = $keywordArgs[$name];
5607
+			} elseif (! empty($default)) {
5608
+				continue;
5609
+			} else {
5610
+				throw $this->error("Missing argument $name");
5611
+			}
5612
+
5613
+			if ($storeInEnv) {
5614
+				$this->set($name, $this->reduce($val, true), true, $env);
5615
+			} else {
5616
+				$output[$name] = ($reduce ? $this->reduce($val, true) : $val);
5617
+			}
5618
+		}
5619
+
5620
+		if ($storeInEnv) {
5621
+			$storeEnv->store = $env->store;
5622
+		}
5623
+
5624
+		foreach ($args as $arg) {
5625
+			list($i, $name, $default, $isVariable) = $arg;
5626
+
5627
+			if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
5628
+				continue;
5629
+			}
5630
+
5631
+			if ($storeInEnv) {
5632
+				$this->set($name, $this->reduce($default, true), true);
5633
+			} else {
5634
+				$output[$name] = ($reduce ? $this->reduce($default, true) : $default);
5635
+			}
5636
+		}
5637
+
5638
+		return $output;
5639
+	}
5640
+
5641
+	/**
5642
+	 * Coerce a php value into a scss one
5643
+	 *
5644
+	 * @param mixed $value
5645
+	 *
5646
+	 * @return array|\ScssPhp\ScssPhp\Node\Number
5647
+	 */
5648
+	protected function coerceValue($value)
5649
+	{
5650
+		if (\is_array($value) || $value instanceof \ArrayAccess) {
5651
+			return $value;
5652
+		}
5653
+
5654
+		if (\is_bool($value)) {
5655
+			return $this->toBool($value);
5656
+		}
5657
+
5658
+		if (\is_null($value)) {
5659
+			return static::$null;
5660
+		}
5661
+
5662
+		if (is_numeric($value)) {
5663
+			return new Node\Number($value, '');
5664
+		}
5665
+
5666
+		if ($value === '') {
5667
+			return static::$emptyString;
5668
+		}
5669
+
5670
+		$value = [Type::T_KEYWORD, $value];
5671
+		$color = $this->coerceColor($value);
5672
+
5673
+		if ($color) {
5674
+			return $color;
5675
+		}
5676
+
5677
+		return $value;
5678
+	}
5679
+
5680
+	/**
5681
+	 * Coerce something to map
5682
+	 *
5683
+	 * @param array $item
5684
+	 *
5685
+	 * @return array
5686
+	 */
5687
+	protected function coerceMap($item)
5688
+	{
5689
+		if ($item[0] === Type::T_MAP) {
5690
+			return $item;
5691
+		}
5692
+
5693
+		if (
5694
+			$item[0] === static::$emptyList[0] &&
5695
+			$item[1] === static::$emptyList[1] &&
5696
+			$item[2] === static::$emptyList[2]
5697
+		) {
5698
+			return static::$emptyMap;
5699
+		}
5700
+
5701
+		return $item;
5702
+	}
5703
+
5704
+	/**
5705
+	 * Coerce something to list
5706
+	 *
5707
+	 * @param array   $item
5708
+	 * @param string  $delim
5709
+	 * @param boolean $removeTrailingNull
5710
+	 *
5711
+	 * @return array
5712
+	 */
5713
+	protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
5714
+	{
5715
+		if (isset($item) && $item[0] === Type::T_LIST) {
5716
+			// remove trailing null from the list
5717
+			if ($removeTrailingNull && end($item[2]) === static::$null) {
5718
+				array_pop($item[2]);
5719
+			}
5720
+
5721
+			return $item;
5722
+		}
5723
+
5724
+		if (isset($item) && $item[0] === Type::T_MAP) {
5725
+			$keys = $item[1];
5726
+			$values = $item[2];
5727
+			$list = [];
5728
+
5729
+			for ($i = 0, $s = \count($keys); $i < $s; $i++) {
5730
+				$key = $keys[$i];
5731
+				$value = $values[$i];
5732
+
5733
+				switch ($key[0]) {
5734
+					case Type::T_LIST:
5735
+					case Type::T_MAP:
5736
+					case Type::T_STRING:
5737
+					case Type::T_NULL:
5738
+						break;
5739
+
5740
+					default:
5741
+						$key = [Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))];
5742
+						break;
5743
+				}
5744
+
5745
+				$list[] = [
5746
+					Type::T_LIST,
5747
+					'',
5748
+					[$key, $value]
5749
+				];
5750
+			}
5751
+
5752
+			return [Type::T_LIST, ',', $list];
5753
+		}
5754
+
5755
+		return [Type::T_LIST, $delim, ! isset($item) ? [] : [$item]];
5756
+	}
5757
+
5758
+	/**
5759
+	 * Coerce color for expression
5760
+	 *
5761
+	 * @param array $value
5762
+	 *
5763
+	 * @return array|null
5764
+	 */
5765
+	protected function coerceForExpression($value)
5766
+	{
5767
+		if ($color = $this->coerceColor($value)) {
5768
+			return $color;
5769
+		}
5770
+
5771
+		return $value;
5772
+	}
5773
+
5774
+	/**
5775
+	 * Coerce value to color
5776
+	 *
5777
+	 * @param array $value
5778
+	 *
5779
+	 * @return array|null
5780
+	 */
5781
+	protected function coerceColor($value, $inRGBFunction = false)
5782
+	{
5783
+		switch ($value[0]) {
5784
+			case Type::T_COLOR:
5785
+				for ($i = 1; $i <= 3; $i++) {
5786
+					if (! is_numeric($value[$i])) {
5787
+						$cv = $this->compileRGBAValue($value[$i]);
5788
+
5789
+						if (! is_numeric($cv)) {
5790
+							return null;
5791
+						}
5792
+
5793
+						$value[$i] = $cv;
5794
+					}
5795
+
5796
+					if (isset($value[4])) {
5797
+						if (! is_numeric($value[4])) {
5798
+							$cv = $this->compileRGBAValue($value[4], true);
5799
+
5800
+							if (! is_numeric($cv)) {
5801
+								return null;
5802
+							}
5803
+
5804
+							$value[4] = $cv;
5805
+						}
5806
+					}
5807
+				}
5808
+
5809
+				return $value;
5810
+
5811
+			case Type::T_LIST:
5812
+				if ($inRGBFunction) {
5813
+					if (\count($value[2]) == 3 || \count($value[2]) == 4) {
5814
+						$color = $value[2];
5815
+						array_unshift($color, Type::T_COLOR);
5816
+
5817
+						return $this->coerceColor($color);
5818
+					}
5819
+				}
5820
+
5821
+				return null;
5822
+
5823
+			case Type::T_KEYWORD:
5824
+				if (! \is_string($value[1])) {
5825
+					return null;
5826
+				}
5827
+
5828
+				$name = strtolower($value[1]);
5829
+
5830
+				// hexa color?
5831
+				if (preg_match('/^#([0-9a-f]+)$/i', $name, $m)) {
5832
+					$nofValues = \strlen($m[1]);
5833
+
5834
+					if (\in_array($nofValues, [3, 4, 6, 8])) {
5835
+						$nbChannels = 3;
5836
+						$color      = [];
5837
+						$num        = hexdec($m[1]);
5838
+
5839
+						switch ($nofValues) {
5840
+							case 4:
5841
+								$nbChannels = 4;
5842
+								// then continuing with the case 3:
5843
+							case 3:
5844
+								for ($i = 0; $i < $nbChannels; $i++) {
5845
+									$t = $num & 0xf;
5846
+									array_unshift($color, $t << 4 | $t);
5847
+									$num >>= 4;
5848
+								}
5849
+
5850
+								break;
5851
+
5852
+							case 8:
5853
+								$nbChannels = 4;
5854
+								// then continuing with the case 6:
5855
+							case 6:
5856
+								for ($i = 0; $i < $nbChannels; $i++) {
5857
+									array_unshift($color, $num & 0xff);
5858
+									$num >>= 8;
5859
+								}
5860
+
5861
+								break;
5862
+						}
5863
+
5864
+						if ($nbChannels === 4) {
5865
+							if ($color[3] === 255) {
5866
+								$color[3] = 1; // fully opaque
5867
+							} else {
5868
+								$color[3] = round($color[3] / 255, Node\Number::PRECISION);
5869
+							}
5870
+						}
5871
+
5872
+						array_unshift($color, Type::T_COLOR);
5873
+
5874
+						return $color;
5875
+					}
5876
+				}
5877
+
5878
+				if ($rgba = Colors::colorNameToRGBa($name)) {
5879
+					return isset($rgba[3])
5880
+						? [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2], $rgba[3]]
5881
+						: [Type::T_COLOR, $rgba[0], $rgba[1], $rgba[2]];
5882
+				}
5883
+
5884
+				return null;
5885
+		}
5886
+
5887
+		return null;
5888
+	}
5889
+
5890
+	/**
5891
+	 * @param integer|\ScssPhp\ScssPhp\Node\Number $value
5892
+	 * @param boolean                              $isAlpha
5893
+	 *
5894
+	 * @return integer|mixed
5895
+	 */
5896
+	protected function compileRGBAValue($value, $isAlpha = false)
5897
+	{
5898
+		if ($isAlpha) {
5899
+			return $this->compileColorPartValue($value, 0, 1, false);
5900
+		}
5901
+
5902
+		return $this->compileColorPartValue($value, 0, 255, true);
5903
+	}
5904
+
5905
+	/**
5906
+	 * @param mixed         $value
5907
+	 * @param integer|float $min
5908
+	 * @param integer|float $max
5909
+	 * @param boolean       $isInt
5910
+	 * @param boolean       $clamp
5911
+	 * @param boolean       $modulo
5912
+	 *
5913
+	 * @return integer|mixed
5914
+	 */
5915
+	protected function compileColorPartValue($value, $min, $max, $isInt = true, $clamp = true, $modulo = false)
5916
+	{
5917
+		if (! is_numeric($value)) {
5918
+			if (\is_array($value)) {
5919
+				$reduced = $this->reduce($value);
5920
+
5921
+				if (\is_object($reduced) && $value->type === Type::T_NUMBER) {
5922
+					$value = $reduced;
5923
+				}
5924
+			}
5925
+
5926
+			if (\is_object($value) && $value->type === Type::T_NUMBER) {
5927
+				$num = $value->dimension;
5928
+
5929
+				if (\count($value->units)) {
5930
+					$unit = array_keys($value->units);
5931
+					$unit = reset($unit);
5932
+
5933
+					switch ($unit) {
5934
+						case '%':
5935
+							$num *= $max / 100;
5936
+							break;
5937
+						default:
5938
+							break;
5939
+					}
5940
+				}
5941
+
5942
+				$value = $num;
5943
+			} elseif (\is_array($value)) {
5944
+				$value = $this->compileValue($value);
5945
+			}
5946
+		}
5947
+
5948
+		if (is_numeric($value)) {
5949
+			if ($isInt) {
5950
+				$value = round($value);
5951
+			}
5952
+
5953
+			if ($clamp) {
5954
+				$value = min($max, max($min, $value));
5955
+			}
5956
+
5957
+			if ($modulo) {
5958
+				$value = $value % $max;
5959
+
5960
+				// still negative?
5961
+				while ($value < $min) {
5962
+					$value += $max;
5963
+				}
5964
+			}
5965
+
5966
+			return $value;
5967
+		}
5968
+
5969
+		return $value;
5970
+	}
5971
+
5972
+	/**
5973
+	 * Coerce value to string
5974
+	 *
5975
+	 * @param array $value
5976
+	 *
5977
+	 * @return array|null
5978
+	 */
5979
+	protected function coerceString($value)
5980
+	{
5981
+		if ($value[0] === Type::T_STRING) {
5982
+			return $value;
5983
+		}
5984
+
5985
+		return [Type::T_STRING, '', [$this->compileValue($value)]];
5986
+	}
5987
+
5988
+	/**
5989
+	 * Coerce value to a percentage
5990
+	 *
5991
+	 * @param array $value
5992
+	 *
5993
+	 * @return integer|float
5994
+	 */
5995
+	protected function coercePercent($value)
5996
+	{
5997
+		if ($value[0] === Type::T_NUMBER) {
5998
+			if (! empty($value[2]['%'])) {
5999
+				return $value[1] / 100;
6000
+			}
6001
+
6002
+			return $value[1];
6003
+		}
6004
+
6005
+		return 0;
6006
+	}
6007
+
6008
+	/**
6009
+	 * Assert value is a map
6010
+	 *
6011
+	 * @api
6012
+	 *
6013
+	 * @param array $value
6014
+	 *
6015
+	 * @return array
6016
+	 *
6017
+	 * @throws \Exception
6018
+	 */
6019
+	public function assertMap($value)
6020
+	{
6021
+		$value = $this->coerceMap($value);
6022
+
6023
+		if ($value[0] !== Type::T_MAP) {
6024
+			throw $this->error('expecting map, %s received', $value[0]);
6025
+		}
6026
+
6027
+		return $value;
6028
+	}
6029
+
6030
+	/**
6031
+	 * Assert value is a list
6032
+	 *
6033
+	 * @api
6034
+	 *
6035
+	 * @param array $value
6036
+	 *
6037
+	 * @return array
6038
+	 *
6039
+	 * @throws \Exception
6040
+	 */
6041
+	public function assertList($value)
6042
+	{
6043
+		if ($value[0] !== Type::T_LIST) {
6044
+			throw $this->error('expecting list, %s received', $value[0]);
6045
+		}
6046
+
6047
+		return $value;
6048
+	}
6049
+
6050
+	/**
6051
+	 * Assert value is a color
6052
+	 *
6053
+	 * @api
6054
+	 *
6055
+	 * @param array $value
6056
+	 *
6057
+	 * @return array
6058
+	 *
6059
+	 * @throws \Exception
6060
+	 */
6061
+	public function assertColor($value)
6062
+	{
6063
+		if ($color = $this->coerceColor($value)) {
6064
+			return $color;
6065
+		}
6066
+
6067
+		throw $this->error('expecting color, %s received', $value[0]);
6068
+	}
6069
+
6070
+	/**
6071
+	 * Assert value is a number
6072
+	 *
6073
+	 * @api
6074
+	 *
6075
+	 * @param array $value
6076
+	 *
6077
+	 * @return integer|float
6078
+	 *
6079
+	 * @throws \Exception
6080
+	 */
6081
+	public function assertNumber($value)
6082
+	{
6083
+		if ($value[0] !== Type::T_NUMBER) {
6084
+			throw $this->error('expecting number, %s received', $value[0]);
6085
+		}
6086
+
6087
+		return $value[1];
6088
+	}
6089
+
6090
+	/**
6091
+	 * Make sure a color's components don't go out of bounds
6092
+	 *
6093
+	 * @param array $c
6094
+	 *
6095
+	 * @return array
6096
+	 */
6097
+	protected function fixColor($c)
6098
+	{
6099
+		foreach ([1, 2, 3] as $i) {
6100
+			if ($c[$i] < 0) {
6101
+				$c[$i] = 0;
6102
+			}
6103
+
6104
+			if ($c[$i] > 255) {
6105
+				$c[$i] = 255;
6106
+			}
6107
+		}
6108
+
6109
+		return $c;
6110
+	}
6111
+
6112
+	/**
6113
+	 * Convert RGB to HSL
6114
+	 *
6115
+	 * @api
6116
+	 *
6117
+	 * @param integer $red
6118
+	 * @param integer $green
6119
+	 * @param integer $blue
6120
+	 *
6121
+	 * @return array
6122
+	 */
6123
+	public function toHSL($red, $green, $blue)
6124
+	{
6125
+		$min = min($red, $green, $blue);
6126
+		$max = max($red, $green, $blue);
6127
+
6128
+		$l = $min + $max;
6129
+		$d = $max - $min;
6130
+
6131
+		if ((int) $d === 0) {
6132
+			$h = $s = 0;
6133
+		} else {
6134
+			if ($l < 255) {
6135
+				$s = $d / $l;
6136
+			} else {
6137
+				$s = $d / (510 - $l);
6138
+			}
6139
+
6140
+			if ($red == $max) {
6141
+				$h = 60 * ($green - $blue) / $d;
6142
+			} elseif ($green == $max) {
6143
+				$h = 60 * ($blue - $red) / $d + 120;
6144
+			} elseif ($blue == $max) {
6145
+				$h = 60 * ($red - $green) / $d + 240;
6146
+			}
6147
+		}
6148
+
6149
+		return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
6150
+	}
6151
+
6152
+	/**
6153
+	 * Hue to RGB helper
6154
+	 *
6155
+	 * @param float $m1
6156
+	 * @param float $m2
6157
+	 * @param float $h
6158
+	 *
6159
+	 * @return float
6160
+	 */
6161
+	protected function hueToRGB($m1, $m2, $h)
6162
+	{
6163
+		if ($h < 0) {
6164
+			$h += 1;
6165
+		} elseif ($h > 1) {
6166
+			$h -= 1;
6167
+		}
6168
+
6169
+		if ($h * 6 < 1) {
6170
+			return $m1 + ($m2 - $m1) * $h * 6;
6171
+		}
6172
+
6173
+		if ($h * 2 < 1) {
6174
+			return $m2;
6175
+		}
6176
+
6177
+		if ($h * 3 < 2) {
6178
+			return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
6179
+		}
6180
+
6181
+		return $m1;
6182
+	}
6183
+
6184
+	/**
6185
+	 * Convert HSL to RGB
6186
+	 *
6187
+	 * @api
6188
+	 *
6189
+	 * @param integer $hue        H from 0 to 360
6190
+	 * @param integer $saturation S from 0 to 100
6191
+	 * @param integer $lightness  L from 0 to 100
6192
+	 *
6193
+	 * @return array
6194
+	 */
6195
+	public function toRGB($hue, $saturation, $lightness)
6196
+	{
6197
+		if ($hue < 0) {
6198
+			$hue += 360;
6199
+		}
6200
+
6201
+		$h = $hue / 360;
6202
+		$s = min(100, max(0, $saturation)) / 100;
6203
+		$l = min(100, max(0, $lightness)) / 100;
6204
+
6205
+		$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
6206
+		$m1 = $l * 2 - $m2;
6207
+
6208
+		$r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255;
6209
+		$g = $this->hueToRGB($m1, $m2, $h) * 255;
6210
+		$b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255;
6211
+
6212
+		$out = [Type::T_COLOR, $r, $g, $b];
6213
+
6214
+		return $out;
6215
+	}
6216
+
6217
+	// Built in functions
6218
+
6219
+	protected static $libCall = ['name', 'args...'];
6220
+	protected function libCall($args, $kwargs)
6221
+	{
6222
+		$functionReference = $this->reduce(array_shift($args), true);
6223
+
6224
+		if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
6225
+			$name = $this->compileStringContent($this->coerceString($this->reduce($functionReference, true)));
6226
+			$warning = "DEPRECATION WARNING: Passing a string to call() is deprecated and will be illegal\n"
6227
+				. "in Sass 4.0. Use call(function-reference($name)) instead.";
6228
+			fwrite($this->stderr, "$warning\n\n");
6229
+			$functionReference = $this->libGetFunction([$functionReference]);
6230
+		}
6231
+
6232
+		if ($functionReference === static::$null) {
6233
+			return static::$null;
6234
+		}
6235
+
6236
+		if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
6237
+			throw $this->error('Function reference expected, got ' . $functionReference[0]);
6238
+		}
6239
+
6240
+		$callArgs = [];
6241
+
6242
+		// $kwargs['args'] is [Type::T_LIST, ',', [..]]
6243
+		foreach ($kwargs['args'][2] as $varname => $arg) {
6244
+			if (is_numeric($varname)) {
6245
+				$varname = null;
6246
+			} else {
6247
+				$varname = [ 'var', $varname];
6248
+			}
6249
+
6250
+			$callArgs[] = [$varname, $arg, false];
6251
+		}
6252
+
6253
+		return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
6254
+	}
6255
+
6256
+
6257
+	protected static $libGetFunction = [
6258
+		['name'],
6259
+		['name', 'css']
6260
+	];
6261
+	protected function libGetFunction($args)
6262
+	{
6263
+		$name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
6264
+		$isCss = false;
6265
+
6266
+		if (count($args)) {
6267
+			$isCss = $this->reduce(array_shift($args), true);
6268
+			$isCss = (($isCss === static::$true) ? true : false);
6269
+		}
6270
+
6271
+		if ($isCss) {
6272
+			return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]];
6273
+		}
6274
+
6275
+		return $this->getFunctionReference($name, true);
6276
+	}
6277
+
6278
+	protected static $libIf = ['condition', 'if-true', 'if-false:'];
6279
+	protected function libIf($args)
6280
+	{
6281
+		list($cond, $t, $f) = $args;
6282
+
6283
+		if (! $this->isTruthy($this->reduce($cond, true))) {
6284
+			return $this->reduce($f, true);
6285
+		}
6286
+
6287
+		return $this->reduce($t, true);
6288
+	}
6289
+
6290
+	protected static $libIndex = ['list', 'value'];
6291
+	protected function libIndex($args)
6292
+	{
6293
+		list($list, $value) = $args;
6294
+
6295
+		if (
6296
+			$list[0] === Type::T_MAP ||
6297
+			$list[0] === Type::T_STRING ||
6298
+			$list[0] === Type::T_KEYWORD ||
6299
+			$list[0] === Type::T_INTERPOLATE
6300
+		) {
6301
+			$list = $this->coerceList($list, ' ');
6302
+		}
6303
+
6304
+		if ($list[0] !== Type::T_LIST) {
6305
+			return static::$null;
6306
+		}
6307
+
6308
+		$values = [];
6309
+
6310
+		foreach ($list[2] as $item) {
6311
+			$values[] = $this->normalizeValue($item);
6312
+		}
6313
+
6314
+		$key = array_search($this->normalizeValue($value), $values);
6315
+
6316
+		return false === $key ? static::$null : $key + 1;
6317
+	}
6318
+
6319
+	protected static $libRgb = [
6320
+		['color'],
6321
+		['color', 'alpha'],
6322
+		['channels'],
6323
+		['red', 'green', 'blue'],
6324
+		['red', 'green', 'blue', 'alpha'] ];
6325
+	protected function libRgb($args, $kwargs, $funcName = 'rgb')
6326
+	{
6327
+		switch (\count($args)) {
6328
+			case 1:
6329
+				if (! $color = $this->coerceColor($args[0], true)) {
6330
+					$color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6331
+				}
6332
+				break;
6333
+
6334
+			case 3:
6335
+				$color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
6336
+
6337
+				if (! $color = $this->coerceColor($color)) {
6338
+					$color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6339
+				}
6340
+
6341
+				return $color;
6342
+
6343
+			case 2:
6344
+				if ($color = $this->coerceColor($args[0], true)) {
6345
+					$alpha = $this->compileRGBAValue($args[1], true);
6346
+
6347
+					if (is_numeric($alpha)) {
6348
+						$color[4] = $alpha;
6349
+					} else {
6350
+						$color = [Type::T_STRING, '',
6351
+							[$funcName . '(', $color[1], ', ', $color[2], ', ', $color[3], ', ', $alpha, ')']];
6352
+					}
6353
+				} else {
6354
+					$color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6355
+				}
6356
+				break;
6357
+
6358
+			case 4:
6359
+			default:
6360
+				$color = [Type::T_COLOR, $args[0], $args[1], $args[2], $args[3]];
6361
+
6362
+				if (! $color = $this->coerceColor($color)) {
6363
+					$color = [Type::T_STRING, '',
6364
+						[$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6365
+				}
6366
+				break;
6367
+		}
6368
+
6369
+		return $color;
6370
+	}
6371
+
6372
+	protected static $libRgba = [
6373
+		['color'],
6374
+		['color', 'alpha'],
6375
+		['channels'],
6376
+		['red', 'green', 'blue'],
6377
+		['red', 'green', 'blue', 'alpha'] ];
6378
+	protected function libRgba($args, $kwargs)
6379
+	{
6380
+		return $this->libRgb($args, $kwargs, 'rgba');
6381
+	}
6382
+
6383
+	// helper function for adjust_color, change_color, and scale_color
6384
+	protected function alterColor($args, $fn)
6385
+	{
6386
+		$color = $this->assertColor($args[0]);
6387
+
6388
+		foreach ([1 => 1, 2 => 2, 3 => 3, 7 => 4] as $iarg => $irgba) {
6389
+			if (isset($args[$iarg])) {
6390
+				$val = $this->assertNumber($args[$iarg]);
6391
+
6392
+				if (! isset($color[$irgba])) {
6393
+					$color[$irgba] = (($irgba < 4) ? 0 : 1);
6394
+				}
6395
+
6396
+				$color[$irgba] = \call_user_func($fn, $color[$irgba], $val, $iarg);
6397
+			}
6398
+		}
6399
+
6400
+		if (! empty($args[4]) || ! empty($args[5]) || ! empty($args[6])) {
6401
+			$hsl = $this->toHSL($color[1], $color[2], $color[3]);
6402
+
6403
+			foreach ([4 => 1, 5 => 2, 6 => 3] as $iarg => $ihsl) {
6404
+				if (! empty($args[$iarg])) {
6405
+					$val = $this->assertNumber($args[$iarg]);
6406
+					$hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
6407
+				}
6408
+			}
6409
+
6410
+			$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6411
+
6412
+			if (isset($color[4])) {
6413
+				$rgb[4] = $color[4];
6414
+			}
6415
+
6416
+			$color = $rgb;
6417
+		}
6418
+
6419
+		return $color;
6420
+	}
6421
+
6422
+	protected static $libAdjustColor = [
6423
+		'color', 'red:null', 'green:null', 'blue:null',
6424
+		'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6425
+	];
6426
+	protected function libAdjustColor($args)
6427
+	{
6428
+		return $this->alterColor($args, function ($base, $alter, $i) {
6429
+			return $base + $alter;
6430
+		});
6431
+	}
6432
+
6433
+	protected static $libChangeColor = [
6434
+		'color', 'red:null', 'green:null', 'blue:null',
6435
+		'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6436
+	];
6437
+	protected function libChangeColor($args)
6438
+	{
6439
+		return $this->alterColor($args, function ($base, $alter, $i) {
6440
+			return $alter;
6441
+		});
6442
+	}
6443
+
6444
+	protected static $libScaleColor = [
6445
+		'color', 'red:null', 'green:null', 'blue:null',
6446
+		'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
6447
+	];
6448
+	protected function libScaleColor($args)
6449
+	{
6450
+		return $this->alterColor($args, function ($base, $scale, $i) {
6451
+			// 1, 2, 3 - rgb
6452
+			// 4, 5, 6 - hsl
6453
+			// 7 - a
6454
+			switch ($i) {
6455
+				case 1:
6456
+				case 2:
6457
+				case 3:
6458
+					$max = 255;
6459
+					break;
6460
+
6461
+				case 4:
6462
+					$max = 360;
6463
+					break;
6464
+
6465
+				case 7:
6466
+					$max = 1;
6467
+					break;
6468
+
6469
+				default:
6470
+					$max = 100;
6471
+			}
6472
+
6473
+			$scale = $scale / 100;
6474
+
6475
+			if ($scale < 0) {
6476
+				return $base * $scale + $base;
6477
+			}
6478
+
6479
+			return ($max - $base) * $scale + $base;
6480
+		});
6481
+	}
6482
+
6483
+	protected static $libIeHexStr = ['color'];
6484
+	protected function libIeHexStr($args)
6485
+	{
6486
+		$color = $this->coerceColor($args[0]);
6487
+
6488
+		if (\is_null($color)) {
6489
+			$this->throwError('Error: argument `$color` of `ie-hex-str($color)` must be a color');
6490
+		}
6491
+
6492
+		$color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
6493
+
6494
+		return [Type::T_STRING, '', [sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3])]];
6495
+	}
6496
+
6497
+	protected static $libRed = ['color'];
6498
+	protected function libRed($args)
6499
+	{
6500
+		$color = $this->coerceColor($args[0]);
6501
+
6502
+		if (\is_null($color)) {
6503
+			$this->throwError('Error: argument `$color` of `red($color)` must be a color');
6504
+		}
6505
+
6506
+		return $color[1];
6507
+	}
6508
+
6509
+	protected static $libGreen = ['color'];
6510
+	protected function libGreen($args)
6511
+	{
6512
+		$color = $this->coerceColor($args[0]);
6513
+
6514
+		if (\is_null($color)) {
6515
+			$this->throwError('Error: argument `$color` of `green($color)` must be a color');
6516
+		}
6517
+
6518
+		return $color[2];
6519
+	}
6520
+
6521
+	protected static $libBlue = ['color'];
6522
+	protected function libBlue($args)
6523
+	{
6524
+		$color = $this->coerceColor($args[0]);
6525
+
6526
+		if (\is_null($color)) {
6527
+			$this->throwError('Error: argument `$color` of `blue($color)` must be a color');
6528
+		}
6529
+
6530
+		return $color[3];
6531
+	}
6532
+
6533
+	protected static $libAlpha = ['color'];
6534
+	protected function libAlpha($args)
6535
+	{
6536
+		if ($color = $this->coerceColor($args[0])) {
6537
+			return isset($color[4]) ? $color[4] : 1;
6538
+		}
6539
+
6540
+		// this might be the IE function, so return value unchanged
6541
+		return null;
6542
+	}
6543
+
6544
+	protected static $libOpacity = ['color'];
6545
+	protected function libOpacity($args)
6546
+	{
6547
+		$value = $args[0];
6548
+
6549
+		if ($value[0] === Type::T_NUMBER) {
6550
+			return null;
6551
+		}
6552
+
6553
+		return $this->libAlpha($args);
6554
+	}
6555
+
6556
+	// mix two colors
6557
+	protected static $libMix = [
6558
+		['color1', 'color2', 'weight:0.5'],
6559
+		['color-1', 'color-2', 'weight:0.5']
6560
+		];
6561
+	protected function libMix($args)
6562
+	{
6563
+		list($first, $second, $weight) = $args;
6564
+
6565
+		$first = $this->assertColor($first);
6566
+		$second = $this->assertColor($second);
6567
+
6568
+		if (! isset($weight)) {
6569
+			$weight = 0.5;
6570
+		} else {
6571
+			$weight = $this->coercePercent($weight);
6572
+		}
6573
+
6574
+		$firstAlpha = isset($first[4]) ? $first[4] : 1;
6575
+		$secondAlpha = isset($second[4]) ? $second[4] : 1;
6576
+
6577
+		$w = $weight * 2 - 1;
6578
+		$a = $firstAlpha - $secondAlpha;
6579
+
6580
+		$w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
6581
+		$w2 = 1.0 - $w1;
6582
+
6583
+		$new = [Type::T_COLOR,
6584
+			$w1 * $first[1] + $w2 * $second[1],
6585
+			$w1 * $first[2] + $w2 * $second[2],
6586
+			$w1 * $first[3] + $w2 * $second[3],
6587
+		];
6588
+
6589
+		if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
6590
+			$new[] = $firstAlpha * $weight + $secondAlpha * (1 - $weight);
6591
+		}
6592
+
6593
+		return $this->fixColor($new);
6594
+	}
6595
+
6596
+	protected static $libHsl = [
6597
+		['channels'],
6598
+		['hue', 'saturation', 'lightness'],
6599
+		['hue', 'saturation', 'lightness', 'alpha'] ];
6600
+	protected function libHsl($args, $kwargs, $funcName = 'hsl')
6601
+	{
6602
+		$args_to_check = $args;
6603
+
6604
+		if (\count($args) == 1) {
6605
+			if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < 3 || \count($args[0][2]) > 4) {
6606
+				return [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6607
+			}
6608
+
6609
+			$args = $args[0][2];
6610
+			$args_to_check = $kwargs['channels'][2];
6611
+		}
6612
+
6613
+		$hue = $this->compileColorPartValue($args[0], 0, 360, false, false, true);
6614
+		$saturation = $this->compileColorPartValue($args[1], 0, 100, false);
6615
+		$lightness = $this->compileColorPartValue($args[2], 0, 100, false);
6616
+
6617
+		foreach ($kwargs as $k => $arg) {
6618
+			if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
6619
+				return null;
6620
+			}
6621
+		}
6622
+
6623
+		foreach ($args_to_check as $k => $arg) {
6624
+			if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
6625
+				if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
6626
+					return null;
6627
+				}
6628
+
6629
+				$args[$k] = $this->stringifyFncallArgs($arg);
6630
+				$hue = '';
6631
+			}
6632
+
6633
+			if (
6634
+				$k >= 2 && count($args) === 4 &&
6635
+				in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
6636
+				in_array($arg[1], ['calc','env'])
6637
+			) {
6638
+				return null;
6639
+			}
6640
+		}
6641
+
6642
+		$alpha = null;
6643
+
6644
+		if (\count($args) === 4) {
6645
+			$alpha = $this->compileColorPartValue($args[3], 0, 100, false);
6646
+
6647
+			if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness) || ! is_numeric($alpha)) {
6648
+				return [Type::T_STRING, '',
6649
+					[$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6650
+			}
6651
+		} else {
6652
+			if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness)) {
6653
+				return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6654
+			}
6655
+		}
6656
+
6657
+		$color = $this->toRGB($hue, $saturation, $lightness);
6658
+
6659
+		if (! \is_null($alpha)) {
6660
+			$color[4] = $alpha;
6661
+		}
6662
+
6663
+		return $color;
6664
+	}
6665
+
6666
+	protected static $libHsla = [
6667
+			['channels'],
6668
+			['hue', 'saturation', 'lightness'],
6669
+			['hue', 'saturation', 'lightness', 'alpha']];
6670
+	protected function libHsla($args, $kwargs)
6671
+	{
6672
+		return $this->libHsl($args, $kwargs, 'hsla');
6673
+	}
6674
+
6675
+	protected static $libHue = ['color'];
6676
+	protected function libHue($args)
6677
+	{
6678
+		$color = $this->assertColor($args[0]);
6679
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
6680
+
6681
+		return new Node\Number($hsl[1], 'deg');
6682
+	}
6683
+
6684
+	protected static $libSaturation = ['color'];
6685
+	protected function libSaturation($args)
6686
+	{
6687
+		$color = $this->assertColor($args[0]);
6688
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
6689
+
6690
+		return new Node\Number($hsl[2], '%');
6691
+	}
6692
+
6693
+	protected static $libLightness = ['color'];
6694
+	protected function libLightness($args)
6695
+	{
6696
+		$color = $this->assertColor($args[0]);
6697
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
6698
+
6699
+		return new Node\Number($hsl[3], '%');
6700
+	}
6701
+
6702
+	protected function adjustHsl($color, $idx, $amount)
6703
+	{
6704
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
6705
+		$hsl[$idx] += $amount;
6706
+		$out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
6707
+
6708
+		if (isset($color[4])) {
6709
+			$out[4] = $color[4];
6710
+		}
6711
+
6712
+		return $out;
6713
+	}
6714
+
6715
+	protected static $libAdjustHue = ['color', 'degrees'];
6716
+	protected function libAdjustHue($args)
6717
+	{
6718
+		$color = $this->assertColor($args[0]);
6719
+		$degrees = $this->assertNumber($args[1]);
6720
+
6721
+		return $this->adjustHsl($color, 1, $degrees);
6722
+	}
6723
+
6724
+	protected static $libLighten = ['color', 'amount'];
6725
+	protected function libLighten($args)
6726
+	{
6727
+		$color = $this->assertColor($args[0]);
6728
+		$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6729
+
6730
+		return $this->adjustHsl($color, 3, $amount);
6731
+	}
6732
+
6733
+	protected static $libDarken = ['color', 'amount'];
6734
+	protected function libDarken($args)
6735
+	{
6736
+		$color = $this->assertColor($args[0]);
6737
+		$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
6738
+
6739
+		return $this->adjustHsl($color, 3, -$amount);
6740
+	}
6741
+
6742
+	protected static $libSaturate = [['color', 'amount'], ['amount']];
6743
+	protected function libSaturate($args)
6744
+	{
6745
+		$value = $args[0];
6746
+
6747
+		if ($value[0] === Type::T_NUMBER) {
6748
+			return null;
6749
+		}
6750
+
6751
+		if (count($args) === 1) {
6752
+			$val = $this->compileValue($value);
6753
+			throw $this->error("\$amount: $val is not a number");
6754
+		}
6755
+
6756
+		$color = $this->assertColor($value);
6757
+		$amount = 100 * $this->coercePercent($args[1]);
6758
+
6759
+		return $this->adjustHsl($color, 2, $amount);
6760
+	}
6761
+
6762
+	protected static $libDesaturate = ['color', 'amount'];
6763
+	protected function libDesaturate($args)
6764
+	{
6765
+		$color = $this->assertColor($args[0]);
6766
+		$amount = 100 * $this->coercePercent($args[1]);
6767
+
6768
+		return $this->adjustHsl($color, 2, -$amount);
6769
+	}
6770
+
6771
+	protected static $libGrayscale = ['color'];
6772
+	protected function libGrayscale($args)
6773
+	{
6774
+		$value = $args[0];
6775
+
6776
+		if ($value[0] === Type::T_NUMBER) {
6777
+			return null;
6778
+		}
6779
+
6780
+		return $this->adjustHsl($this->assertColor($value), 2, -100);
6781
+	}
6782
+
6783
+	protected static $libComplement = ['color'];
6784
+	protected function libComplement($args)
6785
+	{
6786
+		return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
6787
+	}
6788
+
6789
+	protected static $libInvert = ['color', 'weight:1'];
6790
+	protected function libInvert($args)
6791
+	{
6792
+		list($value, $weight) = $args;
6793
+
6794
+		if (! isset($weight)) {
6795
+			$weight = 1;
6796
+		} else {
6797
+			$weight = $this->coercePercent($weight);
6798
+		}
6799
+
6800
+		if ($value[0] === Type::T_NUMBER) {
6801
+			return null;
6802
+		}
6803
+
6804
+		$color = $this->assertColor($value);
6805
+		$inverted = $color;
6806
+		$inverted[1] = 255 - $inverted[1];
6807
+		$inverted[2] = 255 - $inverted[2];
6808
+		$inverted[3] = 255 - $inverted[3];
6809
+
6810
+		if ($weight < 1) {
6811
+			return $this->libMix([$inverted, $color, [Type::T_NUMBER, $weight]]);
6812
+		}
6813
+
6814
+		return $inverted;
6815
+	}
6816
+
6817
+	// increases opacity by amount
6818
+	protected static $libOpacify = ['color', 'amount'];
6819
+	protected function libOpacify($args)
6820
+	{
6821
+		$color = $this->assertColor($args[0]);
6822
+		$amount = $this->coercePercent($args[1]);
6823
+
6824
+		$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
6825
+		$color[4] = min(1, max(0, $color[4]));
6826
+
6827
+		return $color;
6828
+	}
6829
+
6830
+	protected static $libFadeIn = ['color', 'amount'];
6831
+	protected function libFadeIn($args)
6832
+	{
6833
+		return $this->libOpacify($args);
6834
+	}
6835
+
6836
+	// decreases opacity by amount
6837
+	protected static $libTransparentize = ['color', 'amount'];
6838
+	protected function libTransparentize($args)
6839
+	{
6840
+		$color = $this->assertColor($args[0]);
6841
+		$amount = $this->coercePercent($args[1]);
6842
+
6843
+		$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
6844
+		$color[4] = min(1, max(0, $color[4]));
6845
+
6846
+		return $color;
6847
+	}
6848
+
6849
+	protected static $libFadeOut = ['color', 'amount'];
6850
+	protected function libFadeOut($args)
6851
+	{
6852
+		return $this->libTransparentize($args);
6853
+	}
6854
+
6855
+	protected static $libUnquote = ['string'];
6856
+	protected function libUnquote($args)
6857
+	{
6858
+		$str = $args[0];
6859
+
6860
+		if ($str[0] === Type::T_STRING) {
6861
+			$str[1] = '';
6862
+		}
6863
+
6864
+		return $str;
6865
+	}
6866
+
6867
+	protected static $libQuote = ['string'];
6868
+	protected function libQuote($args)
6869
+	{
6870
+		$value = $args[0];
6871
+
6872
+		if ($value[0] === Type::T_STRING && ! empty($value[1])) {
6873
+			return $value;
6874
+		}
6875
+
6876
+		return [Type::T_STRING, '"', [$value]];
6877
+	}
6878
+
6879
+	protected static $libPercentage = ['number'];
6880
+	protected function libPercentage($args)
6881
+	{
6882
+		return new Node\Number($this->coercePercent($args[0]) * 100, '%');
6883
+	}
6884
+
6885
+	protected static $libRound = ['number'];
6886
+	protected function libRound($args)
6887
+	{
6888
+		$num = $args[0];
6889
+
6890
+		return new Node\Number(round($num[1]), $num[2]);
6891
+	}
6892
+
6893
+	protected static $libFloor = ['number'];
6894
+	protected function libFloor($args)
6895
+	{
6896
+		$num = $args[0];
6897
+
6898
+		return new Node\Number(floor($num[1]), $num[2]);
6899
+	}
6900
+
6901
+	protected static $libCeil = ['number'];
6902
+	protected function libCeil($args)
6903
+	{
6904
+		$num = $args[0];
6905
+
6906
+		return new Node\Number(ceil($num[1]), $num[2]);
6907
+	}
6908
+
6909
+	protected static $libAbs = ['number'];
6910
+	protected function libAbs($args)
6911
+	{
6912
+		$num = $args[0];
6913
+
6914
+		return new Node\Number(abs($num[1]), $num[2]);
6915
+	}
6916
+
6917
+	protected function libMin($args)
6918
+	{
6919
+		$numbers = $this->getNormalizedNumbers($args);
6920
+		$minOriginal = null;
6921
+		$minNormalized = null;
6922
+
6923
+		foreach ($numbers as $key => $pair) {
6924
+			list($original, $normalized) = $pair;
6925
+
6926
+			if (\is_null($normalized) || \is_null($minNormalized)) {
6927
+				if (\is_null($minOriginal) || $original[1] <= $minOriginal[1]) {
6928
+					$minOriginal = $original;
6929
+					$minNormalized = $normalized;
6930
+				}
6931
+			} elseif ($normalized[1] <= $minNormalized[1]) {
6932
+				$minOriginal = $original;
6933
+				$minNormalized = $normalized;
6934
+			}
6935
+		}
6936
+
6937
+		return $minOriginal;
6938
+	}
6939
+
6940
+	protected function libMax($args)
6941
+	{
6942
+		$numbers = $this->getNormalizedNumbers($args);
6943
+		$maxOriginal = null;
6944
+		$maxNormalized = null;
6945
+
6946
+		foreach ($numbers as $key => $pair) {
6947
+			list($original, $normalized) = $pair;
6948
+
6949
+			if (\is_null($normalized) || \is_null($maxNormalized)) {
6950
+				if (\is_null($maxOriginal) || $original[1] >= $maxOriginal[1]) {
6951
+					$maxOriginal = $original;
6952
+					$maxNormalized = $normalized;
6953
+				}
6954
+			} elseif ($normalized[1] >= $maxNormalized[1]) {
6955
+				$maxOriginal = $original;
6956
+				$maxNormalized = $normalized;
6957
+			}
6958
+		}
6959
+
6960
+		return $maxOriginal;
6961
+	}
6962
+
6963
+	/**
6964
+	 * Helper to normalize args containing numbers
6965
+	 *
6966
+	 * @param array $args
6967
+	 *
6968
+	 * @return array
6969
+	 */
6970
+	protected function getNormalizedNumbers($args)
6971
+	{
6972
+		$unit         = null;
6973
+		$originalUnit = null;
6974
+		$numbers      = [];
6975
+
6976
+		foreach ($args as $key => $item) {
6977
+			if ($item[0] !== Type::T_NUMBER) {
6978
+				throw $this->error('%s is not a number', $item[0]);
6979
+			}
6980
+
6981
+			$number = $item->normalize();
6982
+
6983
+			if (empty($unit)) {
6984
+				$unit = $number[2];
6985
+				$originalUnit = $item->unitStr();
6986
+			} elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
6987
+				throw $this->error('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
6988
+			}
6989
+
6990
+			$numbers[$key] = [$args[$key], empty($number[2]) ? null : $number];
6991
+		}
6992
+
6993
+		return $numbers;
6994
+	}
6995
+
6996
+	protected static $libLength = ['list'];
6997
+	protected function libLength($args)
6998
+	{
6999
+		$list = $this->coerceList($args[0], ',', true);
7000
+
7001
+		return \count($list[2]);
7002
+	}
7003
+
7004
+	//protected static $libListSeparator = ['list...'];
7005
+	protected function libListSeparator($args)
7006
+	{
7007
+		if (\count($args) > 1) {
7008
+			return 'comma';
7009
+		}
7010
+
7011
+		$list = $this->coerceList($args[0]);
7012
+
7013
+		if (\count($list[2]) <= 1) {
7014
+			return 'space';
7015
+		}
7016
+
7017
+		if ($list[1] === ',') {
7018
+			return 'comma';
7019
+		}
7020
+
7021
+		return 'space';
7022
+	}
7023
+
7024
+	protected static $libNth = ['list', 'n'];
7025
+	protected function libNth($args)
7026
+	{
7027
+		$list = $this->coerceList($args[0], ',', false);
7028
+		$n = $this->assertNumber($args[1]);
7029
+
7030
+		if ($n > 0) {
7031
+			$n--;
7032
+		} elseif ($n < 0) {
7033
+			$n += \count($list[2]);
7034
+		}
7035
+
7036
+		return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue;
7037
+	}
7038
+
7039
+	protected static $libSetNth = ['list', 'n', 'value'];
7040
+	protected function libSetNth($args)
7041
+	{
7042
+		$list = $this->coerceList($args[0]);
7043
+		$n = $this->assertNumber($args[1]);
7044
+
7045
+		if ($n > 0) {
7046
+			$n--;
7047
+		} elseif ($n < 0) {
7048
+			$n += \count($list[2]);
7049
+		}
7050
+
7051
+		if (! isset($list[2][$n])) {
7052
+			throw $this->error('Invalid argument for "n"');
7053
+		}
7054
+
7055
+		$list[2][$n] = $args[2];
7056
+
7057
+		return $list;
7058
+	}
7059
+
7060
+	protected static $libMapGet = ['map', 'key'];
7061
+	protected function libMapGet($args)
7062
+	{
7063
+		$map = $this->assertMap($args[0]);
7064
+		$key = $args[1];
7065
+
7066
+		if (! \is_null($key)) {
7067
+			$key = $this->compileStringContent($this->coerceString($key));
7068
+
7069
+			for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7070
+				if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
7071
+					return $map[2][$i];
7072
+				}
7073
+			}
7074
+		}
7075
+
7076
+		return static::$null;
7077
+	}
7078
+
7079
+	protected static $libMapKeys = ['map'];
7080
+	protected function libMapKeys($args)
7081
+	{
7082
+		$map = $this->assertMap($args[0]);
7083
+		$keys = $map[1];
7084
+
7085
+		return [Type::T_LIST, ',', $keys];
7086
+	}
7087
+
7088
+	protected static $libMapValues = ['map'];
7089
+	protected function libMapValues($args)
7090
+	{
7091
+		$map = $this->assertMap($args[0]);
7092
+		$values = $map[2];
7093
+
7094
+		return [Type::T_LIST, ',', $values];
7095
+	}
7096
+
7097
+	protected static $libMapRemove = ['map', 'key...'];
7098
+	protected function libMapRemove($args)
7099
+	{
7100
+		$map = $this->assertMap($args[0]);
7101
+		$keyList = $this->assertList($args[1]);
7102
+
7103
+		$keys = [];
7104
+
7105
+		foreach ($keyList[2] as $key) {
7106
+			$keys[] = $this->compileStringContent($this->coerceString($key));
7107
+		}
7108
+
7109
+		for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7110
+			if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) {
7111
+				array_splice($map[1], $i, 1);
7112
+				array_splice($map[2], $i, 1);
7113
+			}
7114
+		}
7115
+
7116
+		return $map;
7117
+	}
7118
+
7119
+	protected static $libMapHasKey = ['map', 'key'];
7120
+	protected function libMapHasKey($args)
7121
+	{
7122
+		$map = $this->assertMap($args[0]);
7123
+		$key = $this->compileStringContent($this->coerceString($args[1]));
7124
+
7125
+		for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
7126
+			if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
7127
+				return true;
7128
+			}
7129
+		}
7130
+
7131
+		return false;
7132
+	}
7133
+
7134
+	protected static $libMapMerge = [
7135
+		['map1', 'map2'],
7136
+		['map-1', 'map-2']
7137
+	];
7138
+	protected function libMapMerge($args)
7139
+	{
7140
+		$map1 = $this->assertMap($args[0]);
7141
+		$map2 = $this->assertMap($args[1]);
7142
+
7143
+		foreach ($map2[1] as $i2 => $key2) {
7144
+			$key = $this->compileStringContent($this->coerceString($key2));
7145
+
7146
+			foreach ($map1[1] as $i1 => $key1) {
7147
+				if ($key === $this->compileStringContent($this->coerceString($key1))) {
7148
+					$map1[2][$i1] = $map2[2][$i2];
7149
+					continue 2;
7150
+				}
7151
+			}
7152
+
7153
+			$map1[1][] = $map2[1][$i2];
7154
+			$map1[2][] = $map2[2][$i2];
7155
+		}
7156
+
7157
+		return $map1;
7158
+	}
7159
+
7160
+	protected static $libKeywords = ['args'];
7161
+	protected function libKeywords($args)
7162
+	{
7163
+		$this->assertList($args[0]);
7164
+
7165
+		$keys = [];
7166
+		$values = [];
7167
+
7168
+		foreach ($args[0][2] as $name => $arg) {
7169
+			$keys[] = [Type::T_KEYWORD, $name];
7170
+			$values[] = $arg;
7171
+		}
7172
+
7173
+		return [Type::T_MAP, $keys, $values];
7174
+	}
7175
+
7176
+	protected static $libIsBracketed = ['list'];
7177
+	protected function libIsBracketed($args)
7178
+	{
7179
+		$list = $args[0];
7180
+		$this->coerceList($list, ' ');
7181
+
7182
+		if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
7183
+			return true;
7184
+		}
7185
+
7186
+		return false;
7187
+	}
7188
+
7189
+	protected function listSeparatorForJoin($list1, $sep)
7190
+	{
7191
+		if (! isset($sep)) {
7192
+			return $list1[1];
7193
+		}
7194
+
7195
+		switch ($this->compileValue($sep)) {
7196
+			case 'comma':
7197
+				return ',';
7198
+
7199
+			case 'space':
7200
+				return ' ';
7201
+
7202
+			default:
7203
+				return $list1[1];
7204
+		}
7205
+	}
7206
+
7207
+	protected static $libJoin = ['list1', 'list2', 'separator:null', 'bracketed:auto'];
7208
+	protected function libJoin($args)
7209
+	{
7210
+		list($list1, $list2, $sep, $bracketed) = $args;
7211
+
7212
+		$list1 = $this->coerceList($list1, ' ', true);
7213
+		$list2 = $this->coerceList($list2, ' ', true);
7214
+		$sep   = $this->listSeparatorForJoin($list1, $sep);
7215
+
7216
+		if ($bracketed === static::$true) {
7217
+			$bracketed = true;
7218
+		} elseif ($bracketed === static::$false) {
7219
+			$bracketed = false;
7220
+		} elseif ($bracketed === [Type::T_KEYWORD, 'auto']) {
7221
+			$bracketed = 'auto';
7222
+		} elseif ($bracketed === static::$null) {
7223
+			$bracketed = false;
7224
+		} else {
7225
+			$bracketed = $this->compileValue($bracketed);
7226
+			$bracketed = ! ! $bracketed;
7227
+
7228
+			if ($bracketed === true) {
7229
+				$bracketed = true;
7230
+			}
7231
+		}
7232
+
7233
+		if ($bracketed === 'auto') {
7234
+			$bracketed = false;
7235
+
7236
+			if (! empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') {
7237
+				$bracketed = true;
7238
+			}
7239
+		}
7240
+
7241
+		$res = [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])];
7242
+
7243
+		if (isset($list1['enclosing'])) {
7244
+			$res['enlcosing'] = $list1['enclosing'];
7245
+		}
7246
+
7247
+		if ($bracketed) {
7248
+			$res['enclosing'] = 'bracket';
7249
+		}
7250
+
7251
+		return $res;
7252
+	}
7253
+
7254
+	protected static $libAppend = ['list', 'val', 'separator:null'];
7255
+	protected function libAppend($args)
7256
+	{
7257
+		list($list1, $value, $sep) = $args;
7258
+
7259
+		$list1 = $this->coerceList($list1, ' ', true);
7260
+		$sep   = $this->listSeparatorForJoin($list1, $sep);
7261
+		$res   = [Type::T_LIST, $sep, array_merge($list1[2], [$value])];
7262
+
7263
+		if (isset($list1['enclosing'])) {
7264
+			$res['enclosing'] = $list1['enclosing'];
7265
+		}
7266
+
7267
+		return $res;
7268
+	}
7269
+
7270
+	protected function libZip($args)
7271
+	{
7272
+		foreach ($args as $key => $arg) {
7273
+			$args[$key] = $this->coerceList($arg);
7274
+		}
7275
+
7276
+		$lists = [];
7277
+		$firstList = array_shift($args);
7278
+
7279
+		foreach ($firstList[2] as $key => $item) {
7280
+			$list = [Type::T_LIST, '', [$item]];
7281
+
7282
+			foreach ($args as $arg) {
7283
+				if (isset($arg[2][$key])) {
7284
+					$list[2][] = $arg[2][$key];
7285
+				} else {
7286
+					break 2;
7287
+				}
7288
+			}
7289
+
7290
+			$lists[] = $list;
7291
+		}
7292
+
7293
+		return [Type::T_LIST, ',', $lists];
7294
+	}
7295
+
7296
+	protected static $libTypeOf = ['value'];
7297
+	protected function libTypeOf($args)
7298
+	{
7299
+		$value = $args[0];
7300
+
7301
+		switch ($value[0]) {
7302
+			case Type::T_KEYWORD:
7303
+				if ($value === static::$true || $value === static::$false) {
7304
+					return 'bool';
7305
+				}
7306
+
7307
+				if ($this->coerceColor($value)) {
7308
+					return 'color';
7309
+				}
7310
+
7311
+				// fall-thru
7312
+			case Type::T_FUNCTION:
7313
+				return 'string';
7314
+
7315
+			case Type::T_FUNCTION_REFERENCE:
7316
+				return 'function';
7317
+
7318
+			case Type::T_LIST:
7319
+				if (isset($value[3]) && $value[3]) {
7320
+					return 'arglist';
7321
+				}
7322
+
7323
+				// fall-thru
7324
+			default:
7325
+				return $value[0];
7326
+		}
7327
+	}
7328
+
7329
+	protected static $libUnit = ['number'];
7330
+	protected function libUnit($args)
7331
+	{
7332
+		$num = $args[0];
7333
+
7334
+		if ($num[0] === Type::T_NUMBER) {
7335
+			return [Type::T_STRING, '"', [$num->unitStr()]];
7336
+		}
7337
+
7338
+		return '';
7339
+	}
7341 7340
 
7342
-    protected static $libUnitless = ['number'];
7343
-    protected function libUnitless($args)
7344
-    {
7345
-        $value = $args[0];
7341
+	protected static $libUnitless = ['number'];
7342
+	protected function libUnitless($args)
7343
+	{
7344
+		$value = $args[0];
7346 7345
 
7347
-        return $value[0] === Type::T_NUMBER && $value->unitless();
7348
-    }
7349
-
7350
-    protected static $libComparable = [
7351
-        ['number1', 'number2'],
7352
-        ['number-1', 'number-2']
7353
-    ];
7354
-    protected function libComparable($args)
7355
-    {
7356
-        list($number1, $number2) = $args;
7346
+		return $value[0] === Type::T_NUMBER && $value->unitless();
7347
+	}
7348
+
7349
+	protected static $libComparable = [
7350
+		['number1', 'number2'],
7351
+		['number-1', 'number-2']
7352
+	];
7353
+	protected function libComparable($args)
7354
+	{
7355
+		list($number1, $number2) = $args;
7357 7356
 
7358
-        if (
7359
-            ! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
7360
-            ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
7361
-        ) {
7362
-            throw $this->error('Invalid argument(s) for "comparable"');
7363
-        }
7364
-
7365
-        $number1 = $number1->normalize();
7366
-        $number2 = $number2->normalize();
7367
-
7368
-        return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
7369
-    }
7370
-
7371
-    protected static $libStrIndex = ['string', 'substring'];
7372
-    protected function libStrIndex($args)
7373
-    {
7374
-        $string = $this->coerceString($args[0]);
7375
-        $stringContent = $this->compileStringContent($string);
7376
-
7377
-        $substring = $this->coerceString($args[1]);
7378
-        $substringContent = $this->compileStringContent($substring);
7379
-
7380
-        $result = strpos($stringContent, $substringContent);
7381
-
7382
-        return $result === false ? static::$null : new Node\Number($result + 1, '');
7383
-    }
7384
-
7385
-    protected static $libStrInsert = ['string', 'insert', 'index'];
7386
-    protected function libStrInsert($args)
7387
-    {
7388
-        $string = $this->coerceString($args[0]);
7389
-        $stringContent = $this->compileStringContent($string);
7357
+		if (
7358
+			! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
7359
+			! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
7360
+		) {
7361
+			throw $this->error('Invalid argument(s) for "comparable"');
7362
+		}
7363
+
7364
+		$number1 = $number1->normalize();
7365
+		$number2 = $number2->normalize();
7366
+
7367
+		return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
7368
+	}
7369
+
7370
+	protected static $libStrIndex = ['string', 'substring'];
7371
+	protected function libStrIndex($args)
7372
+	{
7373
+		$string = $this->coerceString($args[0]);
7374
+		$stringContent = $this->compileStringContent($string);
7375
+
7376
+		$substring = $this->coerceString($args[1]);
7377
+		$substringContent = $this->compileStringContent($substring);
7378
+
7379
+		$result = strpos($stringContent, $substringContent);
7380
+
7381
+		return $result === false ? static::$null : new Node\Number($result + 1, '');
7382
+	}
7383
+
7384
+	protected static $libStrInsert = ['string', 'insert', 'index'];
7385
+	protected function libStrInsert($args)
7386
+	{
7387
+		$string = $this->coerceString($args[0]);
7388
+		$stringContent = $this->compileStringContent($string);
7390 7389
 
7391
-        $insert = $this->coerceString($args[1]);
7392
-        $insertContent = $this->compileStringContent($insert);
7390
+		$insert = $this->coerceString($args[1]);
7391
+		$insertContent = $this->compileStringContent($insert);
7393 7392
 
7394
-        list(, $index) = $args[2];
7395
-
7396
-        $string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
7397
-
7398
-        return $string;
7399
-    }
7393
+		list(, $index) = $args[2];
7394
+
7395
+		$string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
7396
+
7397
+		return $string;
7398
+	}
7400 7399
 
7401
-    protected static $libStrLength = ['string'];
7402
-    protected function libStrLength($args)
7403
-    {
7404
-        $string = $this->coerceString($args[0]);
7405
-        $stringContent = $this->compileStringContent($string);
7400
+	protected static $libStrLength = ['string'];
7401
+	protected function libStrLength($args)
7402
+	{
7403
+		$string = $this->coerceString($args[0]);
7404
+		$stringContent = $this->compileStringContent($string);
7406 7405
 
7407
-        return new Node\Number(\strlen($stringContent), '');
7408
-    }
7406
+		return new Node\Number(\strlen($stringContent), '');
7407
+	}
7409 7408
 
7410
-    protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
7411
-    protected function libStrSlice($args)
7412
-    {
7413
-        if (isset($args[2]) && ! $args[2][1]) {
7414
-            return static::$nullString;
7415
-        }
7416
-
7417
-        $string = $this->coerceString($args[0]);
7418
-        $stringContent = $this->compileStringContent($string);
7419
-
7420
-        $start = (int) $args[1][1];
7421
-
7422
-        if ($start > 0) {
7423
-            $start--;
7424
-        }
7425
-
7426
-        $end    = isset($args[2]) ? (int) $args[2][1] : -1;
7427
-        $length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
7428
-
7429
-        $string[2] = $length
7430
-            ? [substr($stringContent, $start, $length)]
7431
-            : [substr($stringContent, $start)];
7432
-
7433
-        return $string;
7434
-    }
7435
-
7436
-    protected static $libToLowerCase = ['string'];
7437
-    protected function libToLowerCase($args)
7438
-    {
7439
-        $string = $this->coerceString($args[0]);
7440
-        $stringContent = $this->compileStringContent($string);
7441
-
7442
-        $string[2] = [\function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
7443
-
7444
-        return $string;
7445
-    }
7446
-
7447
-    protected static $libToUpperCase = ['string'];
7448
-    protected function libToUpperCase($args)
7449
-    {
7450
-        $string = $this->coerceString($args[0]);
7451
-        $stringContent = $this->compileStringContent($string);
7452
-
7453
-        $string[2] = [\function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
7454
-
7455
-        return $string;
7456
-    }
7409
+	protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
7410
+	protected function libStrSlice($args)
7411
+	{
7412
+		if (isset($args[2]) && ! $args[2][1]) {
7413
+			return static::$nullString;
7414
+		}
7415
+
7416
+		$string = $this->coerceString($args[0]);
7417
+		$stringContent = $this->compileStringContent($string);
7418
+
7419
+		$start = (int) $args[1][1];
7420
+
7421
+		if ($start > 0) {
7422
+			$start--;
7423
+		}
7424
+
7425
+		$end    = isset($args[2]) ? (int) $args[2][1] : -1;
7426
+		$length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
7427
+
7428
+		$string[2] = $length
7429
+			? [substr($stringContent, $start, $length)]
7430
+			: [substr($stringContent, $start)];
7431
+
7432
+		return $string;
7433
+	}
7434
+
7435
+	protected static $libToLowerCase = ['string'];
7436
+	protected function libToLowerCase($args)
7437
+	{
7438
+		$string = $this->coerceString($args[0]);
7439
+		$stringContent = $this->compileStringContent($string);
7440
+
7441
+		$string[2] = [\function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
7442
+
7443
+		return $string;
7444
+	}
7445
+
7446
+	protected static $libToUpperCase = ['string'];
7447
+	protected function libToUpperCase($args)
7448
+	{
7449
+		$string = $this->coerceString($args[0]);
7450
+		$stringContent = $this->compileStringContent($string);
7451
+
7452
+		$string[2] = [\function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
7453
+
7454
+		return $string;
7455
+	}
7457 7456
 
7458
-    protected static $libFeatureExists = ['feature'];
7459
-    protected function libFeatureExists($args)
7460
-    {
7461
-        $string = $this->coerceString($args[0]);
7462
-        $name = $this->compileStringContent($string);
7457
+	protected static $libFeatureExists = ['feature'];
7458
+	protected function libFeatureExists($args)
7459
+	{
7460
+		$string = $this->coerceString($args[0]);
7461
+		$name = $this->compileStringContent($string);
7463 7462
 
7464
-        return $this->toBool(
7465
-            \array_key_exists($name, $this->registeredFeatures) ? $this->registeredFeatures[$name] : false
7466
-        );
7467
-    }
7463
+		return $this->toBool(
7464
+			\array_key_exists($name, $this->registeredFeatures) ? $this->registeredFeatures[$name] : false
7465
+		);
7466
+	}
7468 7467
 
7469
-    protected static $libFunctionExists = ['name'];
7470
-    protected function libFunctionExists($args)
7471
-    {
7472
-        $string = $this->coerceString($args[0]);
7473
-        $name = $this->compileStringContent($string);
7468
+	protected static $libFunctionExists = ['name'];
7469
+	protected function libFunctionExists($args)
7470
+	{
7471
+		$string = $this->coerceString($args[0]);
7472
+		$name = $this->compileStringContent($string);
7474 7473
 
7475
-        // user defined functions
7476
-        if ($this->has(static::$namespaces['function'] . $name)) {
7477
-            return true;
7478
-        }
7474
+		// user defined functions
7475
+		if ($this->has(static::$namespaces['function'] . $name)) {
7476
+			return true;
7477
+		}
7479 7478
 
7480
-        $name = $this->normalizeName($name);
7481
-
7482
-        if (isset($this->userFunctions[$name])) {
7483
-            return true;
7484
-        }
7485
-
7486
-        // built-in functions
7487
-        $f = $this->getBuiltinFunction($name);
7488
-
7489
-        return $this->toBool(\is_callable($f));
7490
-    }
7491
-
7492
-    protected static $libGlobalVariableExists = ['name'];
7493
-    protected function libGlobalVariableExists($args)
7494
-    {
7495
-        $string = $this->coerceString($args[0]);
7496
-        $name = $this->compileStringContent($string);
7497
-
7498
-        return $this->has($name, $this->rootEnv);
7499
-    }
7500
-
7501
-    protected static $libMixinExists = ['name'];
7502
-    protected function libMixinExists($args)
7503
-    {
7504
-        $string = $this->coerceString($args[0]);
7505
-        $name = $this->compileStringContent($string);
7506
-
7507
-        return $this->has(static::$namespaces['mixin'] . $name);
7508
-    }
7509
-
7510
-    protected static $libVariableExists = ['name'];
7511
-    protected function libVariableExists($args)
7512
-    {
7513
-        $string = $this->coerceString($args[0]);
7514
-        $name = $this->compileStringContent($string);
7515
-
7516
-        return $this->has($name);
7517
-    }
7518
-
7519
-    /**
7520
-     * Workaround IE7's content counter bug.
7521
-     *
7522
-     * @param array $args
7523
-     *
7524
-     * @return array
7525
-     */
7526
-    protected function libCounter($args)
7527
-    {
7528
-        $list = array_map([$this, 'compileValue'], $args);
7529
-
7530
-        return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
7531
-    }
7532
-
7533
-    protected static $libRandom = ['limit:1'];
7534
-    protected function libRandom($args)
7535
-    {
7536
-        if (isset($args[0])) {
7537
-            $n = $this->assertNumber($args[0]);
7538
-
7539
-            if ($n < 1) {
7540
-                throw $this->error("\$limit must be greater than or equal to 1");
7541
-            }
7542
-
7543
-            if ($n - \intval($n) > 0) {
7544
-                throw $this->error("Expected \$limit to be an integer but got $n for `random`");
7545
-            }
7546
-
7547
-            return new Node\Number(mt_rand(1, \intval($n)), '');
7548
-        }
7549
-
7550
-        return new Node\Number(mt_rand(1, mt_getrandmax()), '');
7551
-    }
7552
-
7553
-    protected function libUniqueId()
7554
-    {
7555
-        static $id;
7556
-
7557
-        if (! isset($id)) {
7558
-            $id = PHP_INT_SIZE === 4
7559
-                ? mt_rand(0, pow(36, 5)) . str_pad(mt_rand(0, pow(36, 5)) % 10000000, 7, '0', STR_PAD_LEFT)
7560
-                : mt_rand(0, pow(36, 8));
7561
-        }
7562
-
7563
-        $id += mt_rand(0, 10) + 1;
7564
-
7565
-        return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
7566
-    }
7567
-
7568
-    protected function inspectFormatValue($value, $force_enclosing_display = false)
7569
-    {
7570
-        if ($value === static::$null) {
7571
-            $value = [Type::T_KEYWORD, 'null'];
7572
-        }
7573
-
7574
-        $stringValue = [$value];
7575
-
7576
-        if ($value[0] === Type::T_LIST) {
7577
-            if (end($value[2]) === static::$null) {
7578
-                array_pop($value[2]);
7579
-                $value[2][] = [Type::T_STRING, '', ['']];
7580
-                $force_enclosing_display = true;
7581
-            }
7582
-
7583
-            if (
7584
-                ! empty($value['enclosing']) &&
7585
-                ($force_enclosing_display ||
7586
-                    ($value['enclosing'] === 'bracket') ||
7587
-                    ! \count($value[2]))
7588
-            ) {
7589
-                $value['enclosing'] = 'forced_' . $value['enclosing'];
7590
-                $force_enclosing_display = true;
7591
-            }
7592
-
7593
-            foreach ($value[2] as $k => $listelement) {
7594
-                $value[2][$k] = $this->inspectFormatValue($listelement, $force_enclosing_display);
7595
-            }
7596
-
7597
-            $stringValue = [$value];
7598
-        }
7599
-
7600
-        return [Type::T_STRING, '', $stringValue];
7601
-    }
7602
-
7603
-    protected static $libInspect = ['value'];
7604
-    protected function libInspect($args)
7605
-    {
7606
-        $value = $args[0];
7607
-
7608
-        return $this->inspectFormatValue($value);
7609
-    }
7610
-
7611
-    /**
7612
-     * Preprocess selector args
7613
-     *
7614
-     * @param array $arg
7615
-     *
7616
-     * @return array|boolean
7617
-     */
7618
-    protected function getSelectorArg($arg)
7619
-    {
7620
-        static $parser = null;
7621
-
7622
-        if (\is_null($parser)) {
7623
-            $parser = $this->parserFactory(__METHOD__);
7624
-        }
7625
-
7626
-        $arg = $this->libUnquote([$arg]);
7627
-        $arg = $this->compileValue($arg);
7628
-
7629
-        $parsedSelector = [];
7630
-
7631
-        if ($parser->parseSelector($arg, $parsedSelector)) {
7632
-            $selector = $this->evalSelectors($parsedSelector);
7633
-            $gluedSelector = $this->glueFunctionSelectors($selector);
7634
-
7635
-            return $gluedSelector;
7636
-        }
7637
-
7638
-        return false;
7639
-    }
7640
-
7641
-    /**
7642
-     * Postprocess selector to output in right format
7643
-     *
7644
-     * @param array $selectors
7645
-     *
7646
-     * @return string
7647
-     */
7648
-    protected function formatOutputSelector($selectors)
7649
-    {
7650
-        $selectors = $this->collapseSelectors($selectors, true);
7651
-
7652
-        return $selectors;
7653
-    }
7654
-
7655
-    protected static $libIsSuperselector = ['super', 'sub'];
7656
-    protected function libIsSuperselector($args)
7657
-    {
7658
-        list($super, $sub) = $args;
7659
-
7660
-        $super = $this->getSelectorArg($super);
7661
-        $sub = $this->getSelectorArg($sub);
7662
-
7663
-        return $this->isSuperSelector($super, $sub);
7664
-    }
7665
-
7666
-    /**
7667
-     * Test a $super selector again $sub
7668
-     *
7669
-     * @param array $super
7670
-     * @param array $sub
7671
-     *
7672
-     * @return boolean
7673
-     */
7674
-    protected function isSuperSelector($super, $sub)
7675
-    {
7676
-        // one and only one selector for each arg
7677
-        if (! $super || \count($super) !== 1) {
7678
-            throw $this->error('Invalid super selector for isSuperSelector()');
7679
-        }
7680
-
7681
-        if (! $sub || \count($sub) !== 1) {
7682
-            throw $this->error('Invalid sub selector for isSuperSelector()');
7683
-        }
7684
-
7685
-        $super = reset($super);
7686
-        $sub = reset($sub);
7687
-
7688
-        $i = 0;
7689
-        $nextMustMatch = false;
7690
-
7691
-        foreach ($super as $node) {
7692
-            $compound = '';
7693
-
7694
-            array_walk_recursive(
7695
-                $node,
7696
-                function ($value, $key) use (&$compound) {
7697
-                    $compound .= $value;
7698
-                }
7699
-            );
7700
-
7701
-            if ($this->isImmediateRelationshipCombinator($compound)) {
7702
-                if ($node !== $sub[$i]) {
7703
-                    return false;
7704
-                }
7705
-
7706
-                $nextMustMatch = true;
7707
-                $i++;
7708
-            } else {
7709
-                while ($i < \count($sub) && ! $this->isSuperPart($node, $sub[$i])) {
7710
-                    if ($nextMustMatch) {
7711
-                        return false;
7712
-                    }
7713
-
7714
-                    $i++;
7715
-                }
7716
-
7717
-                if ($i >= \count($sub)) {
7718
-                    return false;
7719
-                }
7720
-
7721
-                $nextMustMatch = false;
7722
-                $i++;
7723
-            }
7724
-        }
7725
-
7726
-        return true;
7727
-    }
7728
-
7729
-    /**
7730
-     * Test a part of super selector again a part of sub selector
7731
-     *
7732
-     * @param array $superParts
7733
-     * @param array $subParts
7734
-     *
7735
-     * @return boolean
7736
-     */
7737
-    protected function isSuperPart($superParts, $subParts)
7738
-    {
7739
-        $i = 0;
7740
-
7741
-        foreach ($superParts as $superPart) {
7742
-            while ($i < \count($subParts) && $subParts[$i] !== $superPart) {
7743
-                $i++;
7744
-            }
7745
-
7746
-            if ($i >= \count($subParts)) {
7747
-                return false;
7748
-            }
7749
-
7750
-            $i++;
7751
-        }
7752
-
7753
-        return true;
7754
-    }
7755
-
7756
-    protected static $libSelectorAppend = ['selector...'];
7757
-    protected function libSelectorAppend($args)
7758
-    {
7759
-        // get the selector... list
7760
-        $args = reset($args);
7761
-        $args = $args[2];
7762
-
7763
-        if (\count($args) < 1) {
7764
-            throw $this->error('selector-append() needs at least 1 argument');
7765
-        }
7766
-
7767
-        $selectors = array_map([$this, 'getSelectorArg'], $args);
7768
-
7769
-        return $this->formatOutputSelector($this->selectorAppend($selectors));
7770
-    }
7771
-
7772
-    /**
7773
-     * Append parts of the last selector in the list to the previous, recursively
7774
-     *
7775
-     * @param array $selectors
7776
-     *
7777
-     * @return array
7778
-     *
7779
-     * @throws \ScssPhp\ScssPhp\Exception\CompilerException
7780
-     */
7781
-    protected function selectorAppend($selectors)
7782
-    {
7783
-        $lastSelectors = array_pop($selectors);
7784
-
7785
-        if (! $lastSelectors) {
7786
-            throw $this->error('Invalid selector list in selector-append()');
7787
-        }
7788
-
7789
-        while (\count($selectors)) {
7790
-            $previousSelectors = array_pop($selectors);
7791
-
7792
-            if (! $previousSelectors) {
7793
-                throw $this->error('Invalid selector list in selector-append()');
7794
-            }
7795
-
7796
-            // do the trick, happening $lastSelector to $previousSelector
7797
-            $appended = [];
7798
-
7799
-            foreach ($lastSelectors as $lastSelector) {
7800
-                $previous = $previousSelectors;
7801
-
7802
-                foreach ($lastSelector as $lastSelectorParts) {
7803
-                    foreach ($lastSelectorParts as $lastSelectorPart) {
7804
-                        foreach ($previous as $i => $previousSelector) {
7805
-                            foreach ($previousSelector as $j => $previousSelectorParts) {
7806
-                                $previous[$i][$j][] = $lastSelectorPart;
7807
-                            }
7808
-                        }
7809
-                    }
7810
-                }
7811
-
7812
-                foreach ($previous as $ps) {
7813
-                    $appended[] = $ps;
7814
-                }
7815
-            }
7816
-
7817
-            $lastSelectors = $appended;
7818
-        }
7819
-
7820
-        return $lastSelectors;
7821
-    }
7822
-
7823
-    protected static $libSelectorExtend = [
7824
-        ['selector', 'extendee', 'extender'],
7825
-        ['selectors', 'extendee', 'extender']
7826
-    ];
7827
-    protected function libSelectorExtend($args)
7828
-    {
7829
-        list($selectors, $extendee, $extender) = $args;
7830
-
7831
-        $selectors = $this->getSelectorArg($selectors);
7832
-        $extendee  = $this->getSelectorArg($extendee);
7833
-        $extender  = $this->getSelectorArg($extender);
7834
-
7835
-        if (! $selectors || ! $extendee || ! $extender) {
7836
-            throw $this->error('selector-extend() invalid arguments');
7837
-        }
7838
-
7839
-        $extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
7840
-
7841
-        return $this->formatOutputSelector($extended);
7842
-    }
7843
-
7844
-    protected static $libSelectorReplace = [
7845
-        ['selector', 'original', 'replacement'],
7846
-        ['selectors', 'original', 'replacement']
7847
-    ];
7848
-    protected function libSelectorReplace($args)
7849
-    {
7850
-        list($selectors, $original, $replacement) = $args;
7851
-
7852
-        $selectors   = $this->getSelectorArg($selectors);
7853
-        $original    = $this->getSelectorArg($original);
7854
-        $replacement = $this->getSelectorArg($replacement);
7855
-
7856
-        if (! $selectors || ! $original || ! $replacement) {
7857
-            throw $this->error('selector-replace() invalid arguments');
7858
-        }
7859
-
7860
-        $replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
7861
-
7862
-        return $this->formatOutputSelector($replaced);
7863
-    }
7864
-
7865
-    /**
7866
-     * Extend/replace in selectors
7867
-     * used by selector-extend and selector-replace that use the same logic
7868
-     *
7869
-     * @param array   $selectors
7870
-     * @param array   $extendee
7871
-     * @param array   $extender
7872
-     * @param boolean $replace
7873
-     *
7874
-     * @return array
7875
-     */
7876
-    protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $replace = false)
7877
-    {
7878
-        $saveExtends = $this->extends;
7879
-        $saveExtendsMap = $this->extendsMap;
7880
-
7881
-        $this->extends = [];
7882
-        $this->extendsMap = [];
7883
-
7884
-        foreach ($extendee as $es) {
7885
-            // only use the first one
7886
-            $this->pushExtends(reset($es), $extender, null);
7887
-        }
7888
-
7889
-        $extended = [];
7890
-
7891
-        foreach ($selectors as $selector) {
7892
-            if (! $replace) {
7893
-                $extended[] = $selector;
7894
-            }
7895
-
7896
-            $n = \count($extended);
7897
-
7898
-            $this->matchExtends($selector, $extended);
7899
-
7900
-            // if didnt match, keep the original selector if we are in a replace operation
7901
-            if ($replace && \count($extended) === $n) {
7902
-                $extended[] = $selector;
7903
-            }
7904
-        }
7905
-
7906
-        $this->extends = $saveExtends;
7907
-        $this->extendsMap = $saveExtendsMap;
7908
-
7909
-        return $extended;
7910
-    }
7911
-
7912
-    protected static $libSelectorNest = ['selector...'];
7913
-    protected function libSelectorNest($args)
7914
-    {
7915
-        // get the selector... list
7916
-        $args = reset($args);
7917
-        $args = $args[2];
7918
-
7919
-        if (\count($args) < 1) {
7920
-            throw $this->error('selector-nest() needs at least 1 argument');
7921
-        }
7922
-
7923
-        $selectorsMap = array_map([$this, 'getSelectorArg'], $args);
7924
-        $envs = [];
7925
-
7926
-        foreach ($selectorsMap as $selectors) {
7927
-            $env = new Environment();
7928
-            $env->selectors = $selectors;
7929
-
7930
-            $envs[] = $env;
7931
-        }
7932
-
7933
-        $envs            = array_reverse($envs);
7934
-        $env             = $this->extractEnv($envs);
7935
-        $outputSelectors = $this->multiplySelectors($env);
7936
-
7937
-        return $this->formatOutputSelector($outputSelectors);
7938
-    }
7939
-
7940
-    protected static $libSelectorParse = [
7941
-        ['selector'],
7942
-        ['selectors']
7943
-    ];
7944
-    protected function libSelectorParse($args)
7945
-    {
7946
-        $selectors = reset($args);
7947
-        $selectors = $this->getSelectorArg($selectors);
7948
-
7949
-        return $this->formatOutputSelector($selectors);
7950
-    }
7951
-
7952
-    protected static $libSelectorUnify = ['selectors1', 'selectors2'];
7953
-    protected function libSelectorUnify($args)
7954
-    {
7955
-        list($selectors1, $selectors2) = $args;
7956
-
7957
-        $selectors1 = $this->getSelectorArg($selectors1);
7958
-        $selectors2 = $this->getSelectorArg($selectors2);
7959
-
7960
-        if (! $selectors1 || ! $selectors2) {
7961
-            throw $this->error('selector-unify() invalid arguments');
7962
-        }
7963
-
7964
-        // only consider the first compound of each
7965
-        $compound1 = reset($selectors1);
7966
-        $compound2 = reset($selectors2);
7967
-
7968
-        // unify them and that's it
7969
-        $unified = $this->unifyCompoundSelectors($compound1, $compound2);
7970
-
7971
-        return $this->formatOutputSelector($unified);
7972
-    }
7973
-
7974
-    /**
7975
-     * The selector-unify magic as its best
7976
-     * (at least works as expected on test cases)
7977
-     *
7978
-     * @param array $compound1
7979
-     * @param array $compound2
7980
-     *
7981
-     * @return array|mixed
7982
-     */
7983
-    protected function unifyCompoundSelectors($compound1, $compound2)
7984
-    {
7985
-        if (! \count($compound1)) {
7986
-            return $compound2;
7987
-        }
7479
+		$name = $this->normalizeName($name);
7480
+
7481
+		if (isset($this->userFunctions[$name])) {
7482
+			return true;
7483
+		}
7484
+
7485
+		// built-in functions
7486
+		$f = $this->getBuiltinFunction($name);
7487
+
7488
+		return $this->toBool(\is_callable($f));
7489
+	}
7490
+
7491
+	protected static $libGlobalVariableExists = ['name'];
7492
+	protected function libGlobalVariableExists($args)
7493
+	{
7494
+		$string = $this->coerceString($args[0]);
7495
+		$name = $this->compileStringContent($string);
7496
+
7497
+		return $this->has($name, $this->rootEnv);
7498
+	}
7499
+
7500
+	protected static $libMixinExists = ['name'];
7501
+	protected function libMixinExists($args)
7502
+	{
7503
+		$string = $this->coerceString($args[0]);
7504
+		$name = $this->compileStringContent($string);
7505
+
7506
+		return $this->has(static::$namespaces['mixin'] . $name);
7507
+	}
7508
+
7509
+	protected static $libVariableExists = ['name'];
7510
+	protected function libVariableExists($args)
7511
+	{
7512
+		$string = $this->coerceString($args[0]);
7513
+		$name = $this->compileStringContent($string);
7514
+
7515
+		return $this->has($name);
7516
+	}
7517
+
7518
+	/**
7519
+	 * Workaround IE7's content counter bug.
7520
+	 *
7521
+	 * @param array $args
7522
+	 *
7523
+	 * @return array
7524
+	 */
7525
+	protected function libCounter($args)
7526
+	{
7527
+		$list = array_map([$this, 'compileValue'], $args);
7528
+
7529
+		return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
7530
+	}
7531
+
7532
+	protected static $libRandom = ['limit:1'];
7533
+	protected function libRandom($args)
7534
+	{
7535
+		if (isset($args[0])) {
7536
+			$n = $this->assertNumber($args[0]);
7537
+
7538
+			if ($n < 1) {
7539
+				throw $this->error("\$limit must be greater than or equal to 1");
7540
+			}
7541
+
7542
+			if ($n - \intval($n) > 0) {
7543
+				throw $this->error("Expected \$limit to be an integer but got $n for `random`");
7544
+			}
7545
+
7546
+			return new Node\Number(mt_rand(1, \intval($n)), '');
7547
+		}
7548
+
7549
+		return new Node\Number(mt_rand(1, mt_getrandmax()), '');
7550
+	}
7551
+
7552
+	protected function libUniqueId()
7553
+	{
7554
+		static $id;
7555
+
7556
+		if (! isset($id)) {
7557
+			$id = PHP_INT_SIZE === 4
7558
+				? mt_rand(0, pow(36, 5)) . str_pad(mt_rand(0, pow(36, 5)) % 10000000, 7, '0', STR_PAD_LEFT)
7559
+				: mt_rand(0, pow(36, 8));
7560
+		}
7561
+
7562
+		$id += mt_rand(0, 10) + 1;
7563
+
7564
+		return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
7565
+	}
7566
+
7567
+	protected function inspectFormatValue($value, $force_enclosing_display = false)
7568
+	{
7569
+		if ($value === static::$null) {
7570
+			$value = [Type::T_KEYWORD, 'null'];
7571
+		}
7572
+
7573
+		$stringValue = [$value];
7574
+
7575
+		if ($value[0] === Type::T_LIST) {
7576
+			if (end($value[2]) === static::$null) {
7577
+				array_pop($value[2]);
7578
+				$value[2][] = [Type::T_STRING, '', ['']];
7579
+				$force_enclosing_display = true;
7580
+			}
7581
+
7582
+			if (
7583
+				! empty($value['enclosing']) &&
7584
+				($force_enclosing_display ||
7585
+					($value['enclosing'] === 'bracket') ||
7586
+					! \count($value[2]))
7587
+			) {
7588
+				$value['enclosing'] = 'forced_' . $value['enclosing'];
7589
+				$force_enclosing_display = true;
7590
+			}
7591
+
7592
+			foreach ($value[2] as $k => $listelement) {
7593
+				$value[2][$k] = $this->inspectFormatValue($listelement, $force_enclosing_display);
7594
+			}
7595
+
7596
+			$stringValue = [$value];
7597
+		}
7598
+
7599
+		return [Type::T_STRING, '', $stringValue];
7600
+	}
7601
+
7602
+	protected static $libInspect = ['value'];
7603
+	protected function libInspect($args)
7604
+	{
7605
+		$value = $args[0];
7606
+
7607
+		return $this->inspectFormatValue($value);
7608
+	}
7609
+
7610
+	/**
7611
+	 * Preprocess selector args
7612
+	 *
7613
+	 * @param array $arg
7614
+	 *
7615
+	 * @return array|boolean
7616
+	 */
7617
+	protected function getSelectorArg($arg)
7618
+	{
7619
+		static $parser = null;
7620
+
7621
+		if (\is_null($parser)) {
7622
+			$parser = $this->parserFactory(__METHOD__);
7623
+		}
7624
+
7625
+		$arg = $this->libUnquote([$arg]);
7626
+		$arg = $this->compileValue($arg);
7627
+
7628
+		$parsedSelector = [];
7629
+
7630
+		if ($parser->parseSelector($arg, $parsedSelector)) {
7631
+			$selector = $this->evalSelectors($parsedSelector);
7632
+			$gluedSelector = $this->glueFunctionSelectors($selector);
7633
+
7634
+			return $gluedSelector;
7635
+		}
7636
+
7637
+		return false;
7638
+	}
7639
+
7640
+	/**
7641
+	 * Postprocess selector to output in right format
7642
+	 *
7643
+	 * @param array $selectors
7644
+	 *
7645
+	 * @return string
7646
+	 */
7647
+	protected function formatOutputSelector($selectors)
7648
+	{
7649
+		$selectors = $this->collapseSelectors($selectors, true);
7650
+
7651
+		return $selectors;
7652
+	}
7653
+
7654
+	protected static $libIsSuperselector = ['super', 'sub'];
7655
+	protected function libIsSuperselector($args)
7656
+	{
7657
+		list($super, $sub) = $args;
7658
+
7659
+		$super = $this->getSelectorArg($super);
7660
+		$sub = $this->getSelectorArg($sub);
7661
+
7662
+		return $this->isSuperSelector($super, $sub);
7663
+	}
7664
+
7665
+	/**
7666
+	 * Test a $super selector again $sub
7667
+	 *
7668
+	 * @param array $super
7669
+	 * @param array $sub
7670
+	 *
7671
+	 * @return boolean
7672
+	 */
7673
+	protected function isSuperSelector($super, $sub)
7674
+	{
7675
+		// one and only one selector for each arg
7676
+		if (! $super || \count($super) !== 1) {
7677
+			throw $this->error('Invalid super selector for isSuperSelector()');
7678
+		}
7679
+
7680
+		if (! $sub || \count($sub) !== 1) {
7681
+			throw $this->error('Invalid sub selector for isSuperSelector()');
7682
+		}
7683
+
7684
+		$super = reset($super);
7685
+		$sub = reset($sub);
7686
+
7687
+		$i = 0;
7688
+		$nextMustMatch = false;
7689
+
7690
+		foreach ($super as $node) {
7691
+			$compound = '';
7692
+
7693
+			array_walk_recursive(
7694
+				$node,
7695
+				function ($value, $key) use (&$compound) {
7696
+					$compound .= $value;
7697
+				}
7698
+			);
7699
+
7700
+			if ($this->isImmediateRelationshipCombinator($compound)) {
7701
+				if ($node !== $sub[$i]) {
7702
+					return false;
7703
+				}
7704
+
7705
+				$nextMustMatch = true;
7706
+				$i++;
7707
+			} else {
7708
+				while ($i < \count($sub) && ! $this->isSuperPart($node, $sub[$i])) {
7709
+					if ($nextMustMatch) {
7710
+						return false;
7711
+					}
7712
+
7713
+					$i++;
7714
+				}
7715
+
7716
+				if ($i >= \count($sub)) {
7717
+					return false;
7718
+				}
7719
+
7720
+				$nextMustMatch = false;
7721
+				$i++;
7722
+			}
7723
+		}
7724
+
7725
+		return true;
7726
+	}
7727
+
7728
+	/**
7729
+	 * Test a part of super selector again a part of sub selector
7730
+	 *
7731
+	 * @param array $superParts
7732
+	 * @param array $subParts
7733
+	 *
7734
+	 * @return boolean
7735
+	 */
7736
+	protected function isSuperPart($superParts, $subParts)
7737
+	{
7738
+		$i = 0;
7739
+
7740
+		foreach ($superParts as $superPart) {
7741
+			while ($i < \count($subParts) && $subParts[$i] !== $superPart) {
7742
+				$i++;
7743
+			}
7744
+
7745
+			if ($i >= \count($subParts)) {
7746
+				return false;
7747
+			}
7748
+
7749
+			$i++;
7750
+		}
7751
+
7752
+		return true;
7753
+	}
7754
+
7755
+	protected static $libSelectorAppend = ['selector...'];
7756
+	protected function libSelectorAppend($args)
7757
+	{
7758
+		// get the selector... list
7759
+		$args = reset($args);
7760
+		$args = $args[2];
7761
+
7762
+		if (\count($args) < 1) {
7763
+			throw $this->error('selector-append() needs at least 1 argument');
7764
+		}
7765
+
7766
+		$selectors = array_map([$this, 'getSelectorArg'], $args);
7767
+
7768
+		return $this->formatOutputSelector($this->selectorAppend($selectors));
7769
+	}
7770
+
7771
+	/**
7772
+	 * Append parts of the last selector in the list to the previous, recursively
7773
+	 *
7774
+	 * @param array $selectors
7775
+	 *
7776
+	 * @return array
7777
+	 *
7778
+	 * @throws \ScssPhp\ScssPhp\Exception\CompilerException
7779
+	 */
7780
+	protected function selectorAppend($selectors)
7781
+	{
7782
+		$lastSelectors = array_pop($selectors);
7783
+
7784
+		if (! $lastSelectors) {
7785
+			throw $this->error('Invalid selector list in selector-append()');
7786
+		}
7787
+
7788
+		while (\count($selectors)) {
7789
+			$previousSelectors = array_pop($selectors);
7790
+
7791
+			if (! $previousSelectors) {
7792
+				throw $this->error('Invalid selector list in selector-append()');
7793
+			}
7794
+
7795
+			// do the trick, happening $lastSelector to $previousSelector
7796
+			$appended = [];
7797
+
7798
+			foreach ($lastSelectors as $lastSelector) {
7799
+				$previous = $previousSelectors;
7800
+
7801
+				foreach ($lastSelector as $lastSelectorParts) {
7802
+					foreach ($lastSelectorParts as $lastSelectorPart) {
7803
+						foreach ($previous as $i => $previousSelector) {
7804
+							foreach ($previousSelector as $j => $previousSelectorParts) {
7805
+								$previous[$i][$j][] = $lastSelectorPart;
7806
+							}
7807
+						}
7808
+					}
7809
+				}
7810
+
7811
+				foreach ($previous as $ps) {
7812
+					$appended[] = $ps;
7813
+				}
7814
+			}
7815
+
7816
+			$lastSelectors = $appended;
7817
+		}
7818
+
7819
+		return $lastSelectors;
7820
+	}
7821
+
7822
+	protected static $libSelectorExtend = [
7823
+		['selector', 'extendee', 'extender'],
7824
+		['selectors', 'extendee', 'extender']
7825
+	];
7826
+	protected function libSelectorExtend($args)
7827
+	{
7828
+		list($selectors, $extendee, $extender) = $args;
7829
+
7830
+		$selectors = $this->getSelectorArg($selectors);
7831
+		$extendee  = $this->getSelectorArg($extendee);
7832
+		$extender  = $this->getSelectorArg($extender);
7833
+
7834
+		if (! $selectors || ! $extendee || ! $extender) {
7835
+			throw $this->error('selector-extend() invalid arguments');
7836
+		}
7837
+
7838
+		$extended = $this->extendOrReplaceSelectors($selectors, $extendee, $extender);
7839
+
7840
+		return $this->formatOutputSelector($extended);
7841
+	}
7842
+
7843
+	protected static $libSelectorReplace = [
7844
+		['selector', 'original', 'replacement'],
7845
+		['selectors', 'original', 'replacement']
7846
+	];
7847
+	protected function libSelectorReplace($args)
7848
+	{
7849
+		list($selectors, $original, $replacement) = $args;
7850
+
7851
+		$selectors   = $this->getSelectorArg($selectors);
7852
+		$original    = $this->getSelectorArg($original);
7853
+		$replacement = $this->getSelectorArg($replacement);
7854
+
7855
+		if (! $selectors || ! $original || ! $replacement) {
7856
+			throw $this->error('selector-replace() invalid arguments');
7857
+		}
7858
+
7859
+		$replaced = $this->extendOrReplaceSelectors($selectors, $original, $replacement, true);
7860
+
7861
+		return $this->formatOutputSelector($replaced);
7862
+	}
7863
+
7864
+	/**
7865
+	 * Extend/replace in selectors
7866
+	 * used by selector-extend and selector-replace that use the same logic
7867
+	 *
7868
+	 * @param array   $selectors
7869
+	 * @param array   $extendee
7870
+	 * @param array   $extender
7871
+	 * @param boolean $replace
7872
+	 *
7873
+	 * @return array
7874
+	 */
7875
+	protected function extendOrReplaceSelectors($selectors, $extendee, $extender, $replace = false)
7876
+	{
7877
+		$saveExtends = $this->extends;
7878
+		$saveExtendsMap = $this->extendsMap;
7879
+
7880
+		$this->extends = [];
7881
+		$this->extendsMap = [];
7882
+
7883
+		foreach ($extendee as $es) {
7884
+			// only use the first one
7885
+			$this->pushExtends(reset($es), $extender, null);
7886
+		}
7887
+
7888
+		$extended = [];
7889
+
7890
+		foreach ($selectors as $selector) {
7891
+			if (! $replace) {
7892
+				$extended[] = $selector;
7893
+			}
7894
+
7895
+			$n = \count($extended);
7896
+
7897
+			$this->matchExtends($selector, $extended);
7898
+
7899
+			// if didnt match, keep the original selector if we are in a replace operation
7900
+			if ($replace && \count($extended) === $n) {
7901
+				$extended[] = $selector;
7902
+			}
7903
+		}
7904
+
7905
+		$this->extends = $saveExtends;
7906
+		$this->extendsMap = $saveExtendsMap;
7907
+
7908
+		return $extended;
7909
+	}
7910
+
7911
+	protected static $libSelectorNest = ['selector...'];
7912
+	protected function libSelectorNest($args)
7913
+	{
7914
+		// get the selector... list
7915
+		$args = reset($args);
7916
+		$args = $args[2];
7917
+
7918
+		if (\count($args) < 1) {
7919
+			throw $this->error('selector-nest() needs at least 1 argument');
7920
+		}
7921
+
7922
+		$selectorsMap = array_map([$this, 'getSelectorArg'], $args);
7923
+		$envs = [];
7924
+
7925
+		foreach ($selectorsMap as $selectors) {
7926
+			$env = new Environment();
7927
+			$env->selectors = $selectors;
7928
+
7929
+			$envs[] = $env;
7930
+		}
7931
+
7932
+		$envs            = array_reverse($envs);
7933
+		$env             = $this->extractEnv($envs);
7934
+		$outputSelectors = $this->multiplySelectors($env);
7935
+
7936
+		return $this->formatOutputSelector($outputSelectors);
7937
+	}
7938
+
7939
+	protected static $libSelectorParse = [
7940
+		['selector'],
7941
+		['selectors']
7942
+	];
7943
+	protected function libSelectorParse($args)
7944
+	{
7945
+		$selectors = reset($args);
7946
+		$selectors = $this->getSelectorArg($selectors);
7947
+
7948
+		return $this->formatOutputSelector($selectors);
7949
+	}
7950
+
7951
+	protected static $libSelectorUnify = ['selectors1', 'selectors2'];
7952
+	protected function libSelectorUnify($args)
7953
+	{
7954
+		list($selectors1, $selectors2) = $args;
7955
+
7956
+		$selectors1 = $this->getSelectorArg($selectors1);
7957
+		$selectors2 = $this->getSelectorArg($selectors2);
7958
+
7959
+		if (! $selectors1 || ! $selectors2) {
7960
+			throw $this->error('selector-unify() invalid arguments');
7961
+		}
7962
+
7963
+		// only consider the first compound of each
7964
+		$compound1 = reset($selectors1);
7965
+		$compound2 = reset($selectors2);
7966
+
7967
+		// unify them and that's it
7968
+		$unified = $this->unifyCompoundSelectors($compound1, $compound2);
7969
+
7970
+		return $this->formatOutputSelector($unified);
7971
+	}
7972
+
7973
+	/**
7974
+	 * The selector-unify magic as its best
7975
+	 * (at least works as expected on test cases)
7976
+	 *
7977
+	 * @param array $compound1
7978
+	 * @param array $compound2
7979
+	 *
7980
+	 * @return array|mixed
7981
+	 */
7982
+	protected function unifyCompoundSelectors($compound1, $compound2)
7983
+	{
7984
+		if (! \count($compound1)) {
7985
+			return $compound2;
7986
+		}
7988 7987
 
7989
-        if (! \count($compound2)) {
7990
-            return $compound1;
7991
-        }
7992
-
7993
-        // check that last part are compatible
7994
-        $lastPart1 = array_pop($compound1);
7995
-        $lastPart2 = array_pop($compound2);
7996
-        $last      = $this->mergeParts($lastPart1, $lastPart2);
7997
-
7998
-        if (! $last) {
7999
-            return [[]];
8000
-        }
8001
-
8002
-        $unifiedCompound = [$last];
8003
-        $unifiedSelectors = [$unifiedCompound];
8004
-
8005
-        // do the rest
8006
-        while (\count($compound1) || \count($compound2)) {
8007
-            $part1 = end($compound1);
8008
-            $part2 = end($compound2);
8009
-
8010
-            if ($part1 && ($match2 = $this->matchPartInCompound($part1, $compound2))) {
8011
-                list($compound2, $part2, $after2) = $match2;
8012
-
8013
-                if ($after2) {
8014
-                    $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after2);
8015
-                }
8016
-
8017
-                $c = $this->mergeParts($part1, $part2);
8018
-                $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]);
8019
-
8020
-                $part1 = $part2 = null;
8021
-
8022
-                array_pop($compound1);
8023
-            }
8024
-
8025
-            if ($part2 && ($match1 = $this->matchPartInCompound($part2, $compound1))) {
8026
-                list($compound1, $part1, $after1) = $match1;
8027
-
8028
-                if ($after1) {
8029
-                    $unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after1);
8030
-                }
8031
-
8032
-                $c = $this->mergeParts($part2, $part1);
8033
-                $unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]);
8034
-
8035
-                $part1 = $part2 = null;
8036
-
8037
-                array_pop($compound2);
8038
-            }
8039
-
8040
-            $new = [];
8041
-
8042
-            if ($part1 && $part2) {
8043
-                array_pop($compound1);
8044
-                array_pop($compound2);
8045
-
8046
-                $s   = $this->prependSelectors($unifiedSelectors, [$part2]);
8047
-                $new = array_merge($new, $this->prependSelectors($s, [$part1]));
8048
-                $s   = $this->prependSelectors($unifiedSelectors, [$part1]);
8049
-                $new = array_merge($new, $this->prependSelectors($s, [$part2]));
8050
-            } elseif ($part1) {
8051
-                array_pop($compound1);
8052
-
8053
-                $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part1]));
8054
-            } elseif ($part2) {
8055
-                array_pop($compound2);
8056
-
8057
-                $new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part2]));
8058
-            }
8059
-
8060
-            if ($new) {
8061
-                $unifiedSelectors = $new;
8062
-            }
8063
-        }
8064
-
8065
-        return $unifiedSelectors;
8066
-    }
8067
-
8068
-    /**
8069
-     * Prepend each selector from $selectors with $parts
8070
-     *
8071
-     * @param array $selectors
8072
-     * @param array $parts
8073
-     *
8074
-     * @return array
8075
-     */
8076
-    protected function prependSelectors($selectors, $parts)
8077
-    {
8078
-        $new = [];
8079
-
8080
-        foreach ($selectors as $compoundSelector) {
8081
-            array_unshift($compoundSelector, $parts);
8082
-
8083
-            $new[] = $compoundSelector;
8084
-        }
8085
-
8086
-        return $new;
8087
-    }
8088
-
8089
-    /**
8090
-     * Try to find a matching part in a compound:
8091
-     * - with same html tag name
8092
-     * - with some class or id or something in common
8093
-     *
8094
-     * @param array $part
8095
-     * @param array $compound
8096
-     *
8097
-     * @return array|boolean
8098
-     */
8099
-    protected function matchPartInCompound($part, $compound)
8100
-    {
8101
-        $partTag = $this->findTagName($part);
8102
-        $before  = $compound;
8103
-        $after   = [];
8104
-
8105
-        // try to find a match by tag name first
8106
-        while (\count($before)) {
8107
-            $p = array_pop($before);
8108
-
8109
-            if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) {
8110
-                return [$before, $p, $after];
8111
-            }
8112
-
8113
-            $after[] = $p;
8114
-        }
8115
-
8116
-        // try again matching a non empty intersection and a compatible tagname
8117
-        $before = $compound;
8118
-        $after = [];
8119
-
8120
-        while (\count($before)) {
8121
-            $p = array_pop($before);
8122
-
8123
-            if ($this->checkCompatibleTags($partTag, $this->findTagName($p))) {
8124
-                if (\count(array_intersect($part, $p))) {
8125
-                    return [$before, $p, $after];
8126
-                }
8127
-            }
8128
-
8129
-            $after[] = $p;
8130
-        }
8131
-
8132
-        return false;
8133
-    }
8134
-
8135
-    /**
8136
-     * Merge two part list taking care that
8137
-     * - the html tag is coming first - if any
8138
-     * - the :something are coming last
8139
-     *
8140
-     * @param array $parts1
8141
-     * @param array $parts2
8142
-     *
8143
-     * @return array
8144
-     */
8145
-    protected function mergeParts($parts1, $parts2)
8146
-    {
8147
-        $tag1 = $this->findTagName($parts1);
8148
-        $tag2 = $this->findTagName($parts2);
8149
-        $tag  = $this->checkCompatibleTags($tag1, $tag2);
8150
-
8151
-        // not compatible tags
8152
-        if ($tag === false) {
8153
-            return [];
8154
-        }
8155
-
8156
-        if ($tag) {
8157
-            if ($tag1) {
8158
-                $parts1 = array_diff($parts1, [$tag1]);
8159
-            }
8160
-
8161
-            if ($tag2) {
8162
-                $parts2 = array_diff($parts2, [$tag2]);
8163
-            }
8164
-        }
8165
-
8166
-        $mergedParts = array_merge($parts1, $parts2);
8167
-        $mergedOrderedParts = [];
8168
-
8169
-        foreach ($mergedParts as $part) {
8170
-            if (strpos($part, ':') === 0) {
8171
-                $mergedOrderedParts[] = $part;
8172
-            }
8173
-        }
8174
-
8175
-        $mergedParts = array_diff($mergedParts, $mergedOrderedParts);
8176
-        $mergedParts = array_merge($mergedParts, $mergedOrderedParts);
8177
-
8178
-        if ($tag) {
8179
-            array_unshift($mergedParts, $tag);
8180
-        }
8181
-
8182
-        return $mergedParts;
8183
-    }
8184
-
8185
-    /**
8186
-     * Check the compatibility between two tag names:
8187
-     * if both are defined they should be identical or one has to be '*'
8188
-     *
8189
-     * @param string $tag1
8190
-     * @param string $tag2
8191
-     *
8192
-     * @return array|boolean
8193
-     */
8194
-    protected function checkCompatibleTags($tag1, $tag2)
8195
-    {
8196
-        $tags = [$tag1, $tag2];
8197
-        $tags = array_unique($tags);
8198
-        $tags = array_filter($tags);
8199
-
8200
-        if (\count($tags) > 1) {
8201
-            $tags = array_diff($tags, ['*']);
8202
-        }
8203
-
8204
-        // not compatible nodes
8205
-        if (\count($tags) > 1) {
8206
-            return false;
8207
-        }
8208
-
8209
-        return $tags;
8210
-    }
8211
-
8212
-    /**
8213
-     * Find the html tag name in a selector parts list
8214
-     *
8215
-     * @param array $parts
8216
-     *
8217
-     * @return mixed|string
8218
-     */
8219
-    protected function findTagName($parts)
8220
-    {
8221
-        foreach ($parts as $part) {
8222
-            if (! preg_match('/^[\[.:#%_-]/', $part)) {
8223
-                return $part;
8224
-            }
8225
-        }
8226
-
8227
-        return '';
8228
-    }
8229
-
8230
-    protected static $libSimpleSelectors = ['selector'];
8231
-    protected function libSimpleSelectors($args)
8232
-    {
8233
-        $selector = reset($args);
8234
-        $selector = $this->getSelectorArg($selector);
8235
-
8236
-        // remove selectors list layer, keeping the first one
8237
-        $selector = reset($selector);
8238
-
8239
-        // remove parts list layer, keeping the first part
8240
-        $part = reset($selector);
8241
-
8242
-        $listParts = [];
8243
-
8244
-        foreach ($part as $p) {
8245
-            $listParts[] = [Type::T_STRING, '', [$p]];
8246
-        }
8247
-
8248
-        return [Type::T_LIST, ',', $listParts];
8249
-    }
8250
-
8251
-    protected static $libScssphpGlob = ['pattern'];
8252
-    protected function libScssphpGlob($args)
8253
-    {
8254
-        $string = $this->coerceString($args[0]);
8255
-        $pattern = $this->compileStringContent($string);
8256
-        $matches = glob($pattern);
8257
-        $listParts = [];
8258
-
8259
-        foreach ($matches as $match) {
8260
-            if (! is_file($match)) {
8261
-                continue;
8262
-            }
8263
-
8264
-            $listParts[] = [Type::T_STRING, '"', [$match]];
8265
-        }
8266
-
8267
-        return [Type::T_LIST, ',', $listParts];
8268
-    }
7988
+		if (! \count($compound2)) {
7989
+			return $compound1;
7990
+		}
7991
+
7992
+		// check that last part are compatible
7993
+		$lastPart1 = array_pop($compound1);
7994
+		$lastPart2 = array_pop($compound2);
7995
+		$last      = $this->mergeParts($lastPart1, $lastPart2);
7996
+
7997
+		if (! $last) {
7998
+			return [[]];
7999
+		}
8000
+
8001
+		$unifiedCompound = [$last];
8002
+		$unifiedSelectors = [$unifiedCompound];
8003
+
8004
+		// do the rest
8005
+		while (\count($compound1) || \count($compound2)) {
8006
+			$part1 = end($compound1);
8007
+			$part2 = end($compound2);
8008
+
8009
+			if ($part1 && ($match2 = $this->matchPartInCompound($part1, $compound2))) {
8010
+				list($compound2, $part2, $after2) = $match2;
8011
+
8012
+				if ($after2) {
8013
+					$unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after2);
8014
+				}
8015
+
8016
+				$c = $this->mergeParts($part1, $part2);
8017
+				$unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]);
8018
+
8019
+				$part1 = $part2 = null;
8020
+
8021
+				array_pop($compound1);
8022
+			}
8023
+
8024
+			if ($part2 && ($match1 = $this->matchPartInCompound($part2, $compound1))) {
8025
+				list($compound1, $part1, $after1) = $match1;
8026
+
8027
+				if ($after1) {
8028
+					$unifiedSelectors = $this->prependSelectors($unifiedSelectors, $after1);
8029
+				}
8030
+
8031
+				$c = $this->mergeParts($part2, $part1);
8032
+				$unifiedSelectors = $this->prependSelectors($unifiedSelectors, [$c]);
8033
+
8034
+				$part1 = $part2 = null;
8035
+
8036
+				array_pop($compound2);
8037
+			}
8038
+
8039
+			$new = [];
8040
+
8041
+			if ($part1 && $part2) {
8042
+				array_pop($compound1);
8043
+				array_pop($compound2);
8044
+
8045
+				$s   = $this->prependSelectors($unifiedSelectors, [$part2]);
8046
+				$new = array_merge($new, $this->prependSelectors($s, [$part1]));
8047
+				$s   = $this->prependSelectors($unifiedSelectors, [$part1]);
8048
+				$new = array_merge($new, $this->prependSelectors($s, [$part2]));
8049
+			} elseif ($part1) {
8050
+				array_pop($compound1);
8051
+
8052
+				$new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part1]));
8053
+			} elseif ($part2) {
8054
+				array_pop($compound2);
8055
+
8056
+				$new = array_merge($new, $this->prependSelectors($unifiedSelectors, [$part2]));
8057
+			}
8058
+
8059
+			if ($new) {
8060
+				$unifiedSelectors = $new;
8061
+			}
8062
+		}
8063
+
8064
+		return $unifiedSelectors;
8065
+	}
8066
+
8067
+	/**
8068
+	 * Prepend each selector from $selectors with $parts
8069
+	 *
8070
+	 * @param array $selectors
8071
+	 * @param array $parts
8072
+	 *
8073
+	 * @return array
8074
+	 */
8075
+	protected function prependSelectors($selectors, $parts)
8076
+	{
8077
+		$new = [];
8078
+
8079
+		foreach ($selectors as $compoundSelector) {
8080
+			array_unshift($compoundSelector, $parts);
8081
+
8082
+			$new[] = $compoundSelector;
8083
+		}
8084
+
8085
+		return $new;
8086
+	}
8087
+
8088
+	/**
8089
+	 * Try to find a matching part in a compound:
8090
+	 * - with same html tag name
8091
+	 * - with some class or id or something in common
8092
+	 *
8093
+	 * @param array $part
8094
+	 * @param array $compound
8095
+	 *
8096
+	 * @return array|boolean
8097
+	 */
8098
+	protected function matchPartInCompound($part, $compound)
8099
+	{
8100
+		$partTag = $this->findTagName($part);
8101
+		$before  = $compound;
8102
+		$after   = [];
8103
+
8104
+		// try to find a match by tag name first
8105
+		while (\count($before)) {
8106
+			$p = array_pop($before);
8107
+
8108
+			if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) {
8109
+				return [$before, $p, $after];
8110
+			}
8111
+
8112
+			$after[] = $p;
8113
+		}
8114
+
8115
+		// try again matching a non empty intersection and a compatible tagname
8116
+		$before = $compound;
8117
+		$after = [];
8118
+
8119
+		while (\count($before)) {
8120
+			$p = array_pop($before);
8121
+
8122
+			if ($this->checkCompatibleTags($partTag, $this->findTagName($p))) {
8123
+				if (\count(array_intersect($part, $p))) {
8124
+					return [$before, $p, $after];
8125
+				}
8126
+			}
8127
+
8128
+			$after[] = $p;
8129
+		}
8130
+
8131
+		return false;
8132
+	}
8133
+
8134
+	/**
8135
+	 * Merge two part list taking care that
8136
+	 * - the html tag is coming first - if any
8137
+	 * - the :something are coming last
8138
+	 *
8139
+	 * @param array $parts1
8140
+	 * @param array $parts2
8141
+	 *
8142
+	 * @return array
8143
+	 */
8144
+	protected function mergeParts($parts1, $parts2)
8145
+	{
8146
+		$tag1 = $this->findTagName($parts1);
8147
+		$tag2 = $this->findTagName($parts2);
8148
+		$tag  = $this->checkCompatibleTags($tag1, $tag2);
8149
+
8150
+		// not compatible tags
8151
+		if ($tag === false) {
8152
+			return [];
8153
+		}
8154
+
8155
+		if ($tag) {
8156
+			if ($tag1) {
8157
+				$parts1 = array_diff($parts1, [$tag1]);
8158
+			}
8159
+
8160
+			if ($tag2) {
8161
+				$parts2 = array_diff($parts2, [$tag2]);
8162
+			}
8163
+		}
8164
+
8165
+		$mergedParts = array_merge($parts1, $parts2);
8166
+		$mergedOrderedParts = [];
8167
+
8168
+		foreach ($mergedParts as $part) {
8169
+			if (strpos($part, ':') === 0) {
8170
+				$mergedOrderedParts[] = $part;
8171
+			}
8172
+		}
8173
+
8174
+		$mergedParts = array_diff($mergedParts, $mergedOrderedParts);
8175
+		$mergedParts = array_merge($mergedParts, $mergedOrderedParts);
8176
+
8177
+		if ($tag) {
8178
+			array_unshift($mergedParts, $tag);
8179
+		}
8180
+
8181
+		return $mergedParts;
8182
+	}
8183
+
8184
+	/**
8185
+	 * Check the compatibility between two tag names:
8186
+	 * if both are defined they should be identical or one has to be '*'
8187
+	 *
8188
+	 * @param string $tag1
8189
+	 * @param string $tag2
8190
+	 *
8191
+	 * @return array|boolean
8192
+	 */
8193
+	protected function checkCompatibleTags($tag1, $tag2)
8194
+	{
8195
+		$tags = [$tag1, $tag2];
8196
+		$tags = array_unique($tags);
8197
+		$tags = array_filter($tags);
8198
+
8199
+		if (\count($tags) > 1) {
8200
+			$tags = array_diff($tags, ['*']);
8201
+		}
8202
+
8203
+		// not compatible nodes
8204
+		if (\count($tags) > 1) {
8205
+			return false;
8206
+		}
8207
+
8208
+		return $tags;
8209
+	}
8210
+
8211
+	/**
8212
+	 * Find the html tag name in a selector parts list
8213
+	 *
8214
+	 * @param array $parts
8215
+	 *
8216
+	 * @return mixed|string
8217
+	 */
8218
+	protected function findTagName($parts)
8219
+	{
8220
+		foreach ($parts as $part) {
8221
+			if (! preg_match('/^[\[.:#%_-]/', $part)) {
8222
+				return $part;
8223
+			}
8224
+		}
8225
+
8226
+		return '';
8227
+	}
8228
+
8229
+	protected static $libSimpleSelectors = ['selector'];
8230
+	protected function libSimpleSelectors($args)
8231
+	{
8232
+		$selector = reset($args);
8233
+		$selector = $this->getSelectorArg($selector);
8234
+
8235
+		// remove selectors list layer, keeping the first one
8236
+		$selector = reset($selector);
8237
+
8238
+		// remove parts list layer, keeping the first part
8239
+		$part = reset($selector);
8240
+
8241
+		$listParts = [];
8242
+
8243
+		foreach ($part as $p) {
8244
+			$listParts[] = [Type::T_STRING, '', [$p]];
8245
+		}
8246
+
8247
+		return [Type::T_LIST, ',', $listParts];
8248
+	}
8249
+
8250
+	protected static $libScssphpGlob = ['pattern'];
8251
+	protected function libScssphpGlob($args)
8252
+	{
8253
+		$string = $this->coerceString($args[0]);
8254
+		$pattern = $this->compileStringContent($string);
8255
+		$matches = glob($pattern);
8256
+		$listParts = [];
8257
+
8258
+		foreach ($matches as $match) {
8259
+			if (! is_file($match)) {
8260
+				continue;
8261
+			}
8262
+
8263
+			$listParts[] = [Type::T_STRING, '"', [$match]];
8264
+		}
8265
+
8266
+		return [Type::T_LIST, ',', $listParts];
8267
+	}
8269 8268
 }
Please login to merge, or discard this patch.
Spacing   +189 added lines, -189 removed lines patch added patch discarded remove patch
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
             if (\is_array($cache) && isset($cache['dependencies']) && isset($cache['out'])) {
234 234
                 // check if any dependency file changed before accepting the cache
235 235
                 foreach ($cache['dependencies'] as $file => $mtime) {
236
-                    if (! is_file($file) || filemtime($file) !== $mtime) {
236
+                    if (!is_file($file) || filemtime($file) !== $mtime) {
237 237
                         unset($cache);
238 238
                         break;
239 239
                     }
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
 
285 285
         $out = $this->formatter->format($this->scope, $sourceMapGenerator);
286 286
 
287
-        if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
287
+        if (!empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
288 288
             $sourceMap    = $sourceMapGenerator->generateJson();
289 289
             $sourceMapUrl = null;
290 290
 
@@ -465,22 +465,22 @@  discard block
 block discarded – undo
465 465
             foreach ($block->selectors as $s) {
466 466
                 $selectors[] = $s;
467 467
 
468
-                if (! \is_array($s)) {
468
+                if (!\is_array($s)) {
469 469
                     continue;
470 470
                 }
471 471
 
472 472
                 // check extends
473
-                if (! empty($this->extendsMap)) {
473
+                if (!empty($this->extendsMap)) {
474 474
                     $this->matchExtends($s, $selectors);
475 475
 
476 476
                     // remove duplicates
477
-                    array_walk($selectors, function (&$value) {
477
+                    array_walk($selectors, function(&$value) {
478 478
                         $value = serialize($value);
479 479
                     });
480 480
 
481 481
                     $selectors = array_unique($selectors);
482 482
 
483
-                    array_walk($selectors, function (&$value) {
483
+                    array_walk($selectors, function(&$value) {
484 484
                         $value = unserialize($value);
485 485
                     });
486 486
                 }
@@ -573,7 +573,7 @@  discard block
 block discarded – undo
573 573
             // if the new part is just including a previous part don't try to extend anymore
574 574
             if (\count($part) > 1) {
575 575
                 foreach ($partsPile as $previousPart) {
576
-                    if (! \count(array_diff($previousPart, $part))) {
576
+                    if (!\count(array_diff($previousPart, $part))) {
577 577
                         continue 2;
578 578
                     }
579 579
                 }
@@ -607,14 +607,14 @@  discard block
 block discarded – undo
607 607
                         $slice = [];
608 608
 
609 609
                         foreach ($tempReplacement[$l] as $chunk) {
610
-                            if (! \in_array($chunk, $slice)) {
610
+                            if (!\in_array($chunk, $slice)) {
611 611
                                 $slice[] = $chunk;
612 612
                             }
613 613
                         }
614 614
 
615 615
                         array_unshift($replacement, $slice);
616 616
 
617
-                        if (! $this->isImmediateRelationshipCombinator(end($slice))) {
617
+                        if (!$this->isImmediateRelationshipCombinator(end($slice))) {
618 618
                             break;
619 619
                         }
620 620
                     }
@@ -647,7 +647,7 @@  discard block
 block discarded – undo
647 647
                     }
648 648
 
649 649
                     // selector sequence merging
650
-                    if (! empty($before) && \count($new) > 1) {
650
+                    if (!empty($before) && \count($new) > 1) {
651 651
                         $preSharedParts = $k > 0 ? \array_slice($before, 0, $k) : [];
652 652
                         $postSharedParts = $k > 0 ? \array_slice($before, $k) : $before;
653 653
 
@@ -714,7 +714,7 @@  discard block
 block discarded – undo
714 714
 
715 715
             if (
716 716
                 $this->isPseudoSelector($part, $matchesExtended) &&
717
-                \in_array($matchesExtended[1], [ 'slotted' ])
717
+                \in_array($matchesExtended[1], ['slotted'])
718 718
             ) {
719 719
                 $prev = end($out);
720 720
                 $prev = $this->glueFunctionSelectors($prev);
@@ -730,7 +730,7 @@  discard block
 block discarded – undo
730 730
                         $extended = explode($matchesExtended[1] . '(', $matchesExtended[0], 2);
731 731
                         $extended[1] = $matchesPrev[2] . ', ' . $extended[1];
732 732
                         $extended = implode($matchesExtended[1] . '(', $extended);
733
-                        $extended = [ [ $extended ]];
733
+                        $extended = [[$extended]];
734 734
                         array_pop($out);
735 735
                     }
736 736
                 }
@@ -754,17 +754,17 @@  discard block
 block discarded – undo
754 754
         $single = [];
755 755
 
756 756
         // simple usual cases, no need to do the whole trick
757
-        if (\in_array($rawSingle, [['>'],['+'],['~']])) {
757
+        if (\in_array($rawSingle, [['>'], ['+'], ['~']])) {
758 758
             return false;
759 759
         }
760 760
 
761 761
         foreach ($rawSingle as $part) {
762 762
             // matches Number
763
-            if (! \is_string($part)) {
763
+            if (!\is_string($part)) {
764 764
                 return false;
765 765
             }
766 766
 
767
-            if (! preg_match('/^[\[.:#%]/', $part) && \count($single)) {
767
+            if (!preg_match('/^[\[.:#%]/', $part) && \count($single)) {
768 768
                 $single[\count($single) - 1] .= $part;
769 769
             } else {
770 770
                 $single[] = $part;
@@ -791,7 +791,7 @@  discard block
 block discarded – undo
791 791
             if (
792 792
                 $initial &&
793 793
                 $this->isPseudoSelector($part, $matches) &&
794
-                ! \in_array($matches[1], [ 'not' ])
794
+                !\in_array($matches[1], ['not'])
795 795
             ) {
796 796
                 $buffer    = $matches[2];
797 797
                 $parser    = $this->parserFactory(__METHOD__);
@@ -812,7 +812,7 @@  discard block
 block discarded – undo
812 812
                             $subSelectorsExtended = implode(', ', $subSelectorsExtended);
813 813
                             $singleExtended = $single;
814 814
                             $singleExtended[$k] = str_replace('(' . $buffer . ')', "($subSelectorsExtended)", $part);
815
-                            $outOrigin[] = [ $singleExtended ];
815
+                            $outOrigin[] = [$singleExtended];
816 816
                             $found = true;
817 817
                         }
818 818
                     }
@@ -836,7 +836,7 @@  discard block
 block discarded – undo
836 836
 
837 837
             foreach ($origin as $j => $new) {
838 838
                 // prevent infinite loop when target extends itself
839
-                if ($this->isSelfExtend($single, $origin) && ! $initial) {
839
+                if ($this->isSelfExtend($single, $origin) && !$initial) {
840 840
                     return false;
841 841
                 }
842 842
 
@@ -890,7 +890,7 @@  discard block
 block discarded – undo
890 890
             $parents  = \array_slice($fragment, 0, $j);
891 891
             $slice    = end($parents);
892 892
 
893
-            if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
893
+            if (empty($slice) || !$this->isImmediateRelationshipCombinator($slice[0])) {
894 894
                 break;
895 895
             }
896 896
 
@@ -963,7 +963,7 @@  discard block
 block discarded – undo
963 963
 
964 964
         $mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
965 965
 
966
-        if (! empty($mediaQueries) && $mediaQueries) {
966
+        if (!empty($mediaQueries) && $mediaQueries) {
967 967
             $previousScope = $this->scope;
968 968
             $parentScope = $this->mediaParent($this->scope);
969 969
 
@@ -1046,8 +1046,8 @@  discard block
 block discarded – undo
1046 1046
      */
1047 1047
     protected function mediaParent(OutputBlock $scope)
1048 1048
     {
1049
-        while (! empty($scope->parent)) {
1050
-            if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
1049
+        while (!empty($scope->parent)) {
1050
+            if (!empty($scope->type) && $scope->type !== Type::T_MEDIA) {
1051 1051
                 break;
1052 1052
             }
1053 1053
 
@@ -1068,7 +1068,7 @@  discard block
 block discarded – undo
1068 1068
         if (\is_array($directive)) {
1069 1069
             $s = '@' . $directive[0];
1070 1070
 
1071
-            if (! empty($directive[1])) {
1071
+            if (!empty($directive[1])) {
1072 1072
                 $s .= ' ' . $this->compileValue($directive[1]);
1073 1073
             }
1074 1074
 
@@ -1076,7 +1076,7 @@  discard block
 block discarded – undo
1076 1076
         } else {
1077 1077
             $s = '@' . $directive->name;
1078 1078
 
1079
-            if (! empty($directive->value)) {
1079
+            if (!empty($directive->value)) {
1080 1080
                 $s .= ' ' . $this->compileValue($directive->value);
1081 1081
             }
1082 1082
 
@@ -1119,7 +1119,7 @@  discard block
 block discarded – undo
1119 1119
         $selfParent = $block->selfParent;
1120 1120
 
1121 1121
         if (
1122
-            ! $block->selfParent->selectors &&
1122
+            !$block->selfParent->selectors &&
1123 1123
             isset($block->parent) && $block->parent &&
1124 1124
             isset($block->parent->selectors) && $block->parent->selectors
1125 1125
         ) {
@@ -1166,7 +1166,7 @@  discard block
 block discarded – undo
1166 1166
         }
1167 1167
 
1168 1168
         for (;;) {
1169
-            if (! $scope) {
1169
+            if (!$scope) {
1170 1170
                 break;
1171 1171
             }
1172 1172
 
@@ -1192,7 +1192,7 @@  discard block
 block discarded – undo
1192 1192
             }
1193 1193
         }
1194 1194
 
1195
-        if (! \count($filteredScopes)) {
1195
+        if (!\count($filteredScopes)) {
1196 1196
             return $this->rootBlock;
1197 1197
         }
1198 1198
 
@@ -1225,7 +1225,7 @@  discard block
 block discarded – undo
1225 1225
      */
1226 1226
     protected function completeScope($scope, $previousScope)
1227 1227
     {
1228
-        if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) {
1228
+        if (!$scope->type && (!$scope->selectors || !\count($scope->selectors)) && \count($scope->lines)) {
1229 1229
             $scope->selectors = $this->findScopeSelectors($previousScope, $scope->depth);
1230 1230
         }
1231 1231
 
@@ -1328,7 +1328,7 @@  discard block
 block discarded – undo
1328 1328
         $filtered = [];
1329 1329
 
1330 1330
         foreach ($envs as $e) {
1331
-            if ($e->block && ! $this->isWith($e->block, $with, $without)) {
1331
+            if ($e->block && !$this->isWith($e->block, $with, $without)) {
1332 1332
                 $ec = clone $e;
1333 1333
                 $ec->block     = null;
1334 1334
                 $ec->selectors = [];
@@ -1421,8 +1421,8 @@  discard block
 block discarded – undo
1421 1421
 
1422 1422
         $envs = $this->compactEnv($env);
1423 1423
 
1424
-        $this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
1425
-            return ! isset($e->block->selectors);
1424
+        $this->env = $this->extractEnv(array_filter($envs, function(Environment $e) {
1425
+            return !isset($e->block->selectors);
1426 1426
         }));
1427 1427
 
1428 1428
         $this->scope = $this->makeOutputBlock($block->type, $selectors);
@@ -1734,7 +1734,7 @@  discard block
 block discarded – undo
1734 1734
 
1735 1735
                 array_walk_recursive(
1736 1736
                     $node,
1737
-                    function ($value, $key) use (&$compound) {
1737
+                    function($value, $key) use (&$compound) {
1738 1738
                         $compound .= $value;
1739 1739
                     }
1740 1740
                 );
@@ -1817,7 +1817,7 @@  discard block
 block discarded – undo
1817 1817
         foreach ($single as $part) {
1818 1818
             if (
1819 1819
                 empty($joined) ||
1820
-                ! \is_string($part) ||
1820
+                !\is_string($part) ||
1821 1821
                 preg_match('/[\[.:#%]/', $part)
1822 1822
             ) {
1823 1823
                 $joined[] = $part;
@@ -1843,7 +1843,7 @@  discard block
 block discarded – undo
1843 1843
      */
1844 1844
     protected function compileSelector($selector)
1845 1845
     {
1846
-        if (! \is_array($selector)) {
1846
+        if (!\is_array($selector)) {
1847 1847
             return $selector; // media and the like
1848 1848
         }
1849 1849
 
@@ -1866,7 +1866,7 @@  discard block
 block discarded – undo
1866 1866
     protected function compileSelectorPart($piece)
1867 1867
     {
1868 1868
         foreach ($piece as &$p) {
1869
-            if (! \is_array($p)) {
1869
+            if (!\is_array($p)) {
1870 1870
                 continue;
1871 1871
             }
1872 1872
 
@@ -1893,7 +1893,7 @@  discard block
 block discarded – undo
1893 1893
      */
1894 1894
     protected function hasSelectorPlaceholder($selector)
1895 1895
     {
1896
-        if (! \is_array($selector)) {
1896
+        if (!\is_array($selector)) {
1897 1897
             return false;
1898 1898
         }
1899 1899
 
@@ -2095,13 +2095,13 @@  discard block
 block discarded – undo
2095 2095
                         $newType = array_map([$this, 'compileValue'], \array_slice($q, 1));
2096 2096
 
2097 2097
                         // combining not and anything else than media type is too risky and should be avoided
2098
-                        if (! $mediaTypeOnly) {
2099
-                            if (\in_array(Type::T_NOT, $newType) || ($type && \in_array(Type::T_NOT, $type) )) {
2098
+                        if (!$mediaTypeOnly) {
2099
+                            if (\in_array(Type::T_NOT, $newType) || ($type && \in_array(Type::T_NOT, $type))) {
2100 2100
                                 if ($type) {
2101 2101
                                     array_unshift($parts, implode(' ', array_filter($type)));
2102 2102
                                 }
2103 2103
 
2104
-                                if (! empty($parts)) {
2104
+                                if (!empty($parts)) {
2105 2105
                                     if (\strlen($current)) {
2106 2106
                                         $current .= $this->formatter->tagSeparator;
2107 2107
                                     }
@@ -2164,7 +2164,7 @@  discard block
 block discarded – undo
2164 2164
                 array_unshift($parts, implode(' ', array_filter($type)));
2165 2165
             }
2166 2166
 
2167
-            if (! empty($parts)) {
2167
+            if (!empty($parts)) {
2168 2168
                 if (\strlen($current)) {
2169 2169
                     $current .= $this->formatter->tagSeparator;
2170 2170
                 }
@@ -2178,7 +2178,7 @@  discard block
 block discarded – undo
2178 2178
         }
2179 2179
 
2180 2180
         // no @media type except all, and no conflict?
2181
-        if (! $out && $default) {
2181
+        if (!$out && $default) {
2182 2182
             $out[] = $default;
2183 2183
         }
2184 2184
 
@@ -2202,7 +2202,7 @@  discard block
 block discarded – undo
2202 2202
         $part1 = end($selectors1);
2203 2203
         $part2 = end($selectors2);
2204 2204
 
2205
-        if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2205
+        if (!$this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2206 2206
             return array_merge($selectors1, $selectors2);
2207 2207
         }
2208 2208
 
@@ -2212,7 +2212,7 @@  discard block
 block discarded – undo
2212 2212
             $part1 = array_pop($selectors1);
2213 2213
             $part2 = array_pop($selectors2);
2214 2214
 
2215
-            if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2215
+            if (!$this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
2216 2216
                 if ($this->isImmediateRelationshipCombinator(reset($merged)[0])) {
2217 2217
                     array_unshift($merged, [$part1[0] . $part2[0]]);
2218 2218
                     $merged = array_merge($selectors1, $selectors2, $merged);
@@ -2224,7 +2224,7 @@  discard block
 block discarded – undo
2224 2224
             }
2225 2225
 
2226 2226
             array_unshift($merged, $part1);
2227
-        } while (! empty($selectors1) && ! empty($selectors2));
2227
+        } while (!empty($selectors1) && !empty($selectors2));
2228 2228
 
2229 2229
         return $merged;
2230 2230
     }
@@ -2306,7 +2306,7 @@  discard block
 block discarded – undo
2306 2306
             $path = $this->compileStringContent($rawPath);
2307 2307
 
2308 2308
             if (strpos($path, 'url(') !== 0 && $path = $this->findImport($path)) {
2309
-                if (! $once || ! \in_array($path, $this->importedFiles)) {
2309
+                if (!$once || !\in_array($path, $this->importedFiles)) {
2310 2310
                     $this->importFile($path, $out);
2311 2311
                     $this->importedFiles[] = $path;
2312 2312
                 }
@@ -2412,7 +2412,7 @@  discard block
 block discarded – undo
2412 2412
         $i = 0;
2413 2413
 
2414 2414
         while ($i < \count($root->children)) {
2415
-            if (! isset($root->children[$i]->type) || ! \in_array($root->children[$i]->type, $allowed)) {
2415
+            if (!isset($root->children[$i]->type) || !\in_array($root->children[$i]->type, $allowed)) {
2416 2416
                 break;
2417 2417
             }
2418 2418
 
@@ -2460,7 +2460,7 @@  discard block
 block discarded – undo
2460 2460
             if (
2461 2461
                 $lastChild->depth === $out->depth &&
2462 2462
                 \is_null($lastChild->selectors) &&
2463
-                ! \count($lastChild->children)
2463
+                !\count($lastChild->children)
2464 2464
             ) {
2465 2465
                 $outWrite = $lastChild;
2466 2466
             } else {
@@ -2494,7 +2494,7 @@  discard block
 block discarded – undo
2494 2494
             $this->sourceIndex  = $child[1]->sourceIndex;
2495 2495
             $this->sourceLine   = $child[1]->sourceLine;
2496 2496
             $this->sourceColumn = $child[1]->sourceColumn;
2497
-        } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
2497
+        } elseif (!empty($out->sourceLine) && !empty($out->sourceName)) {
2498 2498
             $this->sourceLine   = $out->sourceLine;
2499 2499
             $this->sourceIndex  = array_search($out->sourceName, $this->sourceNames);
2500 2500
             $this->sourceColumn = $out->sourceColumn;
@@ -2534,7 +2534,7 @@  discard block
 block discarded – undo
2534 2534
                 break;
2535 2535
 
2536 2536
             case Type::T_CHARSET:
2537
-                if (! $this->charsetSeen) {
2537
+                if (!$this->charsetSeen) {
2538 2538
                     $this->charsetSeen = true;
2539 2539
                     $this->appendRootDirective('@charset ' . $this->compileValue($child[1]) . ';', $out);
2540 2540
                 }
@@ -2581,7 +2581,7 @@  discard block
 block discarded – undo
2581 2581
                         (\is_null($result = $this->get($name[1], false)) ||
2582 2582
                         $result === static::$null);
2583 2583
 
2584
-                    if (! $isDefault || $shouldSet) {
2584
+                    if (!$isDefault || $shouldSet) {
2585 2585
                         $this->set($name[1], $this->reduce($value), true, null, $value);
2586 2586
                     }
2587 2587
                     break;
@@ -2597,7 +2597,7 @@  discard block
 block discarded – undo
2597 2597
                         $value = $this->get($value[1], true, null, true);
2598 2598
                     }
2599 2599
 
2600
-                    $shorthandValue=&$value;
2600
+                    $shorthandValue = &$value;
2601 2601
 
2602 2602
                     $shorthandDividerNeedsUnit = false;
2603 2603
                     $maxListElements           = null;
@@ -2613,7 +2613,7 @@  discard block
 block discarded – undo
2613 2613
                     if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
2614 2614
                         // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
2615 2615
                         // we need to handle the first list element
2616
-                        $shorthandValue=&$value[2][0];
2616
+                        $shorthandValue = &$value[2][0];
2617 2617
                     }
2618 2618
 
2619 2619
                     if ($shorthandValue[0] === Type::T_EXPRESSION && $shorthandValue[1] === '/') {
@@ -2626,7 +2626,7 @@  discard block
 block discarded – undo
2626 2626
                                 $divider = $this->reduce($divider, true);
2627 2627
                             }
2628 2628
 
2629
-                            if (\intval($divider->dimension) && ! \count($divider->units)) {
2629
+                            if (\intval($divider->dimension) && !\count($divider->units)) {
2630 2630
                                 $revert = false;
2631 2631
                             }
2632 2632
                         }
@@ -2650,7 +2650,7 @@  discard block
 block discarded – undo
2650 2650
                                                 $divider = $this->reduce($divider, true);
2651 2651
                                             }
2652 2652
 
2653
-                                            if (\intval($divider->dimension) && ! \count($divider->units)) {
2653
+                                            if (\intval($divider->dimension) && !\count($divider->units)) {
2654 2654
                                                 $revert = false;
2655 2655
                                             }
2656 2656
                                         }
@@ -2716,7 +2716,7 @@  discard block
 block discarded – undo
2716 2716
                         $result = current($result);
2717 2717
                         $selectors = $out->selectors;
2718 2718
 
2719
-                        if (! $selectors && isset($child['selfParent'])) {
2719
+                        if (!$selectors && isset($child['selfParent'])) {
2720 2720
                             $selectors = $this->multiplySelectors($this->env, $child['selfParent']);
2721 2721
                         }
2722 2722
 
@@ -2806,15 +2806,15 @@  discard block
 block discarded – undo
2806 2806
                 $start = $this->reduce($for->start, true);
2807 2807
                 $end   = $this->reduce($for->end, true);
2808 2808
 
2809
-                if (! $start instanceof Node\Number) {
2809
+                if (!$start instanceof Node\Number) {
2810 2810
                     throw $this->error('%s is not a number', $start[0]);
2811 2811
                 }
2812 2812
 
2813
-                if (! $end instanceof Node\Number) {
2813
+                if (!$end instanceof Node\Number) {
2814 2814
                     throw $this->error('%s is not a number', $end[0]);
2815 2815
                 }
2816 2816
 
2817
-                if (! ($start[2] == $end[2] || $end->unitless())) {
2817
+                if (!($start[2] == $end[2] || $end->unitless())) {
2818 2818
                     throw $this->error('Incompatible units: "%s" && "%s".', $start->unitStr(), $end->unitStr());
2819 2819
                 }
2820 2820
 
@@ -2828,7 +2828,7 @@  discard block
 block discarded – undo
2828 2828
 
2829 2829
                 for (;;) {
2830 2830
                     if (
2831
-                        (! $for->until && $start - $d == $end) ||
2831
+                        (!$for->until && $start - $d == $end) ||
2832 2832
                         ($for->until && $start == $end)
2833 2833
                     ) {
2834 2834
                         break;
@@ -2879,7 +2879,7 @@  discard block
 block discarded – undo
2879 2879
 
2880 2880
                 $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
2881 2881
 
2882
-                if (! $mixin) {
2882
+                if (!$mixin) {
2883 2883
                     throw $this->error("Undefined mixin $name");
2884 2884
                 }
2885 2885
 
@@ -2934,7 +2934,7 @@  discard block
 block discarded – undo
2934 2934
 
2935 2935
                 $this->env->marker = 'mixin';
2936 2936
 
2937
-                if (! empty($mixin->parentEnv)) {
2937
+                if (!empty($mixin->parentEnv)) {
2938 2938
                     $this->env->declarationScopeParent = $mixin->parentEnv;
2939 2939
                 } else {
2940 2940
                     throw $this->error("@mixin $name() without parentEnv");
@@ -2951,7 +2951,7 @@  discard block
 block discarded – undo
2951 2951
                 $argUsing   = $this->get(static::$namespaces['special'] . 'using', false, $env);
2952 2952
                 $argContent = $child[1];
2953 2953
 
2954
-                if (! $content) {
2954
+                if (!$content) {
2955 2955
                     break;
2956 2956
                 }
2957 2957
 
@@ -3119,9 +3119,9 @@  discard block
 block discarded – undo
3119 3119
 
3120 3120
                 // special case: looks like css shorthand
3121 3121
                 if (
3122
-                    $opName == 'div' && ! $inParens && ! $inExp && isset($right[2]) &&
3122
+                    $opName == 'div' && !$inParens && !$inExp && isset($right[2]) &&
3123 3123
                     (($right[0] !== Type::T_NUMBER && $right[2] != '') ||
3124
-                    ($right[0] === Type::T_NUMBER && ! $right->unitless()))
3124
+                    ($right[0] === Type::T_NUMBER && !$right->unitless()))
3125 3125
                 ) {
3126 3126
                     return $this->expToString($value);
3127 3127
                 }
@@ -3153,7 +3153,7 @@  discard block
 block discarded – undo
3153 3153
                     $coerceUnit = false;
3154 3154
 
3155 3155
                     if (
3156
-                        ! isset($genOp) &&
3156
+                        !isset($genOp) &&
3157 3157
                         $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
3158 3158
                     ) {
3159 3159
                         $coerceUnit = true;
@@ -3286,7 +3286,7 @@  discard block
 block discarded – undo
3286 3286
                 return $this->fncall($value[1], $value[2]);
3287 3287
 
3288 3288
             case Type::T_SELF:
3289
-                $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3289
+                $selfParent = !empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
3290 3290
                 $selfSelector = $this->multiplySelectors($this->env, $selfParent);
3291 3291
                 $selfSelector = $this->collapseSelectors($selfSelector, true);
3292 3292
 
@@ -3344,10 +3344,10 @@  discard block
 block discarded – undo
3344 3344
             // native PHP functions
3345 3345
             case 'user':
3346 3346
             case 'native':
3347
-                list(,,$name, $fn, $prototype) = $functionReference;
3347
+                list(,, $name, $fn, $prototype) = $functionReference;
3348 3348
                 $returnValue = $this->callNativeFunction($name, $fn, $prototype, $argValues);
3349 3349
 
3350
-                if (! isset($returnValue)) {
3350
+                if (!isset($returnValue)) {
3351 3351
                     return $this->fncall([Type::T_FUNCTION, $name, [Type::T_LIST, ',', []]], $argValues);
3352 3352
                 }
3353 3353
 
@@ -3471,7 +3471,7 @@  discard block
 block discarded – undo
3471 3471
                     $value[2][$key] = $this->normalizeValue($item);
3472 3472
                 }
3473 3473
 
3474
-                if (! empty($value['enclosing'])) {
3474
+                if (!empty($value['enclosing'])) {
3475 3475
                     unset($value['enclosing']);
3476 3476
                 }
3477 3477
 
@@ -3612,8 +3612,8 @@  discard block
 block discarded – undo
3612 3612
                   ($left === static::$false || $left === static::$true) &&
3613 3613
                   ($right === static::$false || $right === static::$true);
3614 3614
 
3615
-        if (! $shouldEval) {
3616
-            if (! $truthy) {
3615
+        if (!$shouldEval) {
3616
+            if (!$truthy) {
3617 3617
                 return null;
3618 3618
             }
3619 3619
         }
@@ -3640,8 +3640,8 @@  discard block
 block discarded – undo
3640 3640
                   ($left === static::$false || $left === static::$true) &&
3641 3641
                   ($right === static::$false || $right === static::$true);
3642 3642
 
3643
-        if (! $shouldEval) {
3644
-            if (! $truthy) {
3643
+        if (!$shouldEval) {
3644
+            if (!$truthy) {
3645 3645
                 return null;
3646 3646
             }
3647 3647
         }
@@ -3921,10 +3921,10 @@  discard block
 block discarded – undo
3921 3921
                 if (\count($value) === 5) {
3922 3922
                     $alpha = $this->compileRGBAValue($value[4], true);
3923 3923
 
3924
-                    if (! is_numeric($alpha) || $alpha < 1) {
3924
+                    if (!is_numeric($alpha) || $alpha < 1) {
3925 3925
                         $colorName = Colors::RGBaToColorName($r, $g, $b, $alpha);
3926 3926
 
3927
-                        if (! \is_null($colorName)) {
3927
+                        if (!\is_null($colorName)) {
3928 3928
                             return $colorName;
3929 3929
                         }
3930 3930
 
@@ -3938,13 +3938,13 @@  discard block
 block discarded – undo
3938 3938
                     }
3939 3939
                 }
3940 3940
 
3941
-                if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b)) {
3941
+                if (!is_numeric($r) || !is_numeric($g) || !is_numeric($b)) {
3942 3942
                     return 'rgb(' . $r . ', ' . $g . ', ' . $b . ')';
3943 3943
                 }
3944 3944
 
3945 3945
                 $colorName = Colors::RGBaToColorName($r, $g, $b);
3946 3946
 
3947
-                if (! \is_null($colorName)) {
3947
+                if (!\is_null($colorName)) {
3948 3948
                     return $colorName;
3949 3949
                 }
3950 3950
 
@@ -3973,8 +3973,8 @@  discard block
 block discarded – undo
3973 3973
                         $value[1] = '"';
3974 3974
                     }
3975 3975
                     $content = str_replace(
3976
-                        array('\\a', "\n", "\f" , '\\'  , "\r" , $value[1]),
3977
-                        array("\r" , ' ' , '\\f', '\\\\', '\\a', '\\' . $value[1]),
3976
+                        array('\\a', "\n", "\f", '\\', "\r", $value[1]),
3977
+                        array("\r", ' ', '\\f', '\\\\', '\\a', '\\' . $value[1]),
3978 3978
                         $content
3979 3979
                     );
3980 3980
                 }
@@ -3982,12 +3982,12 @@  discard block
 block discarded – undo
3982 3982
                 return $value[1] . $content . $value[1];
3983 3983
 
3984 3984
             case Type::T_FUNCTION:
3985
-                $args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
3985
+                $args = !empty($value[2]) ? $this->compileValue($value[2]) : '';
3986 3986
 
3987 3987
                 return "$value[1]($args)";
3988 3988
 
3989 3989
             case Type::T_FUNCTION_REFERENCE:
3990
-                $name = ! empty($value[2]) ? $value[2] : '';
3990
+                $name = !empty($value[2]) ? $value[2] : '';
3991 3991
 
3992 3992
                 return "get-function(\"$name\")";
3993 3993
 
@@ -4001,7 +4001,7 @@  discard block
 block discarded – undo
4001 4001
                 list(, $delim, $items) = $value;
4002 4002
                 $pre = $post = '';
4003 4003
 
4004
-                if (! empty($value['enclosing'])) {
4004
+                if (!empty($value['enclosing'])) {
4005 4005
                     switch ($value['enclosing']) {
4006 4006
                         case 'parent':
4007 4007
                             //$pre = '(';
@@ -4052,7 +4052,7 @@  discard block
 block discarded – undo
4052 4052
                     $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
4053 4053
                 }
4054 4054
 
4055
-                array_walk($filtered, function (&$value, $key) {
4055
+                array_walk($filtered, function(&$value, $key) {
4056 4056
                     $value = $key . ': ' . $value;
4057 4057
                 });
4058 4058
 
@@ -4065,12 +4065,12 @@  discard block
 block discarded – undo
4065 4065
 
4066 4066
                 $delim = $left[1];
4067 4067
 
4068
-                if ($delim && $delim !== ' ' && ! $whiteLeft) {
4068
+                if ($delim && $delim !== ' ' && !$whiteLeft) {
4069 4069
                     $delim .= ' ';
4070 4070
                 }
4071 4071
 
4072 4072
                 $left = \count($left[2]) > 0
4073
-                    ?  $this->compileValue($left) . $delim . $whiteLeft
4073
+                    ? $this->compileValue($left) . $delim . $whiteLeft
4074 4074
                     : '';
4075 4075
 
4076 4076
                 $delim = $right[1];
@@ -4235,7 +4235,7 @@  discard block
 block discarded – undo
4235 4235
 
4236 4236
         $selfParentSelectors = null;
4237 4237
 
4238
-        if (! \is_null($selfParent) && $selfParent->selectors) {
4238
+        if (!\is_null($selfParent) && $selfParent->selectors) {
4239 4239
             $selfParentSelectors = $this->evalSelectors($selfParent->selectors);
4240 4240
         }
4241 4241
 
@@ -4273,7 +4273,7 @@  discard block
 block discarded – undo
4273 4273
         $selectors = array_values($selectors);
4274 4274
 
4275 4275
         // case we are just starting a at-root : nothing to multiply but parentSelectors
4276
-        if (! $selectors && $selfParentSelectors) {
4276
+        if (!$selectors && $selfParentSelectors) {
4277 4277
             $selectors = $selfParentSelectors;
4278 4278
         }
4279 4279
 
@@ -4304,7 +4304,7 @@  discard block
 block discarded – undo
4304 4304
                     $stillHasSelf = true;
4305 4305
                 }
4306 4306
 
4307
-                if ($p === static::$selfSelector && ! $setSelf) {
4307
+                if ($p === static::$selfSelector && !$setSelf) {
4308 4308
                     $setSelf = true;
4309 4309
 
4310 4310
                     if (\is_null($selfParentSelectors)) {
@@ -4321,7 +4321,7 @@  discard block
 block discarded – undo
4321 4321
                             if (\is_array($pp)) {
4322 4322
                                 $flatten = [];
4323 4323
 
4324
-                                array_walk_recursive($pp, function ($a) use (&$flatten) {
4324
+                                array_walk_recursive($pp, function($a) use (&$flatten) {
4325 4325
                                     $flatten[] = $a;
4326 4326
                                 });
4327 4327
 
@@ -4353,8 +4353,8 @@  discard block
 block discarded – undo
4353 4353
     protected function multiplyMedia(Environment $env = null, $childQueries = null)
4354 4354
     {
4355 4355
         if (
4356
-            ! isset($env) ||
4357
-            ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4356
+            !isset($env) ||
4357
+            !empty($env->block->type) && $env->block->type !== Type::T_MEDIA
4358 4358
         ) {
4359 4359
             return $childQueries;
4360 4360
         }
@@ -4469,7 +4469,7 @@  discard block
 block discarded – undo
4469 4469
     protected function backPropagateEnv($store, $excludedVars = null)
4470 4470
     {
4471 4471
         foreach ($store as $key => $value) {
4472
-            if (empty($excludedVars) || ! \in_array($key, $excludedVars)) {
4472
+            if (empty($excludedVars) || !\in_array($key, $excludedVars)) {
4473 4473
                 $this->set($key, $value, true);
4474 4474
             }
4475 4475
         }
@@ -4498,7 +4498,7 @@  discard block
 block discarded – undo
4498 4498
     {
4499 4499
         $name = $this->normalizeName($name);
4500 4500
 
4501
-        if (! isset($env)) {
4501
+        if (!isset($env)) {
4502 4502
             $env = $this->getStoreEnv();
4503 4503
         }
4504 4504
 
@@ -4535,13 +4535,13 @@  discard block
 block discarded – undo
4535 4535
                 break;
4536 4536
             }
4537 4537
 
4538
-            if (! $hasNamespace && isset($env->marker)) {
4539
-                if (! empty($env->store[$specialContentKey])) {
4538
+            if (!$hasNamespace && isset($env->marker)) {
4539
+                if (!empty($env->store[$specialContentKey])) {
4540 4540
                     $env = $env->store[$specialContentKey]->scope;
4541 4541
                     continue;
4542 4542
                 }
4543 4543
 
4544
-                if (! empty($env->declarationScopeParent)) {
4544
+                if (!empty($env->declarationScopeParent)) {
4545 4545
                     $env = $env->declarationScopeParent;
4546 4546
                     continue;
4547 4547
                 } else {
@@ -4601,7 +4601,7 @@  discard block
 block discarded – undo
4601 4601
         $normalizedName = $this->normalizeName($name);
4602 4602
         $specialContentKey = static::$namespaces['special'] . 'content';
4603 4603
 
4604
-        if (! isset($env)) {
4604
+        if (!isset($env)) {
4605 4605
             $env = $this->getStoreEnv();
4606 4606
         }
4607 4607
 
@@ -4622,13 +4622,13 @@  discard block
 block discarded – undo
4622 4622
                 return $env->store[$normalizedName];
4623 4623
             }
4624 4624
 
4625
-            if (! $hasNamespace && isset($env->marker)) {
4626
-                if (! empty($env->store[$specialContentKey])) {
4625
+            if (!$hasNamespace && isset($env->marker)) {
4626
+                if (!empty($env->store[$specialContentKey])) {
4627 4627
                     $env = $env->store[$specialContentKey]->scope;
4628 4628
                     continue;
4629 4629
                 }
4630 4630
 
4631
-                if (! empty($env->declarationScopeParent)) {
4631
+                if (!empty($env->declarationScopeParent)) {
4632 4632
                     $env = $env->declarationScopeParent;
4633 4633
                 } else {
4634 4634
                     $env = $this->rootEnv;
@@ -4663,7 +4663,7 @@  discard block
 block discarded – undo
4663 4663
      */
4664 4664
     protected function has($name, Environment $env = null)
4665 4665
     {
4666
-        return ! \is_null($this->get($name, false, $env));
4666
+        return !\is_null($this->get($name, false, $env));
4667 4667
     }
4668 4668
 
4669 4669
     /**
@@ -4684,7 +4684,7 @@  discard block
 block discarded – undo
4684 4684
                 $name = substr($name, 1);
4685 4685
             }
4686 4686
 
4687
-            if (! $parser->parseValue($strValue, $value)) {
4687
+            if (!$parser->parseValue($strValue, $value)) {
4688 4688
                 $value = $this->coerceValue($strValue);
4689 4689
             }
4690 4690
 
@@ -4763,7 +4763,7 @@  discard block
 block discarded – undo
4763 4763
      */
4764 4764
     public function addImportPath($path)
4765 4765
     {
4766
-        if (! \in_array($path, $this->importPaths)) {
4766
+        if (!\in_array($path, $this->importPaths)) {
4767 4767
             $this->importPaths[] = $path;
4768 4768
         }
4769 4769
     }
@@ -4929,17 +4929,17 @@  discard block
 block discarded – undo
4929 4929
         $hasExtension = preg_match('/[.]s?css$/', $url);
4930 4930
 
4931 4931
         // for "normal" scss imports (ignore vanilla css and external requests)
4932
-        if (! preg_match('~\.css$|^https?://|^//~', $url)) {
4932
+        if (!preg_match('~\.css$|^https?://|^//~', $url)) {
4933 4933
             $isPartial = (strpos(basename($url), '_') === 0);
4934 4934
 
4935 4935
             // try both normal and the _partial filename
4936 4936
             $urls = [$url . ($hasExtension ? '' : '.scss')];
4937 4937
 
4938
-            if (! $isPartial) {
4938
+            if (!$isPartial) {
4939 4939
                 $urls[] = preg_replace('~[^/]+$~', '_\0', $url) . ($hasExtension ? '' : '.scss');
4940 4940
             }
4941 4941
 
4942
-            if (! $hasExtension) {
4942
+            if (!$hasExtension) {
4943 4943
                 $urls[] = "$url/index.scss";
4944 4944
                 $urls[] = "$url/_index.scss";
4945 4945
                 // allow to find a plain css file, *if* no scss or partial scss is found
@@ -4952,7 +4952,7 @@  discard block
 block discarded – undo
4952 4952
                 // check urls for normal import paths
4953 4953
                 foreach ($urls as $full) {
4954 4954
                     $separator = (
4955
-                        ! empty($dir) &&
4955
+                        !empty($dir) &&
4956 4956
                         substr($dir, -1) !== '/' &&
4957 4957
                         substr($full, 0, 1) !== '/'
4958 4958
                     ) ? '/' : '';
@@ -4966,14 +4966,14 @@  discard block
 block discarded – undo
4966 4966
                 // check custom callback for import path
4967 4967
                 $file = \call_user_func($dir, $url);
4968 4968
 
4969
-                if (! \is_null($file)) {
4969
+                if (!\is_null($file)) {
4970 4970
                     return $file;
4971 4971
                 }
4972 4972
             }
4973 4973
         }
4974 4974
 
4975 4975
         if ($urls) {
4976
-            if (! $hasExtension || preg_match('/[.]scss$/', $url)) {
4976
+            if (!$hasExtension || preg_match('/[.]scss$/', $url)) {
4977 4977
                 throw $this->error("`$url` file not found for @import");
4978 4978
             }
4979 4979
         }
@@ -5061,7 +5061,7 @@  discard block
 block discarded – undo
5061 5061
             $msg = sprintf($msg, ...$args);
5062 5062
         }
5063 5063
 
5064
-        if (! $this->ignoreCallStackMessage) {
5064
+        if (!$this->ignoreCallStackMessage) {
5065 5065
             $line   = $this->sourceLine;
5066 5066
             $column = $this->sourceColumn;
5067 5067
 
@@ -5138,7 +5138,7 @@  discard block
 block discarded – undo
5138 5138
 
5139 5139
                     $callStackMsg[] = $msg;
5140 5140
 
5141
-                    if (! \is_null($limit) && $ncall > $limit) {
5141
+                    if (!\is_null($limit) && $ncall > $limit) {
5142 5142
                         break;
5143 5143
                     }
5144 5144
                 }
@@ -5158,7 +5158,7 @@  discard block
 block discarded – undo
5158 5158
     protected function handleImportLoop($name)
5159 5159
     {
5160 5160
         for ($env = $this->env; $env; $env = $env->parent) {
5161
-            if (! $env->block) {
5161
+            if (!$env->block) {
5162 5162
                 continue;
5163 5163
             }
5164 5164
 
@@ -5180,7 +5180,7 @@  discard block
 block discarded – undo
5180 5180
      */
5181 5181
     protected function callScssFunction($func, $argValues)
5182 5182
     {
5183
-        if (! $func) {
5183
+        if (!$func) {
5184 5184
             return static::$defaultValue;
5185 5185
         }
5186 5186
         $name = $func->name;
@@ -5199,7 +5199,7 @@  discard block
 block discarded – undo
5199 5199
 
5200 5200
         $this->env->marker = 'function';
5201 5201
 
5202
-        if (! empty($func->parentEnv)) {
5202
+        if (!empty($func->parentEnv)) {
5203 5203
             $this->env->declarationScopeParent = $func->parentEnv;
5204 5204
         } else {
5205 5205
             throw $this->error("@function $name() without parentEnv");
@@ -5209,7 +5209,7 @@  discard block
 block discarded – undo
5209 5209
 
5210 5210
         $this->popEnv();
5211 5211
 
5212
-        return ! isset($ret) ? static::$defaultValue : $ret;
5212
+        return !isset($ret) ? static::$defaultValue : $ret;
5213 5213
     }
5214 5214
 
5215 5215
     /**
@@ -5246,7 +5246,7 @@  discard block
 block discarded – undo
5246 5246
 
5247 5247
         $returnValue = \call_user_func($function, $sorted, $kwargs);
5248 5248
 
5249
-        if (! isset($returnValue)) {
5249
+        if (!isset($returnValue)) {
5250 5250
             return null;
5251 5251
         }
5252 5252
 
@@ -5264,7 +5264,7 @@  discard block
 block discarded – undo
5264 5264
     {
5265 5265
         $libName = 'lib' . preg_replace_callback(
5266 5266
             '/_(.)/',
5267
-            function ($m) {
5267
+            function($m) {
5268 5268
                 return ucfirst($m[1]);
5269 5269
             },
5270 5270
             ucfirst($name)
@@ -5286,7 +5286,7 @@  discard block
 block discarded – undo
5286 5286
     {
5287 5287
         static $parser = null;
5288 5288
 
5289
-        if (! isset($prototypes)) {
5289
+        if (!isset($prototypes)) {
5290 5290
             $keyArgs = [];
5291 5291
             $posArgs = [];
5292 5292
 
@@ -5327,7 +5327,7 @@  discard block
 block discarded – undo
5327 5327
 
5328 5328
         $finalArgs = [];
5329 5329
 
5330
-        if (! \is_array(reset($prototypes))) {
5330
+        if (!\is_array(reset($prototypes))) {
5331 5331
             $prototypes = [$prototypes];
5332 5332
         }
5333 5333
 
@@ -5388,7 +5388,7 @@  discard block
 block discarded – undo
5388 5388
                 foreach ($prototype as $i => $p) {
5389 5389
                     $name = explode(':', $p)[0];
5390 5390
 
5391
-                    if (! isset($finalArgs[$i])) {
5391
+                    if (!isset($finalArgs[$i])) {
5392 5392
                         $finalArgs[$i] = null;
5393 5393
                     }
5394 5394
                 }
@@ -5421,7 +5421,7 @@  discard block
 block discarded – undo
5421 5421
             $this->ignoreCallStackMessage = $ignoreCallStackMessage;
5422 5422
         }
5423 5423
 
5424
-        if ($exceptionMessage && ! $prototypeHasMatch) {
5424
+        if ($exceptionMessage && !$prototypeHasMatch) {
5425 5425
             if (\in_array($functionName, ['libRgb', 'libRgba', 'libHsl', 'libHsla'])) {
5426 5426
                 // if var() or calc() is used as an argument, return as a css function
5427 5427
                 foreach ($args as $arg) {
@@ -5484,12 +5484,12 @@  discard block
 block discarded – undo
5484 5484
 
5485 5485
         // assign the keyword args
5486 5486
         foreach ((array) $argValues as $arg) {
5487
-            if (! empty($arg[0])) {
5487
+            if (!empty($arg[0])) {
5488 5488
                 $hasKeywordArgument = true;
5489 5489
 
5490 5490
                 $name = $arg[0][1];
5491 5491
 
5492
-                if (! isset($args[$name])) {
5492
+                if (!isset($args[$name])) {
5493 5493
                     foreach (array_keys($args) as $an) {
5494 5494
                         if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5495 5495
                             $name = $an;
@@ -5498,7 +5498,7 @@  discard block
 block discarded – undo
5498 5498
                     }
5499 5499
                 }
5500 5500
 
5501
-                if (! isset($args[$name]) || $args[$name][3]) {
5501
+                if (!isset($args[$name]) || $args[$name][3]) {
5502 5502
                     if ($hasVariable) {
5503 5503
                         $deferredNamedKeywordArgs[$name] = $arg[1];
5504 5504
                     } else {
@@ -5509,14 +5509,14 @@  discard block
 block discarded – undo
5509 5509
                 } else {
5510 5510
                     $keywordArgs[$name] = $arg[1];
5511 5511
                 }
5512
-            } elseif (! empty($arg[2])) {
5512
+            } elseif (!empty($arg[2])) {
5513 5513
                 // $arg[2] means a var followed by ... in the arg ($list... )
5514 5514
                 $val = $this->reduce($arg[1], true);
5515 5515
 
5516 5516
                 if ($val[0] === Type::T_LIST) {
5517 5517
                     foreach ($val[2] as $name => $item) {
5518
-                        if (! is_numeric($name)) {
5519
-                            if (! isset($args[$name])) {
5518
+                        if (!is_numeric($name)) {
5519
+                            if (!isset($args[$name])) {
5520 5520
                                 foreach (array_keys($args) as $an) {
5521 5521
                                     if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5522 5522
                                         $name = $an;
@@ -5543,8 +5543,8 @@  discard block
 block discarded – undo
5543 5543
                         $name = $this->compileStringContent($this->coerceString($name));
5544 5544
                         $item = $val[2][$i];
5545 5545
 
5546
-                        if (! is_numeric($name)) {
5547
-                            if (! isset($args[$name])) {
5546
+                        if (!is_numeric($name)) {
5547
+                            if (!isset($args[$name])) {
5548 5548
                                 foreach (array_keys($args) as $an) {
5549 5549
                                     if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
5550 5550
                                         $name = $an;
@@ -5588,7 +5588,7 @@  discard block
 block discarded – undo
5588 5588
                     }
5589 5589
                 }
5590 5590
 
5591
-                $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , [], $isVariable];
5591
+                $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator, [], $isVariable];
5592 5592
 
5593 5593
                 for ($count = \count($remaining); $i < $count; $i++) {
5594 5594
                     $val[2][] = $remaining[$i];
@@ -5605,7 +5605,7 @@  discard block
 block discarded – undo
5605 5605
                 $val = $remaining[$i];
5606 5606
             } elseif (isset($keywordArgs[$name])) {
5607 5607
                 $val = $keywordArgs[$name];
5608
-            } elseif (! empty($default)) {
5608
+            } elseif (!empty($default)) {
5609 5609
                 continue;
5610 5610
             } else {
5611 5611
                 throw $this->error("Missing argument $name");
@@ -5753,7 +5753,7 @@  discard block
 block discarded – undo
5753 5753
             return [Type::T_LIST, ',', $list];
5754 5754
         }
5755 5755
 
5756
-        return [Type::T_LIST, $delim, ! isset($item) ? [] : [$item]];
5756
+        return [Type::T_LIST, $delim, !isset($item) ? [] : [$item]];
5757 5757
     }
5758 5758
 
5759 5759
     /**
@@ -5784,10 +5784,10 @@  discard block
 block discarded – undo
5784 5784
         switch ($value[0]) {
5785 5785
             case Type::T_COLOR:
5786 5786
                 for ($i = 1; $i <= 3; $i++) {
5787
-                    if (! is_numeric($value[$i])) {
5787
+                    if (!is_numeric($value[$i])) {
5788 5788
                         $cv = $this->compileRGBAValue($value[$i]);
5789 5789
 
5790
-                        if (! is_numeric($cv)) {
5790
+                        if (!is_numeric($cv)) {
5791 5791
                             return null;
5792 5792
                         }
5793 5793
 
@@ -5795,10 +5795,10 @@  discard block
 block discarded – undo
5795 5795
                     }
5796 5796
 
5797 5797
                     if (isset($value[4])) {
5798
-                        if (! is_numeric($value[4])) {
5798
+                        if (!is_numeric($value[4])) {
5799 5799
                             $cv = $this->compileRGBAValue($value[4], true);
5800 5800
 
5801
-                            if (! is_numeric($cv)) {
5801
+                            if (!is_numeric($cv)) {
5802 5802
                                 return null;
5803 5803
                             }
5804 5804
 
@@ -5822,7 +5822,7 @@  discard block
 block discarded – undo
5822 5822
                 return null;
5823 5823
 
5824 5824
             case Type::T_KEYWORD:
5825
-                if (! \is_string($value[1])) {
5825
+                if (!\is_string($value[1])) {
5826 5826
                     return null;
5827 5827
                 }
5828 5828
 
@@ -5915,7 +5915,7 @@  discard block
 block discarded – undo
5915 5915
      */
5916 5916
     protected function compileColorPartValue($value, $min, $max, $isInt = true, $clamp = true, $modulo = false)
5917 5917
     {
5918
-        if (! is_numeric($value)) {
5918
+        if (!is_numeric($value)) {
5919 5919
             if (\is_array($value)) {
5920 5920
                 $reduced = $this->reduce($value);
5921 5921
 
@@ -5996,7 +5996,7 @@  discard block
 block discarded – undo
5996 5996
     protected function coercePercent($value)
5997 5997
     {
5998 5998
         if ($value[0] === Type::T_NUMBER) {
5999
-            if (! empty($value[2]['%'])) {
5999
+            if (!empty($value[2]['%'])) {
6000 6000
                 return $value[1] / 100;
6001 6001
             }
6002 6002
 
@@ -6234,7 +6234,7 @@  discard block
 block discarded – undo
6234 6234
             return static::$null;
6235 6235
         }
6236 6236
 
6237
-        if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
6237
+        if (!in_array($functionReference[0], [Type::T_FUNCTION_REFERENCE, Type::T_FUNCTION])) {
6238 6238
             throw $this->error('Function reference expected, got ' . $functionReference[0]);
6239 6239
         }
6240 6240
 
@@ -6245,7 +6245,7 @@  discard block
 block discarded – undo
6245 6245
             if (is_numeric($varname)) {
6246 6246
                 $varname = null;
6247 6247
             } else {
6248
-                $varname = [ 'var', $varname];
6248
+                $varname = ['var', $varname];
6249 6249
             }
6250 6250
 
6251 6251
             $callArgs[] = [$varname, $arg, false];
@@ -6281,7 +6281,7 @@  discard block
 block discarded – undo
6281 6281
     {
6282 6282
         list($cond, $t, $f) = $args;
6283 6283
 
6284
-        if (! $this->isTruthy($this->reduce($cond, true))) {
6284
+        if (!$this->isTruthy($this->reduce($cond, true))) {
6285 6285
             return $this->reduce($f, true);
6286 6286
         }
6287 6287
 
@@ -6322,12 +6322,12 @@  discard block
 block discarded – undo
6322 6322
         ['color', 'alpha'],
6323 6323
         ['channels'],
6324 6324
         ['red', 'green', 'blue'],
6325
-        ['red', 'green', 'blue', 'alpha'] ];
6325
+        ['red', 'green', 'blue', 'alpha']];
6326 6326
     protected function libRgb($args, $kwargs, $funcName = 'rgb')
6327 6327
     {
6328 6328
         switch (\count($args)) {
6329 6329
             case 1:
6330
-                if (! $color = $this->coerceColor($args[0], true)) {
6330
+                if (!$color = $this->coerceColor($args[0], true)) {
6331 6331
                     $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ')']];
6332 6332
                 }
6333 6333
                 break;
@@ -6335,7 +6335,7 @@  discard block
 block discarded – undo
6335 6335
             case 3:
6336 6336
                 $color = [Type::T_COLOR, $args[0], $args[1], $args[2]];
6337 6337
 
6338
-                if (! $color = $this->coerceColor($color)) {
6338
+                if (!$color = $this->coerceColor($color)) {
6339 6339
                     $color = [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6340 6340
                 }
6341 6341
 
@@ -6360,7 +6360,7 @@  discard block
 block discarded – undo
6360 6360
             default:
6361 6361
                 $color = [Type::T_COLOR, $args[0], $args[1], $args[2], $args[3]];
6362 6362
 
6363
-                if (! $color = $this->coerceColor($color)) {
6363
+                if (!$color = $this->coerceColor($color)) {
6364 6364
                     $color = [Type::T_STRING, '',
6365 6365
                         [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6366 6366
                 }
@@ -6375,7 +6375,7 @@  discard block
 block discarded – undo
6375 6375
         ['color', 'alpha'],
6376 6376
         ['channels'],
6377 6377
         ['red', 'green', 'blue'],
6378
-        ['red', 'green', 'blue', 'alpha'] ];
6378
+        ['red', 'green', 'blue', 'alpha']];
6379 6379
     protected function libRgba($args, $kwargs)
6380 6380
     {
6381 6381
         return $this->libRgb($args, $kwargs, 'rgba');
@@ -6390,7 +6390,7 @@  discard block
 block discarded – undo
6390 6390
             if (isset($args[$iarg])) {
6391 6391
                 $val = $this->assertNumber($args[$iarg]);
6392 6392
 
6393
-                if (! isset($color[$irgba])) {
6393
+                if (!isset($color[$irgba])) {
6394 6394
                     $color[$irgba] = (($irgba < 4) ? 0 : 1);
6395 6395
                 }
6396 6396
 
@@ -6398,11 +6398,11 @@  discard block
 block discarded – undo
6398 6398
             }
6399 6399
         }
6400 6400
 
6401
-        if (! empty($args[4]) || ! empty($args[5]) || ! empty($args[6])) {
6401
+        if (!empty($args[4]) || !empty($args[5]) || !empty($args[6])) {
6402 6402
             $hsl = $this->toHSL($color[1], $color[2], $color[3]);
6403 6403
 
6404 6404
             foreach ([4 => 1, 5 => 2, 6 => 3] as $iarg => $ihsl) {
6405
-                if (! empty($args[$iarg])) {
6405
+                if (!empty($args[$iarg])) {
6406 6406
                     $val = $this->assertNumber($args[$iarg]);
6407 6407
                     $hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
6408 6408
                 }
@@ -6426,7 +6426,7 @@  discard block
 block discarded – undo
6426 6426
     ];
6427 6427
     protected function libAdjustColor($args)
6428 6428
     {
6429
-        return $this->alterColor($args, function ($base, $alter, $i) {
6429
+        return $this->alterColor($args, function($base, $alter, $i) {
6430 6430
             return $base + $alter;
6431 6431
         });
6432 6432
     }
@@ -6437,7 +6437,7 @@  discard block
 block discarded – undo
6437 6437
     ];
6438 6438
     protected function libChangeColor($args)
6439 6439
     {
6440
-        return $this->alterColor($args, function ($base, $alter, $i) {
6440
+        return $this->alterColor($args, function($base, $alter, $i) {
6441 6441
             return $alter;
6442 6442
         });
6443 6443
     }
@@ -6448,7 +6448,7 @@  discard block
 block discarded – undo
6448 6448
     ];
6449 6449
     protected function libScaleColor($args)
6450 6450
     {
6451
-        return $this->alterColor($args, function ($base, $scale, $i) {
6451
+        return $this->alterColor($args, function($base, $scale, $i) {
6452 6452
             // 1, 2, 3 - rgb
6453 6453
             // 4, 5, 6 - hsl
6454 6454
             // 7 - a
@@ -6566,7 +6566,7 @@  discard block
 block discarded – undo
6566 6566
         $first = $this->assertColor($first);
6567 6567
         $second = $this->assertColor($second);
6568 6568
 
6569
-        if (! isset($weight)) {
6569
+        if (!isset($weight)) {
6570 6570
             $weight = 0.5;
6571 6571
         } else {
6572 6572
             $weight = $this->coercePercent($weight);
@@ -6597,7 +6597,7 @@  discard block
 block discarded – undo
6597 6597
     protected static $libHsl = [
6598 6598
         ['channels'],
6599 6599
         ['hue', 'saturation', 'lightness'],
6600
-        ['hue', 'saturation', 'lightness', 'alpha'] ];
6600
+        ['hue', 'saturation', 'lightness', 'alpha']];
6601 6601
     protected function libHsl($args, $kwargs, $funcName = 'hsl')
6602 6602
     {
6603 6603
         $args_to_check = $args;
@@ -6634,7 +6634,7 @@  discard block
 block discarded – undo
6634 6634
             if (
6635 6635
                 $k >= 2 && count($args) === 4 &&
6636 6636
                 in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
6637
-                in_array($arg[1], ['calc','env'])
6637
+                in_array($arg[1], ['calc', 'env'])
6638 6638
             ) {
6639 6639
                 return null;
6640 6640
             }
@@ -6645,19 +6645,19 @@  discard block
 block discarded – undo
6645 6645
         if (\count($args) === 4) {
6646 6646
             $alpha = $this->compileColorPartValue($args[3], 0, 100, false);
6647 6647
 
6648
-            if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness) || ! is_numeric($alpha)) {
6648
+            if (!is_numeric($hue) || !is_numeric($saturation) || !is_numeric($lightness) || !is_numeric($alpha)) {
6649 6649
                 return [Type::T_STRING, '',
6650 6650
                     [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
6651 6651
             }
6652 6652
         } else {
6653
-            if (! is_numeric($hue) || ! is_numeric($saturation) || ! is_numeric($lightness)) {
6653
+            if (!is_numeric($hue) || !is_numeric($saturation) || !is_numeric($lightness)) {
6654 6654
                 return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
6655 6655
             }
6656 6656
         }
6657 6657
 
6658 6658
         $color = $this->toRGB($hue, $saturation, $lightness);
6659 6659
 
6660
-        if (! \is_null($alpha)) {
6660
+        if (!\is_null($alpha)) {
6661 6661
             $color[4] = $alpha;
6662 6662
         }
6663 6663
 
@@ -6792,7 +6792,7 @@  discard block
 block discarded – undo
6792 6792
     {
6793 6793
         list($value, $weight) = $args;
6794 6794
 
6795
-        if (! isset($weight)) {
6795
+        if (!isset($weight)) {
6796 6796
             $weight = 1;
6797 6797
         } else {
6798 6798
             $weight = $this->coercePercent($weight);
@@ -6870,7 +6870,7 @@  discard block
 block discarded – undo
6870 6870
     {
6871 6871
         $value = $args[0];
6872 6872
 
6873
-        if ($value[0] === Type::T_STRING && ! empty($value[1])) {
6873
+        if ($value[0] === Type::T_STRING && !empty($value[1])) {
6874 6874
             return $value;
6875 6875
         }
6876 6876
 
@@ -6984,7 +6984,7 @@  discard block
 block discarded – undo
6984 6984
             if (empty($unit)) {
6985 6985
                 $unit = $number[2];
6986 6986
                 $originalUnit = $item->unitStr();
6987
-            } elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
6987
+            } elseif ($number[1] && $unit !== $number[2] && !empty($number[2])) {
6988 6988
                 throw $this->error('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
6989 6989
             }
6990 6990
 
@@ -7049,7 +7049,7 @@  discard block
 block discarded – undo
7049 7049
             $n += \count($list[2]);
7050 7050
         }
7051 7051
 
7052
-        if (! isset($list[2][$n])) {
7052
+        if (!isset($list[2][$n])) {
7053 7053
             throw $this->error('Invalid argument for "n"');
7054 7054
         }
7055 7055
 
@@ -7064,7 +7064,7 @@  discard block
 block discarded – undo
7064 7064
         $map = $this->assertMap($args[0]);
7065 7065
         $key = $args[1];
7066 7066
 
7067
-        if (! \is_null($key)) {
7067
+        if (!\is_null($key)) {
7068 7068
             $key = $this->compileStringContent($this->coerceString($key));
7069 7069
 
7070 7070
             for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
@@ -7180,7 +7180,7 @@  discard block
 block discarded – undo
7180 7180
         $list = $args[0];
7181 7181
         $this->coerceList($list, ' ');
7182 7182
 
7183
-        if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
7183
+        if (!empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
7184 7184
             return true;
7185 7185
         }
7186 7186
 
@@ -7189,7 +7189,7 @@  discard block
 block discarded – undo
7189 7189
 
7190 7190
     protected function listSeparatorForJoin($list1, $sep)
7191 7191
     {
7192
-        if (! isset($sep)) {
7192
+        if (!isset($sep)) {
7193 7193
             return $list1[1];
7194 7194
         }
7195 7195
 
@@ -7224,7 +7224,7 @@  discard block
 block discarded – undo
7224 7224
             $bracketed = false;
7225 7225
         } else {
7226 7226
             $bracketed = $this->compileValue($bracketed);
7227
-            $bracketed = ! ! $bracketed;
7227
+            $bracketed = !!$bracketed;
7228 7228
 
7229 7229
             if ($bracketed === true) {
7230 7230
                 $bracketed = true;
@@ -7234,7 +7234,7 @@  discard block
 block discarded – undo
7234 7234
         if ($bracketed === 'auto') {
7235 7235
             $bracketed = false;
7236 7236
 
7237
-            if (! empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') {
7237
+            if (!empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') {
7238 7238
                 $bracketed = true;
7239 7239
             }
7240 7240
         }
@@ -7356,8 +7356,8 @@  discard block
 block discarded – undo
7356 7356
         list($number1, $number2) = $args;
7357 7357
 
7358 7358
         if (
7359
-            ! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
7360
-            ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
7359
+            !isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
7360
+            !isset($number2[0]) || $number2[0] !== Type::T_NUMBER
7361 7361
         ) {
7362 7362
             throw $this->error('Invalid argument(s) for "comparable"');
7363 7363
         }
@@ -7410,7 +7410,7 @@  discard block
 block discarded – undo
7410 7410
     protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
7411 7411
     protected function libStrSlice($args)
7412 7412
     {
7413
-        if (isset($args[2]) && ! $args[2][1]) {
7413
+        if (isset($args[2]) && !$args[2][1]) {
7414 7414
             return static::$nullString;
7415 7415
         }
7416 7416
 
@@ -7554,7 +7554,7 @@  discard block
 block discarded – undo
7554 7554
     {
7555 7555
         static $id;
7556 7556
 
7557
-        if (! isset($id)) {
7557
+        if (!isset($id)) {
7558 7558
             $id = PHP_INT_SIZE === 4
7559 7559
                 ? mt_rand(0, pow(36, 5)) . str_pad(mt_rand(0, pow(36, 5)) % 10000000, 7, '0', STR_PAD_LEFT)
7560 7560
                 : mt_rand(0, pow(36, 8));
@@ -7581,10 +7581,10 @@  discard block
 block discarded – undo
7581 7581
             }
7582 7582
 
7583 7583
             if (
7584
-                ! empty($value['enclosing']) &&
7584
+                !empty($value['enclosing']) &&
7585 7585
                 ($force_enclosing_display ||
7586 7586
                     ($value['enclosing'] === 'bracket') ||
7587
-                    ! \count($value[2]))
7587
+                    !\count($value[2]))
7588 7588
             ) {
7589 7589
                 $value['enclosing'] = 'forced_' . $value['enclosing'];
7590 7590
                 $force_enclosing_display = true;
@@ -7674,11 +7674,11 @@  discard block
 block discarded – undo
7674 7674
     protected function isSuperSelector($super, $sub)
7675 7675
     {
7676 7676
         // one and only one selector for each arg
7677
-        if (! $super || \count($super) !== 1) {
7677
+        if (!$super || \count($super) !== 1) {
7678 7678
             throw $this->error('Invalid super selector for isSuperSelector()');
7679 7679
         }
7680 7680
 
7681
-        if (! $sub || \count($sub) !== 1) {
7681
+        if (!$sub || \count($sub) !== 1) {
7682 7682
             throw $this->error('Invalid sub selector for isSuperSelector()');
7683 7683
         }
7684 7684
 
@@ -7693,7 +7693,7 @@  discard block
 block discarded – undo
7693 7693
 
7694 7694
             array_walk_recursive(
7695 7695
                 $node,
7696
-                function ($value, $key) use (&$compound) {
7696
+                function($value, $key) use (&$compound) {
7697 7697
                     $compound .= $value;
7698 7698
                 }
7699 7699
             );
@@ -7706,7 +7706,7 @@  discard block
 block discarded – undo
7706 7706
                 $nextMustMatch = true;
7707 7707
                 $i++;
7708 7708
             } else {
7709
-                while ($i < \count($sub) && ! $this->isSuperPart($node, $sub[$i])) {
7709
+                while ($i < \count($sub) && !$this->isSuperPart($node, $sub[$i])) {
7710 7710
                     if ($nextMustMatch) {
7711 7711
                         return false;
7712 7712
                     }
@@ -7782,14 +7782,14 @@  discard block
 block discarded – undo
7782 7782
     {
7783 7783
         $lastSelectors = array_pop($selectors);
7784 7784
 
7785
-        if (! $lastSelectors) {
7785
+        if (!$lastSelectors) {
7786 7786
             throw $this->error('Invalid selector list in selector-append()');
7787 7787
         }
7788 7788
 
7789 7789
         while (\count($selectors)) {
7790 7790
             $previousSelectors = array_pop($selectors);
7791 7791
 
7792
-            if (! $previousSelectors) {
7792
+            if (!$previousSelectors) {
7793 7793
                 throw $this->error('Invalid selector list in selector-append()');
7794 7794
             }
7795 7795
 
@@ -7832,7 +7832,7 @@  discard block
 block discarded – undo
7832 7832
         $extendee  = $this->getSelectorArg($extendee);
7833 7833
         $extender  = $this->getSelectorArg($extender);
7834 7834
 
7835
-        if (! $selectors || ! $extendee || ! $extender) {
7835
+        if (!$selectors || !$extendee || !$extender) {
7836 7836
             throw $this->error('selector-extend() invalid arguments');
7837 7837
         }
7838 7838
 
@@ -7853,7 +7853,7 @@  discard block
 block discarded – undo
7853 7853
         $original    = $this->getSelectorArg($original);
7854 7854
         $replacement = $this->getSelectorArg($replacement);
7855 7855
 
7856
-        if (! $selectors || ! $original || ! $replacement) {
7856
+        if (!$selectors || !$original || !$replacement) {
7857 7857
             throw $this->error('selector-replace() invalid arguments');
7858 7858
         }
7859 7859
 
@@ -7889,7 +7889,7 @@  discard block
 block discarded – undo
7889 7889
         $extended = [];
7890 7890
 
7891 7891
         foreach ($selectors as $selector) {
7892
-            if (! $replace) {
7892
+            if (!$replace) {
7893 7893
                 $extended[] = $selector;
7894 7894
             }
7895 7895
 
@@ -7957,7 +7957,7 @@  discard block
 block discarded – undo
7957 7957
         $selectors1 = $this->getSelectorArg($selectors1);
7958 7958
         $selectors2 = $this->getSelectorArg($selectors2);
7959 7959
 
7960
-        if (! $selectors1 || ! $selectors2) {
7960
+        if (!$selectors1 || !$selectors2) {
7961 7961
             throw $this->error('selector-unify() invalid arguments');
7962 7962
         }
7963 7963
 
@@ -7982,11 +7982,11 @@  discard block
 block discarded – undo
7982 7982
      */
7983 7983
     protected function unifyCompoundSelectors($compound1, $compound2)
7984 7984
     {
7985
-        if (! \count($compound1)) {
7985
+        if (!\count($compound1)) {
7986 7986
             return $compound2;
7987 7987
         }
7988 7988
 
7989
-        if (! \count($compound2)) {
7989
+        if (!\count($compound2)) {
7990 7990
             return $compound1;
7991 7991
         }
7992 7992
 
@@ -7995,7 +7995,7 @@  discard block
 block discarded – undo
7995 7995
         $lastPart2 = array_pop($compound2);
7996 7996
         $last      = $this->mergeParts($lastPart1, $lastPart2);
7997 7997
 
7998
-        if (! $last) {
7998
+        if (!$last) {
7999 7999
             return [[]];
8000 8000
         }
8001 8001
 
@@ -8219,7 +8219,7 @@  discard block
 block discarded – undo
8219 8219
     protected function findTagName($parts)
8220 8220
     {
8221 8221
         foreach ($parts as $part) {
8222
-            if (! preg_match('/^[\[.:#%_-]/', $part)) {
8222
+            if (!preg_match('/^[\[.:#%_-]/', $part)) {
8223 8223
                 return $part;
8224 8224
             }
8225 8225
         }
@@ -8257,7 +8257,7 @@  discard block
 block discarded – undo
8257 8257
         $listParts = [];
8258 8258
 
8259 8259
         foreach ($matches as $match) {
8260
-            if (! is_file($match)) {
8260
+            if (!is_file($match)) {
8261 8261
                 continue;
8262 8262
             }
8263 8263
 
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Colors.php 2 patches
Indentation   +211 added lines, -211 removed lines patch added patch discarded remove patch
@@ -19,227 +19,227 @@
 block discarded – undo
19 19
  */
20 20
 class Colors
21 21
 {
22
-    /**
23
-     * CSS Colors
24
-     *
25
-     * @see http://www.w3.org/TR/css3-color
26
-     *
27
-     * @var array
28
-     */
29
-    protected static $cssColors = [
30
-        'aliceblue' => '240,248,255',
31
-        'antiquewhite' => '250,235,215',
32
-        'cyan' => '0,255,255',
33
-        'aqua' => '0,255,255',
34
-        'aquamarine' => '127,255,212',
35
-        'azure' => '240,255,255',
36
-        'beige' => '245,245,220',
37
-        'bisque' => '255,228,196',
38
-        'black' => '0,0,0',
39
-        'blanchedalmond' => '255,235,205',
40
-        'blue' => '0,0,255',
41
-        'blueviolet' => '138,43,226',
42
-        'brown' => '165,42,42',
43
-        'burlywood' => '222,184,135',
44
-        'cadetblue' => '95,158,160',
45
-        'chartreuse' => '127,255,0',
46
-        'chocolate' => '210,105,30',
47
-        'coral' => '255,127,80',
48
-        'cornflowerblue' => '100,149,237',
49
-        'cornsilk' => '255,248,220',
50
-        'crimson' => '220,20,60',
51
-        'darkblue' => '0,0,139',
52
-        'darkcyan' => '0,139,139',
53
-        'darkgoldenrod' => '184,134,11',
54
-        'darkgray' => '169,169,169',
55
-        'darkgrey' => '169,169,169',
56
-        'darkgreen' => '0,100,0',
57
-        'darkkhaki' => '189,183,107',
58
-        'darkmagenta' => '139,0,139',
59
-        'darkolivegreen' => '85,107,47',
60
-        'darkorange' => '255,140,0',
61
-        'darkorchid' => '153,50,204',
62
-        'darkred' => '139,0,0',
63
-        'darksalmon' => '233,150,122',
64
-        'darkseagreen' => '143,188,143',
65
-        'darkslateblue' => '72,61,139',
66
-        'darkslategray' => '47,79,79',
67
-        'darkslategrey' => '47,79,79',
68
-        'darkturquoise' => '0,206,209',
69
-        'darkviolet' => '148,0,211',
70
-        'deeppink' => '255,20,147',
71
-        'deepskyblue' => '0,191,255',
72
-        'dimgray' => '105,105,105',
73
-        'dimgrey' => '105,105,105',
74
-        'dodgerblue' => '30,144,255',
75
-        'firebrick' => '178,34,34',
76
-        'floralwhite' => '255,250,240',
77
-        'forestgreen' => '34,139,34',
78
-        'magenta' => '255,0,255',
79
-        'fuchsia' => '255,0,255',
80
-        'gainsboro' => '220,220,220',
81
-        'ghostwhite' => '248,248,255',
82
-        'gold' => '255,215,0',
83
-        'goldenrod' => '218,165,32',
84
-        'gray' => '128,128,128',
85
-        'grey' => '128,128,128',
86
-        'green' => '0,128,0',
87
-        'greenyellow' => '173,255,47',
88
-        'honeydew' => '240,255,240',
89
-        'hotpink' => '255,105,180',
90
-        'indianred' => '205,92,92',
91
-        'indigo' => '75,0,130',
92
-        'ivory' => '255,255,240',
93
-        'khaki' => '240,230,140',
94
-        'lavender' => '230,230,250',
95
-        'lavenderblush' => '255,240,245',
96
-        'lawngreen' => '124,252,0',
97
-        'lemonchiffon' => '255,250,205',
98
-        'lightblue' => '173,216,230',
99
-        'lightcoral' => '240,128,128',
100
-        'lightcyan' => '224,255,255',
101
-        'lightgoldenrodyellow' => '250,250,210',
102
-        'lightgray' => '211,211,211',
103
-        'lightgrey' => '211,211,211',
104
-        'lightgreen' => '144,238,144',
105
-        'lightpink' => '255,182,193',
106
-        'lightsalmon' => '255,160,122',
107
-        'lightseagreen' => '32,178,170',
108
-        'lightskyblue' => '135,206,250',
109
-        'lightslategray' => '119,136,153',
110
-        'lightslategrey' => '119,136,153',
111
-        'lightsteelblue' => '176,196,222',
112
-        'lightyellow' => '255,255,224',
113
-        'lime' => '0,255,0',
114
-        'limegreen' => '50,205,50',
115
-        'linen' => '250,240,230',
116
-        'maroon' => '128,0,0',
117
-        'mediumaquamarine' => '102,205,170',
118
-        'mediumblue' => '0,0,205',
119
-        'mediumorchid' => '186,85,211',
120
-        'mediumpurple' => '147,112,219',
121
-        'mediumseagreen' => '60,179,113',
122
-        'mediumslateblue' => '123,104,238',
123
-        'mediumspringgreen' => '0,250,154',
124
-        'mediumturquoise' => '72,209,204',
125
-        'mediumvioletred' => '199,21,133',
126
-        'midnightblue' => '25,25,112',
127
-        'mintcream' => '245,255,250',
128
-        'mistyrose' => '255,228,225',
129
-        'moccasin' => '255,228,181',
130
-        'navajowhite' => '255,222,173',
131
-        'navy' => '0,0,128',
132
-        'oldlace' => '253,245,230',
133
-        'olive' => '128,128,0',
134
-        'olivedrab' => '107,142,35',
135
-        'orange' => '255,165,0',
136
-        'orangered' => '255,69,0',
137
-        'orchid' => '218,112,214',
138
-        'palegoldenrod' => '238,232,170',
139
-        'palegreen' => '152,251,152',
140
-        'paleturquoise' => '175,238,238',
141
-        'palevioletred' => '219,112,147',
142
-        'papayawhip' => '255,239,213',
143
-        'peachpuff' => '255,218,185',
144
-        'peru' => '205,133,63',
145
-        'pink' => '255,192,203',
146
-        'plum' => '221,160,221',
147
-        'powderblue' => '176,224,230',
148
-        'purple' => '128,0,128',
149
-        'red' => '255,0,0',
150
-        'rosybrown' => '188,143,143',
151
-        'royalblue' => '65,105,225',
152
-        'saddlebrown' => '139,69,19',
153
-        'salmon' => '250,128,114',
154
-        'sandybrown' => '244,164,96',
155
-        'seagreen' => '46,139,87',
156
-        'seashell' => '255,245,238',
157
-        'sienna' => '160,82,45',
158
-        'silver' => '192,192,192',
159
-        'skyblue' => '135,206,235',
160
-        'slateblue' => '106,90,205',
161
-        'slategray' => '112,128,144',
162
-        'slategrey' => '112,128,144',
163
-        'snow' => '255,250,250',
164
-        'springgreen' => '0,255,127',
165
-        'steelblue' => '70,130,180',
166
-        'tan' => '210,180,140',
167
-        'teal' => '0,128,128',
168
-        'thistle' => '216,191,216',
169
-        'tomato' => '255,99,71',
170
-        'turquoise' => '64,224,208',
171
-        'violet' => '238,130,238',
172
-        'wheat' => '245,222,179',
173
-        'white' => '255,255,255',
174
-        'whitesmoke' => '245,245,245',
175
-        'yellow' => '255,255,0',
176
-        'yellowgreen' => '154,205,50',
177
-        'rebeccapurple' => '102,51,153',
178
-        'transparent' => '0,0,0,0',
179
-    ];
22
+	/**
23
+	 * CSS Colors
24
+	 *
25
+	 * @see http://www.w3.org/TR/css3-color
26
+	 *
27
+	 * @var array
28
+	 */
29
+	protected static $cssColors = [
30
+		'aliceblue' => '240,248,255',
31
+		'antiquewhite' => '250,235,215',
32
+		'cyan' => '0,255,255',
33
+		'aqua' => '0,255,255',
34
+		'aquamarine' => '127,255,212',
35
+		'azure' => '240,255,255',
36
+		'beige' => '245,245,220',
37
+		'bisque' => '255,228,196',
38
+		'black' => '0,0,0',
39
+		'blanchedalmond' => '255,235,205',
40
+		'blue' => '0,0,255',
41
+		'blueviolet' => '138,43,226',
42
+		'brown' => '165,42,42',
43
+		'burlywood' => '222,184,135',
44
+		'cadetblue' => '95,158,160',
45
+		'chartreuse' => '127,255,0',
46
+		'chocolate' => '210,105,30',
47
+		'coral' => '255,127,80',
48
+		'cornflowerblue' => '100,149,237',
49
+		'cornsilk' => '255,248,220',
50
+		'crimson' => '220,20,60',
51
+		'darkblue' => '0,0,139',
52
+		'darkcyan' => '0,139,139',
53
+		'darkgoldenrod' => '184,134,11',
54
+		'darkgray' => '169,169,169',
55
+		'darkgrey' => '169,169,169',
56
+		'darkgreen' => '0,100,0',
57
+		'darkkhaki' => '189,183,107',
58
+		'darkmagenta' => '139,0,139',
59
+		'darkolivegreen' => '85,107,47',
60
+		'darkorange' => '255,140,0',
61
+		'darkorchid' => '153,50,204',
62
+		'darkred' => '139,0,0',
63
+		'darksalmon' => '233,150,122',
64
+		'darkseagreen' => '143,188,143',
65
+		'darkslateblue' => '72,61,139',
66
+		'darkslategray' => '47,79,79',
67
+		'darkslategrey' => '47,79,79',
68
+		'darkturquoise' => '0,206,209',
69
+		'darkviolet' => '148,0,211',
70
+		'deeppink' => '255,20,147',
71
+		'deepskyblue' => '0,191,255',
72
+		'dimgray' => '105,105,105',
73
+		'dimgrey' => '105,105,105',
74
+		'dodgerblue' => '30,144,255',
75
+		'firebrick' => '178,34,34',
76
+		'floralwhite' => '255,250,240',
77
+		'forestgreen' => '34,139,34',
78
+		'magenta' => '255,0,255',
79
+		'fuchsia' => '255,0,255',
80
+		'gainsboro' => '220,220,220',
81
+		'ghostwhite' => '248,248,255',
82
+		'gold' => '255,215,0',
83
+		'goldenrod' => '218,165,32',
84
+		'gray' => '128,128,128',
85
+		'grey' => '128,128,128',
86
+		'green' => '0,128,0',
87
+		'greenyellow' => '173,255,47',
88
+		'honeydew' => '240,255,240',
89
+		'hotpink' => '255,105,180',
90
+		'indianred' => '205,92,92',
91
+		'indigo' => '75,0,130',
92
+		'ivory' => '255,255,240',
93
+		'khaki' => '240,230,140',
94
+		'lavender' => '230,230,250',
95
+		'lavenderblush' => '255,240,245',
96
+		'lawngreen' => '124,252,0',
97
+		'lemonchiffon' => '255,250,205',
98
+		'lightblue' => '173,216,230',
99
+		'lightcoral' => '240,128,128',
100
+		'lightcyan' => '224,255,255',
101
+		'lightgoldenrodyellow' => '250,250,210',
102
+		'lightgray' => '211,211,211',
103
+		'lightgrey' => '211,211,211',
104
+		'lightgreen' => '144,238,144',
105
+		'lightpink' => '255,182,193',
106
+		'lightsalmon' => '255,160,122',
107
+		'lightseagreen' => '32,178,170',
108
+		'lightskyblue' => '135,206,250',
109
+		'lightslategray' => '119,136,153',
110
+		'lightslategrey' => '119,136,153',
111
+		'lightsteelblue' => '176,196,222',
112
+		'lightyellow' => '255,255,224',
113
+		'lime' => '0,255,0',
114
+		'limegreen' => '50,205,50',
115
+		'linen' => '250,240,230',
116
+		'maroon' => '128,0,0',
117
+		'mediumaquamarine' => '102,205,170',
118
+		'mediumblue' => '0,0,205',
119
+		'mediumorchid' => '186,85,211',
120
+		'mediumpurple' => '147,112,219',
121
+		'mediumseagreen' => '60,179,113',
122
+		'mediumslateblue' => '123,104,238',
123
+		'mediumspringgreen' => '0,250,154',
124
+		'mediumturquoise' => '72,209,204',
125
+		'mediumvioletred' => '199,21,133',
126
+		'midnightblue' => '25,25,112',
127
+		'mintcream' => '245,255,250',
128
+		'mistyrose' => '255,228,225',
129
+		'moccasin' => '255,228,181',
130
+		'navajowhite' => '255,222,173',
131
+		'navy' => '0,0,128',
132
+		'oldlace' => '253,245,230',
133
+		'olive' => '128,128,0',
134
+		'olivedrab' => '107,142,35',
135
+		'orange' => '255,165,0',
136
+		'orangered' => '255,69,0',
137
+		'orchid' => '218,112,214',
138
+		'palegoldenrod' => '238,232,170',
139
+		'palegreen' => '152,251,152',
140
+		'paleturquoise' => '175,238,238',
141
+		'palevioletred' => '219,112,147',
142
+		'papayawhip' => '255,239,213',
143
+		'peachpuff' => '255,218,185',
144
+		'peru' => '205,133,63',
145
+		'pink' => '255,192,203',
146
+		'plum' => '221,160,221',
147
+		'powderblue' => '176,224,230',
148
+		'purple' => '128,0,128',
149
+		'red' => '255,0,0',
150
+		'rosybrown' => '188,143,143',
151
+		'royalblue' => '65,105,225',
152
+		'saddlebrown' => '139,69,19',
153
+		'salmon' => '250,128,114',
154
+		'sandybrown' => '244,164,96',
155
+		'seagreen' => '46,139,87',
156
+		'seashell' => '255,245,238',
157
+		'sienna' => '160,82,45',
158
+		'silver' => '192,192,192',
159
+		'skyblue' => '135,206,235',
160
+		'slateblue' => '106,90,205',
161
+		'slategray' => '112,128,144',
162
+		'slategrey' => '112,128,144',
163
+		'snow' => '255,250,250',
164
+		'springgreen' => '0,255,127',
165
+		'steelblue' => '70,130,180',
166
+		'tan' => '210,180,140',
167
+		'teal' => '0,128,128',
168
+		'thistle' => '216,191,216',
169
+		'tomato' => '255,99,71',
170
+		'turquoise' => '64,224,208',
171
+		'violet' => '238,130,238',
172
+		'wheat' => '245,222,179',
173
+		'white' => '255,255,255',
174
+		'whitesmoke' => '245,245,245',
175
+		'yellow' => '255,255,0',
176
+		'yellowgreen' => '154,205,50',
177
+		'rebeccapurple' => '102,51,153',
178
+		'transparent' => '0,0,0,0',
179
+	];
180 180
 
181
-    /**
182
-     * Convert named color in a [r,g,b[,a]] array
183
-     *
184
-     * @param string $colorName
185
-     *
186
-     * @return array|null
187
-     */
188
-    public static function colorNameToRGBa($colorName)
189
-    {
190
-        if (\is_string($colorName) && isset(static::$cssColors[$colorName])) {
191
-            $rgba = explode(',', static::$cssColors[$colorName]);
181
+	/**
182
+	 * Convert named color in a [r,g,b[,a]] array
183
+	 *
184
+	 * @param string $colorName
185
+	 *
186
+	 * @return array|null
187
+	 */
188
+	public static function colorNameToRGBa($colorName)
189
+	{
190
+		if (\is_string($colorName) && isset(static::$cssColors[$colorName])) {
191
+			$rgba = explode(',', static::$cssColors[$colorName]);
192 192
 
193
-            // only case with opacity is transparent, with opacity=0, so we can intval on opacity also
194
-            $rgba = array_map('intval', $rgba);
193
+			// only case with opacity is transparent, with opacity=0, so we can intval on opacity also
194
+			$rgba = array_map('intval', $rgba);
195 195
 
196
-            return $rgba;
197
-        }
196
+			return $rgba;
197
+		}
198 198
 
199
-        return null;
200
-    }
199
+		return null;
200
+	}
201 201
 
202
-    /**
203
-     * Reverse conversion : from RGBA to a color name if possible
204
-     *
205
-     * @param integer $r
206
-     * @param integer $g
207
-     * @param integer $b
208
-     * @param integer $a
209
-     *
210
-     * @return string|null
211
-     */
212
-    public static function RGBaToColorName($r, $g, $b, $a = 1)
213
-    {
214
-        static $reverseColorTable = null;
202
+	/**
203
+	 * Reverse conversion : from RGBA to a color name if possible
204
+	 *
205
+	 * @param integer $r
206
+	 * @param integer $g
207
+	 * @param integer $b
208
+	 * @param integer $a
209
+	 *
210
+	 * @return string|null
211
+	 */
212
+	public static function RGBaToColorName($r, $g, $b, $a = 1)
213
+	{
214
+		static $reverseColorTable = null;
215 215
 
216
-        if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b) || ! is_numeric($a)) {
217
-            return null;
218
-        }
216
+		if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b) || ! is_numeric($a)) {
217
+			return null;
218
+		}
219 219
 
220
-        if ($a < 1) {
221
-            return null;
222
-        }
220
+		if ($a < 1) {
221
+			return null;
222
+		}
223 223
 
224
-        if (\is_null($reverseColorTable)) {
225
-            $reverseColorTable = [];
224
+		if (\is_null($reverseColorTable)) {
225
+			$reverseColorTable = [];
226 226
 
227
-            foreach (static::$cssColors as $name => $rgb_str) {
228
-                $rgb_str = explode(',', $rgb_str);
227
+			foreach (static::$cssColors as $name => $rgb_str) {
228
+				$rgb_str = explode(',', $rgb_str);
229 229
 
230
-                if (
231
-                    \count($rgb_str) == 3 &&
232
-                    ! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
233
-                ) {
234
-                    $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
235
-                }
236
-            }
237
-        }
230
+				if (
231
+					\count($rgb_str) == 3 &&
232
+					! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
233
+				) {
234
+					$reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
235
+				}
236
+			}
237
+		}
238 238
 
239
-        if (isset($reverseColorTable[\intval($r)][\intval($g)][\intval($b)])) {
240
-            return $reverseColorTable[\intval($r)][\intval($g)][\intval($b)];
241
-        }
239
+		if (isset($reverseColorTable[\intval($r)][\intval($g)][\intval($b)])) {
240
+			return $reverseColorTable[\intval($r)][\intval($g)][\intval($b)];
241
+		}
242 242
 
243
-        return null;
244
-    }
243
+		return null;
244
+	}
245 245
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -213,7 +213,7 @@  discard block
 block discarded – undo
213 213
     {
214 214
         static $reverseColorTable = null;
215 215
 
216
-        if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b) || ! is_numeric($a)) {
216
+        if (!is_numeric($r) || !is_numeric($g) || !is_numeric($b) || !is_numeric($a)) {
217 217
             return null;
218 218
         }
219 219
 
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 
230 230
                 if (
231 231
                     \count($rgb_str) == 3 &&
232
-                    ! isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
232
+                    !isset($reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])])
233 233
                 ) {
234 234
                     $reverseColorTable[\intval($rgb_str[0])][\intval($rgb_str[1])][\intval($rgb_str[2])] = $name;
235 235
                 }
Please login to merge, or discard this patch.