81 static public $true = array(
"keyword",
"true");
82 static public $false = array(
"keyword",
"false");
83 static public $null = array(
"null");
108 $this->indentLevel = -1;
109 $this->commentsSeen = array();
110 $this->extends = array();
111 $this->extendsMap = array();
112 $this->parsedFiles = array();
116 $locale = setlocale(LC_NUMERIC, 0);
117 setlocale(LC_NUMERIC,
"C");
121 $tree = $this->parser->parse($code);
123 $this->formatter =
new $this->formatter();
130 $out = $this->formatter->format($this->scope);
132 setlocale(LC_NUMERIC, $locale);
138 foreach ($origin as $sel) {
139 if (in_array($target, $sel)) {
152 $i = count($this->extends);
153 $this->extends[] = array($target, $origin);
155 foreach ($target as $part) {
156 if (isset($this->extendsMap[$part])) {
157 $this->extendsMap[$part][] = $i;
159 $this->extendsMap[$part] = array($i);
167 $out->lines = array();
168 $out->children = array();
169 $out->parent = $this->scope;
170 $out->selectors = $selectors;
171 $out->depth = $this->env->depth;
178 foreach ($single as $part) {
179 if (!is_string($part))
return false;
181 if (isset($this->extendsMap[$part])) {
182 foreach ($this->extendsMap[$part] as $idx) {
184 isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
189 $outOrigin = array();
192 foreach ($counts as $idx => $count) {
193 list($target, $origin) = $this->extends[$idx];
196 if ($count != count($target))
continue;
199 if (array_diff(array_intersect($single, $target), $target))
continue;
201 $rem = array_diff($single, $target);
203 foreach ($origin as $j => $new) {
205 foreach ($new as $new_selector) {
206 if (!array_diff($single, $new_selector)) {
214 $outOrigin = array_merge($outOrigin, $origin);
226 foreach (array($base, $other) as $single) {
227 foreach ($single as $part) {
228 if (preg_match(
'/^[^\[.#:]/', $part)) {
237 array_unshift($out, $tag);
243 protected function matchExtends($selector, &$out, $from = 0, $initial=
true) {
244 foreach ($selector as $i => $part) {
245 if ($i < $from)
continue;
248 $before = array_slice($selector, 0, $i);
249 $after = array_slice($selector, $i + 1);
251 foreach ($origin as $new) {
256 foreach ($before as $k => $val) {
257 if (!isset($new[$k]) || $val != $new[$k]) {
263 $result = array_merge(
265 $k > 0 ? array_slice($new, $k) : $new,
269 if ($result == $selector)
continue;
276 if (!empty($before) && count($new) > 1) {
277 $result2 = array_merge(
278 array_slice($new, 0, -1),
279 $k > 0 ? array_slice($before, $k) : $before,
280 array_slice($new, -1),
291 if ($block->selectors) {
292 $selectors = array();
293 foreach ($block->selectors as $s) {
295 if (!is_array($s))
continue;
297 if (!empty($this->extendsMap)) {
302 $block->selectors = array();
303 $placeholderSelector =
false;
304 foreach ($selectors as $selector) {
306 $placeholderSelector =
true;
312 if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) {
313 unset($block->parent->children[$parentKey]);
318 foreach ($block->children as $key => $child) {
336 if (!empty($mediaQuery)) {
342 $parentScope->children[] = $this->scope;
346 foreach ($media->children as $child) {
348 if ($type !==
'block' && $type !==
'media' && $type !==
'directive') {
355 $wrapped = (object)array(
356 "selectors" => array(),
357 "children" => $media->children
359 $media->children = array(array(
"block", $wrapped));
364 $this->scope = $this->scope->parent;
371 while (!empty($scope->parent)) {
372 if (!empty($scope->type) && $scope->type !=
"media") {
375 $scope = $scope->parent;
386 $this->scope->parent->children[] = $this->scope;
389 $this->scope = $this->scope->parent;
415 array_map(array($this,
"evalSelector"), $block->selectors);
418 $this->scope->children[] = $out;
427 foreach ($single as $part) {
428 if (empty($joined) ||
430 preg_match(
'/[\[.:#%]/', $part))
436 if (is_array(end($joined))) {
439 $joined[count($joined) - 1] .= $part;
448 return array_map(array($this,
"evalSelectorPart"), $selector);
452 foreach ($piece as &$p) {
453 if (!is_array($p))
continue;
471 if (!is_array($selector))
return $selector;
473 return implode(
" ", array_map(
474 array($this,
"compileSelectorPart"), $selector));
478 foreach ($piece as &$p) {
479 if (!is_array($p))
continue;
491 return implode($piece);
496 if (!is_array($selector))
return false;
498 foreach ($selector as $parts) {
499 foreach ($parts as $part) {
500 if (
'%' == $part[0]) {
510 foreach ($stms as $stm) {
512 if (isset($ret))
return $ret;
519 foreach ($queryList as $query){
522 foreach ($query as $q) {
526 $type = $this->
mergeMediaTypes($type, array_map(array($this,
"compileValue"), array_slice($q, 1)));
531 $type = array_map(array($this,
"compileValue"), array_slice($q, 1));
544 array_unshift($parts, implode(
' ', array_filter($type)));
546 if (!empty($parts)) {
551 $out .= $this->formatter->tagSeparator;
553 $out .= implode(
" and ", $parts);
568 if (count($type1) > 1) {
569 $m1= strtolower($type1[0]);
570 $t1= strtolower($type1[1]);
572 $t1 = strtolower($type1[0]);
576 if (count($type2) > 1) {
577 $m2 = strtolower($type2[0]);
578 $t2 = strtolower($type2[1]);
580 $t2 = strtolower($type2[0]);
582 if (($m1 ==
'not') ^ ($m2 ==
'not')) {
587 $m1 ==
'not' ? $m2 : $m1,
588 $m1 ==
'not' ? $t2 : $t1
590 } elseif ($m1 ==
'not' && $m2 ==
'not') {
591 # CSS has no way of representing "neither screen nor print" 595 return array(
'not', $t1);
596 } elseif ($t1 != $t2) {
599 return array(empty($m1)? $m2 : $m1, $t1);
605 if ($rawPath[0] ==
"string") {
613 if ($rawPath[0] ==
"list") {
615 if (count($rawPath[2]) == 0)
return false;
616 foreach ($rawPath[2] as $path) {
617 if ($path[0] !=
"string")
return false;
620 foreach ($rawPath[2] as $path) {
632 $this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
633 $this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
637 list(,$rawPath) = $child;
638 $rawPath = $this->
reduce($rawPath);
640 $out->lines[] =
"@import " . $this->
compileValue($rawPath) .
";";
644 list(, $directive) = $child;
645 $s =
"@" . $directive->name;
646 if (!empty($directive->value)) {
658 $out->lines[] =
"@charset ".$this->compileValue($child[1]).
";";
661 list(,$name, $value) = $child;
662 if ($name[0] ==
"var") {
663 $isDefault = !empty($child[3]);
666 $existingValue = $this->
get($name[1],
true);
667 $shouldSet = $existingValue ===
true || $existingValue == self::$null;
670 if (!$isDefault || $shouldSet) {
671 $this->
set($name[1], $this->
reduce($value));
678 if ($value[0] !=
"null") {
679 $value = $this->
reduce($value);
680 if ($value[0] ==
"null") {
686 $out->lines[] = $this->formatter->property(
691 $out->lines[] = $child[1];
695 list(,$block) = $child;
696 $this->
set(self::$namespaces[$block->type] . $block->name, $block);
699 list(, $selectors) = $child;
700 foreach ($selectors as $sel) {
707 list(, $if) = $child;
711 foreach ($if->cases as $case) {
712 if ($case->type ==
"else" ||
713 $case->type ==
"elseif" && $this->isTruthy($this->reduce($case->cond)))
721 return $this->
reduce($child[1],
true);
723 list(,$each) = $child;
725 foreach ($list[2] as $item) {
727 $this->
set($each->var, $item);
734 list(,$while) = $child;
737 if ($ret)
return $ret;
741 list(,$for) = $child;
742 $start = $this->
reduce($for->start,
true);
744 $end = $this->
reduce($for->end,
true);
746 $d = $start < $end ? 1 : -1;
749 if ((!$for->until && $start - $d == $end) ||
750 ($for->until && $start == $end))
755 $this->
set($for->var, array(
"number", $start,
""));
759 if ($ret)
return $ret;
764 list(,$prop) = $child;
767 foreach ($prop->children as $child) {
768 if ($child[0] ==
"assign") {
769 array_unshift($child[1][2], $prefix);
771 if ($child[0] ==
"nestedprop") {
772 array_unshift($child[1]->prefix[2], $prefix);
774 $prefixed[] = $child;
779 list(,$name, $argValues, $content) = $child;
780 $mixin = $this->
get(self::$namespaces[
"mixin"] . $name,
false);
785 $callingScope = $this->env;
789 if ($this->env->depth > 0) {
793 if (isset($content)) {
794 $content->scope = $callingScope;
795 $this->
setRaw(self::$namespaces[
"special"] .
"content", $content);
798 if (isset($mixin->args)) {
802 foreach ($mixin->children as $child) {
809 case "mixin_content":
810 $content = $this->
get(self::$namespaces[
"special"] .
"content");
811 if (!isset($content)) {
812 $this->
throwError(
"Expected @content inside of mixin");
815 $strongTypes = array(
'include',
'block',
'for',
'while');
816 foreach ($content->children as $child) {
817 $this->storeEnv = (in_array($child[0], $strongTypes))
821 $this->compileChild($child, $out);
824 unset($this->storeEnv);
827 list(,$value, $pos) = $child;
828 $line = $this->parser->getLineNo($pos);
830 fwrite(STDERR,
"Line $line DEBUG: $value\n");
833 $this->
throwError(
"unknown child type: $child[0]");
838 list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
839 $content = array($this->
reduce($left));
840 if ($whiteLeft) $content[] =
" ";
842 if ($whiteRight) $content[] =
" ";
843 $content[] = $this->
reduce($right);
844 return array(
"string",
"", $content);
848 return $value != self::$false && $value != self::$null;
855 if ($value[1] ==
"/") {
856 return $this->
shouldEval($value[2], $value[3]);
865 protected function reduce($value, $inExp =
false) {
866 list($type) = $value;
869 list(, $op, $left, $right, $inParens) = $value;
870 $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
874 $left = $this->
reduce($left,
true);
875 $right = $this->
reduce($right,
true);
878 if ($opName ==
"div" && !$inParens && !$inExp) {
879 if ($left[0] !=
"color" && $right[0] !=
"color") {
894 $fn =
"op_${opName}_${ltype}_${rtype}";
895 if (is_callable(array($this, $fn)) ||
896 (($fn =
"op_${ltype}_${rtype}") &&
897 is_callable(array($this, $fn)) &&
899 (($fn =
"op_${opName}") &&
900 is_callable(array($this, $fn)) &&
904 if (!isset($genOp) &&
905 $left[0] ==
"number" && $right[0] ==
"number")
907 if ($opName ==
"mod" && $right[2] !=
"") {
908 $this->
throwError(
"Cannot modulo by a number with units: $right[1]$right[2].");
912 $emptyUnit = $left[2] ==
"" || $right[2] ==
"";
913 $targetUnit =
"" != $left[2] ? $left[2] : $right[2];
915 if ($opName !=
"mul") {
916 $left[2] =
"" != $left[2] ? $left[2] : $targetUnit;
917 $right[2] =
"" != $right[2] ? $right[2] : $targetUnit;
920 if ($opName !=
"mod") {
925 if ($opName ==
"div" && !$emptyUnit && $left[2] == $right[2]) {
929 if ($opName ==
"mul") {
930 $left[2] =
"" != $left[2] ? $left[2] : $right[2];
931 $right[2] =
"" != $right[2] ? $right[2] : $left[2];
932 } elseif ($opName ==
"div" && $left[2] == $right[2]) {
938 $shouldEval = $inParens || $inExp;
939 if (isset($passOp)) {
940 $out = $this->$fn($op, $left, $right, $shouldEval);
942 $out = $this->$fn($left, $right, $shouldEval);
946 if ($unitChange && $out[0] ==
"number") {
955 list(, $op, $exp, $inParens) = $value;
958 $exp = $this->
reduce($exp);
959 if ($exp[0] ==
"number") {
970 if ($inExp || $inParens) {
971 if ($exp == self::$false) {
981 return array(
"string",
"", array($op, $exp));
983 list(, $name) = $value;
984 return $this->
reduce($this->
get($name));
986 foreach ($value[2] as &$item) {
987 $item = $this->
reduce($item);
991 foreach ($value[2] as &$item) {
992 if (is_array($item)) {
993 $item = $this->
reduce($item);
998 $value[1] = $this->
reduce($value[1]);
1001 list(,$name, $argValues) = $value;
1004 $func = $this->
get(self::$namespaces[
"function"] . $name,
false);
1009 if (isset($func->args)) {
1014 $tmp = (object)array(
1016 "children" => array()
1021 return !isset($ret) ? self::$defaultValue : $ret;
1025 if ($this->
callBuiltin($name, $argValues, $returnValue)) {
1026 return $returnValue;
1030 $listArgs = array();
1031 foreach ((array)$argValues as $arg) {
1032 if (empty($arg[0])) {
1033 $listArgs[] = $this->
reduce($arg[1]);
1036 return array(
"function", $name, array(
"list",
",", $listArgs));
1044 list($type) = $value;
1049 if ($value[0] !=
"list") {
1052 foreach ($value[2] as $key => $item) {
1065 list(, $value, $unit) = $number;
1066 if (isset(self::$unitTable[
"in"][$unit])) {
1067 $conv = self::$unitTable[
"in"][$unit];
1068 return array(
"number", $value / $conv,
"in");
1075 list(, $value, $baseUnit) = $number;
1076 if (isset(self::$unitTable[$baseUnit][$unit])) {
1077 $value = $value * self::$unitTable[$baseUnit][$unit];
1080 return array(
"number", $value, $unit);
1084 return array(
"number", $left[1] + $right[1], $left[2]);
1088 return array(
"number", $left[1] * $right[1], $left[2]);
1092 return array(
"number", $left[1] - $right[1], $left[2]);
1096 return array(
"number", $left[1] / $right[1], $left[2]);
1100 return array(
"number", $left[1] % $right[1], $left[2]);
1106 if ($right[0] ==
"string") {
1109 $strLeft[2][] = $right;
1114 if ($left[0] ==
"string") {
1117 array_unshift($strRight[2], $left);
1122 protected function op_and($left, $right, $shouldEval) {
1123 if (!$shouldEval)
return;
1124 if ($left != self::$false)
return $right;
1128 protected function op_or($left, $right, $shouldEval) {
1129 if (!$shouldEval)
return;
1130 if ($left != self::$false)
return $left;
1135 $out = array(
'color');
1136 foreach (range(1, 3) as $i) {
1137 $lval = isset($left[$i]) ? $left[$i] : 0;
1138 $rval = isset($right[$i]) ? $right[$i] : 0;
1141 $out[] = $lval + $rval;
1144 $out[] = $lval - $rval;
1147 $out[] = $lval * $rval;
1150 $out[] = $lval % $rval;
1154 $this->
throwError(
"color: Can't divide by zero");
1156 $out[] = $lval / $rval;
1159 return $this->
op_eq($left, $right);
1161 return $this->
op_neq($left, $right);
1167 if (isset($left[4])) $out[4] = $left[4];
1168 elseif (isset($right[4])) $out[4] = $right[4];
1176 array(
"color", $value, $value, $value));
1182 array(
"color", $value, $value, $value), $right);
1185 protected function op_eq($left, $right) {
1192 return $this->
toBool($left == $right);
1196 return $this->
toBool($left != $right);
1200 return $this->
toBool($left[1] >= $right[1]);
1204 return $this->
toBool($left[1] > $right[1]);
1208 return $this->
toBool($left[1] <= $right[1]);
1212 return $this->
toBool($left[1] < $right[1]);
1216 return $thing ? self::$true : self::$false;
1233 $value = $this->
reduce($value);
1235 list($type) = $value;
1244 list(, $r, $g, $b) = $value;
1250 if (count($value) == 5 && $value[4] != 1) {
1251 return 'rgba('.$r.
', '.$g.
', '.$b.
', '.$value[4].
')';
1254 $h = sprintf(
"#%02x%02x%02x", $r, $g, $b);
1257 if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
1258 $h =
'#' . $h[1] . $h[3] . $h[5];
1263 return round($value[1], $this->numberPrecision) . $value[2];
1267 $args = !empty($value[2]) ? $this->
compileValue($value[2]) :
"";
1268 return "$value[1]($args)";
1271 if ($value[0] !=
"list")
return $this->
compileValue($value);
1273 list(, $delim, $items) = $value;
1275 $filtered = array();
1276 foreach ($items as $item) {
1277 if ($item[0] ==
"null")
continue;
1281 return implode(
"$delim ", $filtered);
1283 list(, $interpolate, $left, $right) = $value;
1284 list(,, $whiteLeft, $whiteRight) = $interpolate;
1286 $left = count($left[2]) > 0 ?
1289 $right = count($right[2]) > 0 ?
1290 $whiteRight.$this->compileValue($right) :
"";
1292 return $left.$this->compileValue($interpolate).$right;
1294 case "interpolate": # raw parse node
1295 list(, $exp) = $value;
1298 $reduced = $this->
reduce($exp);
1299 switch ($reduced[0]) {
1301 $reduced = array(
"keyword",
1305 $reduced = array(
"keyword",
"");
1312 $this->
throwError(
"unknown value type: $type");
1318 foreach ($string[2] as $part) {
1319 if (is_array($part)) {
1326 return implode($parts);
1332 foreach ($items as $i => $item) {
1333 if ($item[0] ==
"interpolate") {
1334 $before = array(
"list", $list[1], array_slice($items, 0, $i));
1335 $after = array(
"list", $list[1], array_slice($items, $i + 1));
1336 return array(
"interpolated", $item, $before, $after);
1345 while (null !== $env) {
1346 if (!empty($env->selectors)) {
1349 $env = $env->parent;
1352 $selectors = array();
1353 $parentSelectors = array(array());
1354 while ($env = array_pop($envs)) {
1355 $selectors = array();
1356 foreach ($env->selectors as $selector) {
1357 foreach ($parentSelectors as $parent) {
1361 $parentSelectors = $selectors;
1371 foreach ($child as $part) {
1373 foreach ($part as $p) {
1374 if ($p == self::$selfSelector) {
1376 foreach ($parent as $i => $parentPart) {
1382 foreach ($parentPart as $pp) {
1394 return $setSelf ? $out : array_merge($parent, $child);
1399 !empty($env->block->type) && $env->block->type !=
"media")
1401 return $childQueries;
1405 if (empty($env->block->type)) {
1409 $parentQueries = $env->block->queryList;
1410 if ($childQueries == null) {
1411 $childQueries = $parentQueries;
1413 $originalQueries = $childQueries;
1414 $childQueries = array();
1416 foreach ($parentQueries as $parentQuery){
1417 foreach ($originalQueries as $childQuery) {
1418 $childQueries []= array_merge($parentQuery, $childQuery);
1428 if (isset($item) && $item[0] ==
"list") {
1432 return array(
"list", $delim, !isset($item) ? array(): array($item));
1436 $hasVariable =
false;
1438 foreach ($argDef as $i => $arg) {
1439 list($name, $default, $isVariable) = $argDef[$i];
1440 $args[$name] = array($i, $name, $default, $isVariable);
1441 $hasVariable |= $isVariable;
1444 $keywordArgs = array();
1445 $deferredKeywordArgs = array();
1446 $remaining = array();
1448 foreach ((array) $argValues as $arg) {
1449 if (!empty($arg[0])) {
1450 if (!isset($args[$arg[0][1]])) {
1452 $deferredKeywordArgs[$arg[0][1]] = $arg[1];
1454 $this->
throwError(
"Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
1456 } elseif ($args[$arg[0][1]][0] < count($remaining)) {
1457 $this->
throwError(
"The argument $%s was passed both by position and by name.", $arg[0][1]);
1459 $keywordArgs[$arg[0][1]] = $arg[1];
1461 } elseif (count($keywordArgs)) {
1462 $this->
throwError(
'Positional arguments must come before keyword arguments.');
1463 } elseif ($arg[2] ==
true) {
1464 $val = $this->
reduce($arg[1],
true);
1465 if ($val[0] ==
"list") {
1466 foreach ($val[2] as $name => $item) {
1467 if (!is_numeric($name)) {
1468 $keywordArgs[$name] = $item;
1470 $remaining[] = $item;
1474 $remaining[] = $val;
1477 $remaining[] = $arg[1];
1481 foreach ($args as $arg) {
1482 list($i, $name, $default, $isVariable) = $arg;
1484 $val = array(
"list",
",", array());
1485 for ($count = count($remaining); $i < $count; $i++) {
1486 $val[2][] = $remaining[$i];
1488 foreach ($deferredKeywordArgs as $itemName => $item) {
1489 $val[2][$itemName] = $item;
1491 } elseif (isset($remaining[$i])) {
1492 $val = $remaining[$i];
1493 } elseif (isset($keywordArgs[$name])) {
1494 $val = $keywordArgs[$name];
1495 } elseif (!empty($default)) {
1501 $this->
set($name, $this->
reduce($val,
true),
true);
1506 $env =
new stdClass;
1507 $env->parent = $this->env;
1508 $env->store = array();
1509 $env->block = $block;
1510 $env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0;
1517 return str_replace(
"-",
"_", $name);
1521 return isset($this->storeEnv) ? $this->storeEnv : $this->env;
1524 protected function set($name, $value, $shadow=
false) {
1528 $this->
setRaw($name, $value);
1537 if (isset($env->store[$name]) || !isset($env->parent)) {
1538 $env->store[$name] = $value;
1546 $env->store[$name] = $value;
1553 if (!isset($defaultValue)) $defaultValue = self::$defaultValue;
1555 if (isset($env->store[$name])) {
1556 return $env->store[$name];
1557 } elseif (isset($env->parent)) {
1572 foreach ($args as $name => $strValue) {
1573 if ($name[0] ===
'$') {
1574 $name = substr($name, 1);
1577 $parser->env = null;
1579 $parser->buffer = (string) $strValue;
1580 $parser->inParens =
false;
1581 $parser->eatWhiteDefault =
true;
1582 $parser->insertComments =
true;
1584 if ( ! $parser->valueList($value)) {
1585 throw new Exception(
"failed to parse passed in variable $name: $strValue");
1588 $this->
set($name, $value);
1599 $this->registeredVars = array_merge($this->registeredVars, $variables);
1609 unset($this->registeredVars[$name]);
1614 $this->env = $this->env->parent;
1619 return $this->parsedFiles;
1623 $this->importPaths[] = $path;
1627 $this->importPaths = (array)$path;
1635 $this->formatter = $formatterName;
1648 $realPath = realpath($path);
1649 if (isset($this->importCache[$realPath])) {
1650 $tree = $this->importCache[$realPath];
1652 $code = file_get_contents($path);
1654 $tree = $parser->parse($code);
1655 $this->parsedFiles[] = $path;
1657 $this->importCache[$realPath] = $tree;
1660 $pi = pathinfo($path);
1661 array_unshift($this->importPaths, $pi[
'dirname']);
1663 array_shift($this->importPaths);
1671 if (!preg_match(
'/\.css|^http:\/\/$/', $url)) {
1673 $urls = array($url, preg_replace(
'/[^\/]+$/',
'_\0', $url));
1676 foreach ($this->importPaths as $dir) {
1677 if (is_string($dir)) {
1679 foreach ($urls as $full) {
1681 (!empty($dir) && substr($dir, -1) !=
'/' ?
'/' :
'') .
1684 if ($this->
fileExists($file = $full.
'.scss') ||
1692 $file = call_user_func($dir,$url,$this);
1693 if ($file !== null) {
1703 return is_file($name);
1709 $libName =
"lib_".$name;
1710 $f = array($this, $libName);
1711 if (is_callable($f)) {
1712 $prototype = isset(
self::$$libName) ?
self::$$libName : null;
1713 $sorted = $this->
sortArgs($prototype, $args);
1714 foreach ($sorted as &$val) {
1715 $val = $this->
reduce($val,
true);
1717 $returnValue = call_user_func($f, $sorted, $this);
1718 } elseif (isset($this->userFunctions[$name])) {
1720 $fn = $this->userFunctions[$name];
1722 foreach ($args as &$val) {
1723 $val = $this->
reduce($val[1],
true);
1726 $returnValue = call_user_func($fn, $args, $this);
1729 if (isset($returnValue)) {
1731 if (is_numeric($returnValue)) {
1732 $returnValue = array(
'number', $returnValue,
"");
1733 } elseif (is_bool($returnValue)) {
1734 $returnValue = $returnValue ? self::$true : self::$false;
1735 } elseif (!is_array($returnValue)) {
1736 $returnValue = array(
'keyword', $returnValue);
1751 foreach ($args as $arg) {
1752 list($key, $value) = $arg;
1755 $posArgs[] = $value;
1757 $keyArgs[$key] = $value;
1761 if (!isset($prototype))
return $posArgs;
1763 $finalArgs = array();
1764 foreach ($prototype as $i => $names) {
1765 if (isset($posArgs[$i])) {
1766 $finalArgs[] = $posArgs[$i];
1771 foreach ((array)$names as $name) {
1772 if (isset($keyArgs[$name])) {
1773 $finalArgs[] = $keyArgs[$name];
1780 $finalArgs[] = null;
1796 switch ($value[0]) {
1797 case "color":
return $value;
1800 if (isset(self::$cssColors[$name])) {
1801 $rgba = explode(
',', self::$cssColors[$name]);
1802 return isset($rgba[3])
1803 ? array(
'color', (
int) $rgba[0], (
int) $rgba[1], (
int) $rgba[2], (
int) $rgba[3])
1804 : array(
'color', (
int) $rgba[0], (
int) $rgba[1], (
int) $rgba[2]);
1813 switch ($value[0]) {
1817 return array(
"string",
"", array($value[1]));
1823 if ($value[0] !=
"list")
1829 if ($color = $this->
coerceColor($value))
return $color;
1834 if ($value[0] !=
"number")
1840 if ($value[0] ==
"number") {
1841 if ($value[2] ==
"%") {
1842 return $value[1] / 100;
1851 foreach (range(1, 3) as $i) {
1852 if ($c[$i] < 0) $c[$i] = 0;
1853 if ($c[$i] > 255) $c[$i] = 255;
1859 public function toHSL($red, $green, $blue) {
1860 $min = min($red, $green, $blue);
1861 $max = max($red, $green, $blue);
1873 $s = $d / (510 - $l);
1876 $h = 60 * ($green - $blue) / $d;
1877 elseif ($green == $max)
1878 $h = 60 * ($blue - $red) / $d + 120;
1879 elseif ($blue == $max)
1880 $h = 60 * ($red - $green) / $d + 240;
1883 return array(
'hsl', fmod($h, 360), $s * 100, $l / 5.1);
1893 return $m1 + ($m2 - $m1) * $h * 6;
1899 return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
1905 public function toRGB($hue, $saturation, $lightness) {
1911 $s = min(100, max(0, $saturation)) / 100;
1912 $l = min(100, max(0, $lightness)) / 100;
1914 $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
1917 $r = $this->
hueToRGB($m1, $m2, $h + 1/3) * 255;
1918 $g = $this->
hueToRGB($m1, $m2, $h) * 255;
1919 $b = $this->
hueToRGB($m1, $m2, $h - 1/3) * 255;
1921 $out = array(
'color', $r, $g, $b);
1927 protected static $lib_if = array(
"condition",
"if-true",
"if-false");
1929 list($cond,$t, $f) = $args;
1930 if (!$this->
isTruthy($cond))
return $f;
1936 list($list, $value) = $args;
1940 foreach ($list[2] as $item) {
1945 return false === $key ?
false : $key + 1;
1948 protected static $lib_rgb = array(
"red",
"green",
"blue");
1950 list($r,$g,$b) = $args;
1951 return array(
"color", $r[1], $g[1], $b[1]);
1955 array(
"red",
"color"),
1956 "green",
"blue",
"alpha");
1959 $num = !isset($args[1]) ? $args[3] : $args[1];
1965 list($r,$g,$b, $a) = $args;
1966 return array(
"color", $r[1], $g[1], $b[1], $a[1]);
1973 foreach (array(1,2,3,7) as $i) {
1974 if (isset($args[$i])) {
1976 $ii = $i == 7 ? 4 : $i;
1978 $this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
1982 if (isset($args[4]) || isset($args[5]) || isset($args[6])) {
1983 $hsl = $this->
toHSL($color[1], $color[2], $color[3]);
1984 foreach (array(4,5,6) as $i) {
1985 if (isset($args[$i])) {
1987 $hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
1991 $rgb = $this->
toRGB($hsl[1], $hsl[2], $hsl[3]);
1992 if (isset($color[4])) $rgb[4] = $color[4];
2000 "color",
"red",
"green",
"blue",
2001 "hue",
"saturation",
"lightness",
"alpha" 2004 return $base += $alter;
2007 return $this->
alter_color($args,
"adjust_color_helper");
2011 "color",
"red",
"green",
"blue",
2012 "hue",
"saturation",
"lightness",
"alpha" 2018 return $this->
alter_color($args,
"change_color_helper");
2022 "color",
"red",
"green",
"blue",
2023 "hue",
"saturation",
"lightness",
"alpha" 2042 $scale = $scale / 100;
2044 return $base * $scale + $base;
2046 return ($max - $base) * $scale + $base;
2050 return $this->
alter_color($args,
"scale_color_helper");
2056 $color[4] = isset($color[4]) ? round(255*$color[4]) : 255;
2058 return sprintf(
'#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
2082 return isset($color[4]) ? $color[4] : 1;
2092 if ($value[0] ===
'number')
return null;
2097 protected static $lib_mix = array(
"color-1",
"color-2",
"weight");
2099 list($first, $second, $weight) = $args;
2103 if (!isset($weight)) {
2109 $firstAlpha = isset($first[4]) ? $first[4] : 1;
2110 $secondAlpha = isset($second[4]) ? $second[4] : 1;
2112 $w = $weight * 2 - 1;
2113 $a = $firstAlpha - $secondAlpha;
2115 $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
2118 $new = array(
'color',
2119 $w1 * $first[1] + $w2 * $second[1],
2120 $w1 * $first[2] + $w2 * $second[2],
2121 $w1 * $first[3] + $w2 * $second[3],
2124 if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
2125 $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
2131 protected static $lib_hsl = array(
"hue",
"saturation",
"lightness");
2133 list($h, $s, $l) = $args;
2134 return $this->
toRGB($h[1], $s[1], $l[1]);
2138 "lightness",
"alpha");
2140 list($h, $s, $l, $a) = $args;
2141 $color = $this->
toRGB($h[1], $s[1], $l[1]);
2149 $hsl = $this->
toHSL($color[1], $color[2], $color[3]);
2150 return array(
"number", $hsl[1],
"deg");
2156 $hsl = $this->
toHSL($color[1], $color[2], $color[3]);
2157 return array(
"number", $hsl[2],
"%");
2163 $hsl = $this->
toHSL($color[1], $color[2], $color[3]);
2164 return array(
"number", $hsl[3],
"%");
2168 $hsl = $this->
toHSL($color[1], $color[2], $color[3]);
2169 $hsl[$idx] += $amount;
2170 $out = $this->
toRGB($hsl[1], $hsl[2], $hsl[3]);
2171 if (isset($color[4])) $out[4] = $color[4];
2179 return $this->
adjustHsl($color, 1, $degrees);
2186 return $this->
adjustHsl($color, 3, $amount);
2193 return $this->
adjustHsl($color, 3, -$amount);
2199 if ($value[0] ===
'number')
return null;
2202 return $this->
adjustHsl($color, 2, $amount);
2209 return $this->
adjustHsl($color, 2, -$amount);
2215 if ($value[0] ===
'number')
return null;
2227 if ($value[0] ===
'number')
return null;
2229 $color[1] = 255 - $color[1];
2230 $color[2] = 255 - $color[2];
2231 $color[3] = 255 - $color[3];
2241 $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
2242 $color[4] = min(1, max(0, $color[4]));
2257 $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
2258 $color[4] = min(1, max(0, $color[4]));
2270 if ($str[0] ==
"string") $str[1] =
"";
2277 if ($value[0] ==
"string" && !empty($value[1]))
2279 return array(
"string",
'"', array($value));
2284 return array(
"number",
2292 $num[1] = round($num[1]);
2299 $num[1] = floor($num[1]);
2306 $num[1] = ceil($num[1]);
2313 $num[1] = abs($num[1]);
2320 foreach ($numbers as $key => $number) {
2321 if (null === $min || $number[1] <= $min[1]) {
2322 $min = array($key, $number[1]);
2326 return $args[$min[0]];
2332 foreach ($numbers as $key => $number) {
2333 if (null === $max || $number[1] >= $max[1]) {
2334 $max = array($key, $number[1]);
2338 return $args[$max[0]];
2343 $originalUnit = null;
2345 foreach ($args as $key => $item) {
2346 if (
'number' != $item[0]) {
2347 $this->
throwError(
"%s is not a number", $item[0]);
2351 if (null === $unit) {
2353 $originalUnit = $item[2];
2354 } elseif ($unit !== $number[2]) {
2355 $this->
throwError(
'Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
2358 $numbers[$key] = $number;
2367 return count($list[2]);
2374 return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
2378 if (!isset($sep))
return $list1[1];
2389 protected static $lib_join = array(
"list1",
"list2",
"separator");
2391 list($list1, $list2, $sep) = $args;
2395 return array(
"list", $sep, array_merge($list1[2], $list2[2]));
2400 list($list1, $value, $sep) = $args;
2403 return array(
"list", $sep, array_merge($list1[2], array($value)));
2407 foreach ($args as $arg) {
2412 $firstList = array_shift($args);
2413 foreach ($firstList[2] as $key => $item) {
2414 $list = array(
"list",
"", array($item));
2415 foreach ($args as $arg) {
2416 if (isset($arg[2][$key])) {
2417 $list[2][] = $arg[2][$key];
2425 return array(
"list",
",", $lists);
2431 switch ($value[0]) {
2433 if ($value == self::$true || $value == self::$false) {
2450 if ($num[0] ==
"number") {
2451 return array(
"string",
'"', array($num[2]));
2459 return $value[0] ==
"number" && empty($value[2]);
2464 list($number1, $number2) = $args;
2465 if (!isset($number1[0]) || $number1[0] !=
"number" || !isset($number2[0]) || $number2[0] !=
"number") {
2466 $this->
throwError(
'Invalid argument(s) for "comparable"');
2472 return $number1[2] == $number2[2] || $number1[2] ==
"" || $number2[2] ==
"";
2481 $list = array_map(array($this,
'compileValue'), $args);
2482 return array(
'string',
'', array(
'counter(' . implode(
',', $list) .
')'));
2486 if (func_num_args() > 1) {
2487 $msg = call_user_func_array(
"sprintf", func_get_args());
2490 if ($this->sourcePos >= 0 && isset($this->sourceParser)) {
2491 $this->sourceParser->throwParseError($msg, $this->sourcePos);
2494 throw new Exception($msg);
2503 'aliceblue' =>
'240,248,255',
2504 'antiquewhite' =>
'250,235,215',
2505 'aqua' =>
'0,255,255',
2506 'aquamarine' =>
'127,255,212',
2507 'azure' =>
'240,255,255',
2508 'beige' =>
'245,245,220',
2509 'bisque' =>
'255,228,196',
2511 'blanchedalmond' =>
'255,235,205',
2512 'blue' =>
'0,0,255',
2513 'blueviolet' =>
'138,43,226',
2514 'brown' =>
'165,42,42',
2515 'burlywood' =>
'222,184,135',
2516 'cadetblue' =>
'95,158,160',
2517 'chartreuse' =>
'127,255,0',
2518 'chocolate' =>
'210,105,30',
2519 'coral' =>
'255,127,80',
2520 'cornflowerblue' =>
'100,149,237',
2521 'cornsilk' =>
'255,248,220',
2522 'crimson' =>
'220,20,60',
2523 'cyan' =>
'0,255,255',
2524 'darkblue' =>
'0,0,139',
2525 'darkcyan' =>
'0,139,139',
2526 'darkgoldenrod' =>
'184,134,11',
2527 'darkgray' =>
'169,169,169',
2528 'darkgreen' =>
'0,100,0',
2529 'darkgrey' =>
'169,169,169',
2530 'darkkhaki' =>
'189,183,107',
2531 'darkmagenta' =>
'139,0,139',
2532 'darkolivegreen' =>
'85,107,47',
2533 'darkorange' =>
'255,140,0',
2534 'darkorchid' =>
'153,50,204',
2535 'darkred' =>
'139,0,0',
2536 'darksalmon' =>
'233,150,122',
2537 'darkseagreen' =>
'143,188,143',
2538 'darkslateblue' =>
'72,61,139',
2539 'darkslategray' =>
'47,79,79',
2540 'darkslategrey' =>
'47,79,79',
2541 'darkturquoise' =>
'0,206,209',
2542 'darkviolet' =>
'148,0,211',
2543 'deeppink' =>
'255,20,147',
2544 'deepskyblue' =>
'0,191,255',
2545 'dimgray' =>
'105,105,105',
2546 'dimgrey' =>
'105,105,105',
2547 'dodgerblue' =>
'30,144,255',
2548 'firebrick' =>
'178,34,34',
2549 'floralwhite' =>
'255,250,240',
2550 'forestgreen' =>
'34,139,34',
2551 'fuchsia' =>
'255,0,255',
2552 'gainsboro' =>
'220,220,220',
2553 'ghostwhite' =>
'248,248,255',
2554 'gold' =>
'255,215,0',
2555 'goldenrod' =>
'218,165,32',
2556 'gray' =>
'128,128,128',
2557 'green' =>
'0,128,0',
2558 'greenyellow' =>
'173,255,47',
2559 'grey' =>
'128,128,128',
2560 'honeydew' =>
'240,255,240',
2561 'hotpink' =>
'255,105,180',
2562 'indianred' =>
'205,92,92',
2563 'indigo' =>
'75,0,130',
2564 'ivory' =>
'255,255,240',
2565 'khaki' =>
'240,230,140',
2566 'lavender' =>
'230,230,250',
2567 'lavenderblush' =>
'255,240,245',
2568 'lawngreen' =>
'124,252,0',
2569 'lemonchiffon' =>
'255,250,205',
2570 'lightblue' =>
'173,216,230',
2571 'lightcoral' =>
'240,128,128',
2572 'lightcyan' =>
'224,255,255',
2573 'lightgoldenrodyellow' =>
'250,250,210',
2574 'lightgray' =>
'211,211,211',
2575 'lightgreen' =>
'144,238,144',
2576 'lightgrey' =>
'211,211,211',
2577 'lightpink' =>
'255,182,193',
2578 'lightsalmon' =>
'255,160,122',
2579 'lightseagreen' =>
'32,178,170',
2580 'lightskyblue' =>
'135,206,250',
2581 'lightslategray' =>
'119,136,153',
2582 'lightslategrey' =>
'119,136,153',
2583 'lightsteelblue' =>
'176,196,222',
2584 'lightyellow' =>
'255,255,224',
2585 'lime' =>
'0,255,0',
2586 'limegreen' =>
'50,205,50',
2587 'linen' =>
'250,240,230',
2588 'magenta' =>
'255,0,255',
2589 'maroon' =>
'128,0,0',
2590 'mediumaquamarine' =>
'102,205,170',
2591 'mediumblue' =>
'0,0,205',
2592 'mediumorchid' =>
'186,85,211',
2593 'mediumpurple' =>
'147,112,219',
2594 'mediumseagreen' =>
'60,179,113',
2595 'mediumslateblue' =>
'123,104,238',
2596 'mediumspringgreen' =>
'0,250,154',
2597 'mediumturquoise' =>
'72,209,204',
2598 'mediumvioletred' =>
'199,21,133',
2599 'midnightblue' =>
'25,25,112',
2600 'mintcream' =>
'245,255,250',
2601 'mistyrose' =>
'255,228,225',
2602 'moccasin' =>
'255,228,181',
2603 'navajowhite' =>
'255,222,173',
2604 'navy' =>
'0,0,128',
2605 'oldlace' =>
'253,245,230',
2606 'olive' =>
'128,128,0',
2607 'olivedrab' =>
'107,142,35',
2608 'orange' =>
'255,165,0',
2609 'orangered' =>
'255,69,0',
2610 'orchid' =>
'218,112,214',
2611 'palegoldenrod' =>
'238,232,170',
2612 'palegreen' =>
'152,251,152',
2613 'paleturquoise' =>
'175,238,238',
2614 'palevioletred' =>
'219,112,147',
2615 'papayawhip' =>
'255,239,213',
2616 'peachpuff' =>
'255,218,185',
2617 'peru' =>
'205,133,63',
2618 'pink' =>
'255,192,203',
2619 'plum' =>
'221,160,221',
2620 'powderblue' =>
'176,224,230',
2621 'purple' =>
'128,0,128',
2623 'rosybrown' =>
'188,143,143',
2624 'royalblue' =>
'65,105,225',
2625 'saddlebrown' =>
'139,69,19',
2626 'salmon' =>
'250,128,114',
2627 'sandybrown' =>
'244,164,96',
2628 'seagreen' =>
'46,139,87',
2629 'seashell' =>
'255,245,238',
2630 'sienna' =>
'160,82,45',
2631 'silver' =>
'192,192,192',
2632 'skyblue' =>
'135,206,235',
2633 'slateblue' =>
'106,90,205',
2634 'slategray' =>
'112,128,144',
2635 'slategrey' =>
'112,128,144',
2636 'snow' =>
'255,250,250',
2637 'springgreen' =>
'0,255,127',
2638 'steelblue' =>
'70,130,180',
2639 'tan' =>
'210,180,140',
2640 'teal' =>
'0,128,128',
2641 'thistle' =>
'216,191,216',
2642 'tomato' =>
'255,99,71',
2643 'transparent' =>
'0,0,0,0',
2644 'turquoise' =>
'64,224,208',
2645 'violet' =>
'238,130,238',
2646 'wheat' =>
'245,222,179',
2647 'white' =>
'255,255,255',
2648 'whitesmoke' =>
'245,245,245',
2649 'yellow' =>
'255,255,0',
2650 'yellowgreen' =>
'154,205,50' 2660 static protected $precedence = array(
2679 static protected $operators = array(
"+",
"-",
"*",
"/",
"%",
2680 "==",
"!=",
"<=",
">=",
"<",
">",
"and",
"or");
2686 static protected $commentSingle =
"//";
2687 static protected $commentMultiLeft =
"/*";
2688 static protected $commentMultiRight =
"*/";
2697 $this->sourceName = $sourceName;
2698 $this->rootParser = $rootParser;
2700 if (empty(self::$operatorStr)) {
2701 self::$operatorStr = $this->makeOperatorStr(self::$operators);
2703 $commentSingle = $this->preg_quote(self::$commentSingle);
2704 $commentMultiLeft = $this->preg_quote(self::$commentMultiLeft);
2705 $commentMultiRight = $this->preg_quote(self::$commentMultiRight);
2706 self::$commentMulti = $commentMultiLeft.
'.*?'.$commentMultiRight;
2707 self::$whitePattern =
'/'.$commentSingle.
'[^\n]*\s*|('.self::$commentMulti.
')\s*|\s+/Ais';
2712 return '('.implode(
'|', array_map(array(
'scss_parser',
'preg_quote'),
2727 $this->inParens =
false;
2728 $this->eatWhiteDefault =
true;
2729 $this->insertComments =
true;
2730 $this->buffer = $buffer;
2732 $this->pushBlock(null);
2733 $this->whitespace();
2735 while (
false !== $this->parseChunk())
2738 if ($this->count != strlen($this->buffer)) {
2739 $this->throwParseError();
2742 if (!empty($this->env->parent)) {
2743 $this->throwParseError(
"unclosed block");
2746 $this->env->isRoot =
true;
2794 if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] ==
"@") {
2795 if ($this->literal(
"@media") && $this->mediaQueryList($mediaQueryList) && $this->literal(
"{")) {
2796 $media = $this->pushSpecialBlock(
"media");
2797 $media->queryList = $mediaQueryList[2];
2803 if ($this->literal(
"@mixin") &&
2804 $this->keyword($mixinName) &&
2805 ($this->argumentDef($args) ||
true) &&
2806 $this->literal(
"{"))
2808 $mixin = $this->pushSpecialBlock(
"mixin");
2809 $mixin->name = $mixinName;
2810 $mixin->args = $args;
2816 if ($this->literal(
"@include") &&
2817 $this->keyword($mixinName) &&
2818 ($this->literal(
"(") &&
2819 ($this->argValues($argValues) ||
true) &&
2820 $this->literal(
")") ||
true) &&
2822 $this->literal(
"{") && $hasBlock =
true))
2824 $child = array(
"include",
2825 $mixinName, isset($argValues) ? $argValues : null, null);
2827 if (!empty($hasBlock)) {
2828 $include = $this->pushSpecialBlock(
"include");
2829 $include->child = $child;
2831 $this->append($child, $s);
2839 if ($this->literal(
"@import") &&
2840 $this->valueList($importPath) &&
2843 $this->append(array(
"import", $importPath), $s);
2849 if ($this->literal(
"@extend") &&
2850 $this->selectors($selector) &&
2853 $this->append(array(
"extend", $selector), $s);
2859 if ($this->literal(
"@function") &&
2860 $this->keyword($fnName) &&
2861 $this->argumentDef($args) &&
2862 $this->literal(
"{"))
2864 $func = $this->pushSpecialBlock(
"function");
2865 $func->name = $fnName;
2866 $func->args = $args;
2872 if ($this->literal(
"@return") && $this->valueList($retVal) && $this->end()) {
2873 $this->append(array(
"return", $retVal), $s);
2879 if ($this->literal(
"@each") &&
2880 $this->variable($varName) &&
2881 $this->literal(
"in") &&
2882 $this->valueList($list) &&
2883 $this->literal(
"{"))
2885 $each = $this->pushSpecialBlock(
"each");
2886 $each->var = $varName[1];
2887 $each->list = $list;
2893 if ($this->literal(
"@while") &&
2894 $this->expression($cond) &&
2895 $this->literal(
"{"))
2897 $while = $this->pushSpecialBlock(
"while");
2898 $while->cond = $cond;
2904 if ($this->literal(
"@for") &&
2905 $this->variable($varName) &&
2906 $this->literal(
"from") &&
2907 $this->expression($start) &&
2908 ($this->literal(
"through") ||
2909 ($forUntil =
true && $this->literal(
"to"))) &&
2910 $this->expression($end) &&
2911 $this->literal(
"{"))
2913 $for = $this->pushSpecialBlock(
"for");
2914 $for->var = $varName[1];
2915 $for->start = $start;
2917 $for->until = isset($forUntil);
2923 if ($this->literal(
"@if") && $this->valueList($cond) && $this->literal(
"{")) {
2924 $if = $this->pushSpecialBlock(
"if");
2926 $if->cases = array();
2932 if (($this->literal(
"@debug") || $this->literal(
"@warn")) &&
2933 $this->valueList($value) &&
2935 $this->append(array(
"debug", $value, $s), $s);
2941 if ($this->literal(
"@content") && $this->end()) {
2942 $this->append(array(
"mixin_content"), $s);
2948 $last = $this->last();
2949 if (isset($last) && $last[0] ==
"if") {
2950 list(, $if) = $last;
2951 if ($this->literal(
"@else")) {
2952 if ($this->literal(
"{")) {
2953 $else = $this->pushSpecialBlock(
"else");
2954 } elseif ($this->literal(
"if") && $this->valueList($cond) && $this->literal(
"{")) {
2955 $else = $this->pushSpecialBlock(
"elseif");
2956 $else->cond = $cond;
2960 $else->dontAppend =
true;
2961 $if->cases[] = $else;
2969 if ($this->literal(
"@charset") &&
2970 $this->valueList($charset) && $this->end())
2972 $this->append(array(
"charset", $charset), $s);
2979 if ($this->literal(
"@",
false) && $this->keyword($dirName) &&
2980 ($this->openString(
"{", $dirValue) ||
true) &&
2981 $this->literal(
"{"))
2983 $directive = $this->pushSpecialBlock(
"directive");
2984 $directive->name = $dirName;
2985 if (isset($dirValue)) $directive->value = $dirValue;
2995 if ($this->keyword($name,
false) &&
2996 $this->literal(
": ") &&
2997 $this->valueList($value) &&
3000 $name = array(
"string",
"", array($name));
3001 $this->append(array(
"assign", $name, $value), $s);
3008 if ($this->variable($name) &&
3009 $this->literal(
":") &&
3010 $this->valueList($value) && $this->end())
3013 $defaultVar = $value[0] ==
"list" && $this->stripDefault($value);
3014 $this->append(array(
"assign", $name, $value, $defaultVar), $s);
3021 if ($this->literal(
"-->")) {
3026 $oldComments = $this->insertComments;
3027 $this->insertComments =
false;
3028 if ($this->selectors($selectors) && $this->literal(
"{")) {
3029 $this->pushBlock($selectors);
3030 $this->insertComments = $oldComments;
3035 $this->insertComments = $oldComments;
3038 if ($this->propertyName($name) && $this->literal(
":")) {
3039 $foundSomething =
false;
3040 if ($this->valueList($value)) {
3041 $this->append(array(
"assign", $name, $value), $s);
3042 $foundSomething =
true;
3045 if ($this->literal(
"{")) {
3046 $propBlock = $this->pushSpecialBlock(
"nestedprop");
3047 $propBlock->prefix = $name;
3048 $foundSomething =
true;
3049 } elseif ($foundSomething) {
3050 $foundSomething = $this->end();
3053 if ($foundSomething) {
3063 if ($this->literal(
"}")) {
3064 $block = $this->popBlock();
3065 if (isset($block->type) && $block->type ==
"include") {
3066 $include = $block->child;
3067 unset($block->child);
3068 $include[3] = $block;
3069 $this->append($include, $s);
3070 } elseif (empty($block->dontAppend)) {
3071 $type = isset($block->type) ? $block->type :
"block";
3072 $this->append(array($type, $block), $s);
3078 if ($this->literal(
";") ||
3079 $this->literal(
"<!--"))
3088 $def = end($value[2]);
3089 if ($def[0] ==
"keyword" && $def[1] ==
"!default") {
3090 array_pop($value[2]);
3091 $value = $this->flattenList($value);
3095 if ($def[0] ==
"list") {
3096 return $this->stripDefault($value[2][count($value[2]) - 1]);
3102 protected function literal($what, $eatWhitespace = null) {
3103 if (!isset($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
3106 if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3107 if ($this->buffer[$this->count] == $what) {
3108 if (!$eatWhitespace) {
3118 return $this->match($this->preg_quote($what), $m, $eatWhitespace);
3125 $b->parent = $this->env;
3127 $b->selectors = $selectors;
3128 $b->children = array();
3135 $block = $this->pushBlock(null);
3136 $block->type = $type;
3141 if (empty($this->env->parent)) {
3142 $this->throwParseError(
"unexpected }");
3146 $this->env = $this->env->parent;
3147 unset($old->parent);
3151 protected function append($statement, $pos=null) {
3152 if ($pos !== null) {
3153 $statement[-1] = $pos;
3154 if (!$this->rootParser) $statement[-2] = $this;
3156 $this->env->children[] = $statement;
3161 $i = count($this->env->children) - 1;
3162 if (isset($this->env->children[$i]))
3163 return $this->env->children[$i];
3169 return $this->genericList($out,
"mediaQuery",
",",
false);
3175 $expressions = null;
3178 if (($this->literal(
"only") && ($only =
true) || $this->literal(
"not") && ($not =
true) ||
true) && $this->mixedKeyword($mediaType)) {
3179 $prop = array(
"mediaType");
3180 if (isset($only)) $prop[] = array(
"keyword",
"only");
3181 if (isset($not)) $prop[] = array(
"keyword",
"not");
3182 $media = array(
"list",
"", array());
3183 foreach ((array)$mediaType as $type) {
3184 if (is_array($type)) {
3185 $media[2][] = $type;
3187 $media[2][] = array(
"keyword", $type);
3194 if (empty($parts) || $this->literal(
"and")) {
3195 $this->genericList($expressions,
"mediaExpression",
"and",
false);
3196 if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
3206 if ($this->literal(
"(") &&
3207 $this->expression($feature) &&
3208 ($this->literal(
":") && $this->expression($value) ||
true) &&
3209 $this->literal(
")"))
3211 $out = array(
"mediaExp", $feature);
3212 if ($value) $out[] = $value;
3221 if ($this->genericList($list,
"argValue",
",",
false)) {
3232 if (!$this->variable($keyword) || !$this->literal(
":")) {
3237 if ($this->genericList($value,
"expression")) {
3238 $out = array($keyword, $value,
false);
3240 if ($this->literal(
"...")) {
3260 return $this->genericList($out,
'spaceList',
',');
3265 return $this->genericList($out,
'expression');
3268 protected function genericList(&$out, $parseItem, $delim=
"", $flatten=
true) {
3271 while ($this->$parseItem($value)) {
3274 if (!$this->literal($delim))
break;
3278 if (count($items) == 0) {
3283 if ($flatten && count($items) == 1) {
3286 $out = array(
"list", $delim, $items);
3295 if ($this->literal(
"(")) {
3296 if ($this->literal(
")")) {
3297 $out = array(
"list",
"", array());
3301 if ($this->valueList($out) && $this->literal(
')') && $out[0] ==
"list") {
3308 if ($this->value($lhs)) {
3309 $out = $this->expHelper($lhs, 0);
3317 $opstr = self::$operatorStr;
3319 $ss = $this->seek();
3320 $whiteBefore = isset($this->buffer[$this->count - 1]) &&
3321 ctype_space($this->buffer[$this->count - 1]);
3322 while ($this->match($opstr, $m) && self::$precedence[$m[1]] >= $minP) {
3323 $whiteAfter = isset($this->buffer[$this->count - 1]) &&
3324 ctype_space($this->buffer[$this->count - 1]);
3329 if ($op ==
"-" && $whiteBefore) {
3330 if (!$whiteAfter)
break;
3333 if (!$this->value($rhs))
break;
3336 if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) {
3337 $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
3340 $lhs = array(
"exp", $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter);
3341 $ss = $this->seek();
3342 $whiteBefore = isset($this->buffer[$this->count - 1]) &&
3343 ctype_space($this->buffer[$this->count - 1]);
3353 if ($this->literal(
"not",
false) && $this->whitespace() && $this->value($inner)) {
3354 $out = array(
"unary",
"not", $inner, $this->inParens);
3360 if ($this->literal(
"+") && $this->value($inner)) {
3361 $out = array(
"unary",
"+", $inner, $this->inParens);
3368 if ($this->literal(
"-",
false) &&
3369 ($this->variable($inner) ||
3370 $this->unit($inner) ||
3371 $this->parenValue($inner)))
3373 $out = array(
"unary",
"-", $inner, $this->inParens);
3379 if ($this->parenValue($out))
return true;
3380 if ($this->interpolation($out))
return true;
3381 if ($this->variable($out))
return true;
3382 if ($this->color($out))
return true;
3383 if ($this->unit($out))
return true;
3384 if ($this->
string($out))
return true;
3385 if ($this->func($out))
return true;
3386 if ($this->progid($out))
return true;
3388 if ($this->keyword($keyword)) {
3389 if ($keyword ==
"null") {
3390 $out = array(
"null");
3392 $out = array(
"keyword", $keyword);
3404 $inParens = $this->inParens;
3405 if ($this->literal(
"(") &&
3406 ($this->inParens =
true) && $this->expression($exp) &&
3407 $this->literal(
")"))
3410 $this->inParens = $inParens;
3413 $this->inParens = $inParens;
3422 if ($this->literal(
"progid:",
false) &&
3423 $this->openString(
"(", $fn) &&
3424 $this->literal(
"("))
3426 $this->openString(
")", $args,
"(");
3427 if ($this->literal(
")")) {
3428 $out = array(
"string",
"", array(
3429 "progid:", $fn,
"(", $args,
")" 3442 if ($this->keyword($name,
false) &&
3443 $this->literal(
"("))
3445 if ($name ==
"alpha" && $this->argumentList($args)) {
3446 $func = array(
"function", $name, array(
"string",
"", $args));
3450 if ($name !=
"expression" && !preg_match(
"/^(-[a-z]+-)?calc$/", $name)) {
3451 $ss = $this->seek();
3452 if ($this->argValues($args) && $this->literal(
")")) {
3453 $func = array(
"fncall", $name, $args);
3459 if (($this->openString(
")", $str,
"(") ||
true ) &&
3460 $this->literal(
")"))
3464 $args[] = array(null, array(
"string",
"", array($str)));
3467 $func = array(
"fncall", $name, $args);
3478 $this->literal(
"(");
3481 while ($this->keyword($var)) {
3482 $ss = $this->seek();
3484 if ($this->literal(
"=") && $this->expression($exp)) {
3485 $args[] = array(
"string",
"", array($var.
"="));
3493 if (!$this->literal(
","))
break;
3495 $args[] = array(
"string",
"", array(
", "));
3498 if (!$this->literal(
")") || !count($args)) {
3509 $this->literal(
"(");
3512 while ($this->variable($var)) {
3513 $arg = array($var[1], null,
false);
3515 $ss = $this->seek();
3516 if ($this->literal(
":") && $this->genericList($defaultVal,
"expression")) {
3517 $arg[1] = $defaultVal;
3522 $ss = $this->seek();
3523 if ($this->literal(
"...")) {
3524 $sss = $this->seek();
3525 if (!$this->literal(
")")) {
3526 $this->throwParseError(
"... has to be after the final argument");
3535 if (!$this->literal(
","))
break;
3538 if (!$this->literal(
")")) {
3548 $color = array(
'color');
3550 if ($this->match(
'(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
3559 $num = hexdec($num);
3560 foreach (array(3,2,1) as $i) {
3564 $color[$i] = $t * (256/$width) + $t * floor(16/$width);
3575 if ($this->match(
'([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
3576 $unit = array(
"number", $m[1], empty($m[3]) ?
"" : $m[3]);
3584 if ($this->literal(
'"',
false)) {
3586 } elseif ($this->literal(
"'",
false)) {
3593 $oldWhite = $this->eatWhiteDefault;
3594 $this->eatWhiteDefault =
false;
3596 while ($this->matchString($m, $delim)) {
3598 if ($m[2] ==
"#{") {
3599 $this->count -= strlen($m[2]);
3600 if ($this->interpolation($inter,
false)) {
3601 $content[] = $inter;
3603 $this->count += strlen($m[2]);
3606 } elseif ($m[2] ==
'\\') {
3608 if ($this->literal($delim,
false)) {
3609 $content[] = $delim;
3612 $this->count -= strlen($delim);
3617 $this->eatWhiteDefault = $oldWhite;
3619 if ($this->literal($delim)) {
3620 $out = array(
"string", $delim, $content);
3633 $oldWhite = $this->eatWhiteDefault;
3634 $this->eatWhiteDefault =
false;
3637 if ($this->keyword($key)) {
3642 if ($this->interpolation($inter)) {
3650 $this->eatWhiteDefault = $oldWhite;
3652 if (count($parts) == 0)
return false;
3654 if ($this->eatWhiteDefault) {
3655 $this->whitespace();
3664 $oldWhite = $this->eatWhiteDefault;
3665 $this->eatWhiteDefault =
false;
3667 $stop = array(
"'",
'"',
"#{", $end);
3668 $stop = array_map(array($this,
"preg_quote"), $stop);
3669 $stop[] = self::$commentMulti;
3671 $patt =
'(.*?)('.implode(
"|", $stop).
')';
3676 while ($this->match($patt, $m,
false)) {
3677 if (isset($m[1]) && $m[1] !==
'') {
3680 $nestingLevel += substr_count($m[1], $nestingOpen);
3686 $this->count-= strlen($tok);
3688 if ($nestingLevel == 0) {
3695 if (($tok ==
"'" || $tok ==
'"') && $this->
string($str)) {
3700 if ($tok ==
"#{" && $this->interpolation($inter)) {
3701 $content[] = $inter;
3706 $this->count+= strlen($tok);
3709 $this->eatWhiteDefault = $oldWhite;
3711 if (count($content) == 0)
return false;
3714 if (is_string(end($content))) {
3715 $content[count($content) - 1] = rtrim(end($content));
3718 $out = array(
"string",
"", $content);
3724 $oldWhite = $this->eatWhiteDefault;
3725 $this->eatWhiteDefault =
true;
3728 if ($this->literal(
"#{") && $this->valueList($value) && $this->literal(
"}",
false)) {
3733 $left = preg_match(
'/\s/', $this->buffer[$s - 1]) ?
" " :
"";
3734 $right = preg_match(
'/\s/', $this->buffer[$this->count]) ?
" ":
"";
3736 $left = $right =
false;
3739 $out = array(
"interpolate", $value, $left, $right);
3740 $this->eatWhiteDefault = $oldWhite;
3741 if ($this->eatWhiteDefault) $this->whitespace();
3746 $this->eatWhiteDefault = $oldWhite;
3757 $oldWhite = $this->eatWhiteDefault;
3758 $this->eatWhiteDefault =
false;
3761 if ($this->interpolation($inter)) {
3763 } elseif ($this->keyword($text)) {
3765 } elseif (count($parts) == 0 && $this->match(
'[:.#]', $m,
false)) {
3773 $this->eatWhiteDefault = $oldWhite;
3774 if (count($parts) == 0)
return false;
3777 if (preg_match(self::$whitePattern,
3778 $this->buffer, $m, null, $this->count))
3780 if (!empty($m[0])) {
3782 $this->count += strlen($m[0]);
3786 $this->whitespace();
3788 $out = array(
"string",
"", $parts);
3795 $selectors = array();
3796 while ($this->selector($sel)) {
3797 $selectors[] = $sel;
3798 if (!$this->literal(
","))
break;
3799 while ($this->literal(
","));
3802 if (count($selectors) == 0) {
3813 $selector = array();
3816 if ($this->match(
'[>+~]+', $m)) {
3817 $selector[] = array($m[0]);
3818 } elseif ($this->selectorSingle($part)) {
3819 $selector[] = $part;
3820 $this->whitespace();
3821 } elseif ($this->match(
'\/[^\/]+\/', $m)) {
3822 $selector[] = array($m[0]);
3829 if (count($selector) == 0) {
3840 $oldWhite = $this->eatWhiteDefault;
3841 $this->eatWhiteDefault =
false;
3845 if ($this->literal(
"*",
false)) {
3851 if ($this->match(
"\s*[{,]", $m)) {
3858 if ($this->literal(
"&",
false)) {
3863 if ($this->literal(
".",
false)) {
3868 if ($this->literal(
"|",
false)) {
3874 if ($this->unit($unit)) {
3879 if ($this->keyword($name)) {
3884 if ($this->interpolation($inter)) {
3889 if ($this->literal(
'%',
false) && $this->placeholder($placeholder)) {
3891 $parts[] = $placeholder;
3895 if ($this->literal(
"#",
false)) {
3901 if ($this->match(
"::?", $m) && $this->mixedKeyword($nameParts)) {
3903 foreach ($nameParts as $sub) {
3907 $ss = $this->seek();
3908 if ($this->literal(
"(") &&
3909 ($this->openString(
")", $str,
"(") ||
true ) &&
3910 $this->literal(
")"))
3913 if (!empty($str)) $parts[] = $str;
3926 if ($this->literal(
"[",
false)) {
3927 $attrParts = array(
"[");
3930 if ($this->literal(
"]",
false)) {
3935 if ($this->match(
'\s+', $m)) {
3939 if ($this->
string($str)) {
3940 $attrParts[] = $str;
3944 if ($this->keyword($word)) {
3945 $attrParts[] = $word;
3949 if ($this->interpolation($inter,
false)) {
3950 $attrParts[] = $inter;
3955 if ($this->match(
'[|-~\$\*\^=]+', $m)) {
3956 $attrParts[] = $m[0];
3963 if ($this->literal(
"]",
false)) {
3965 foreach ($attrParts as $part) {
3977 $this->eatWhiteDefault = $oldWhite;
3979 if (count($parts) == 0)
return false;
3987 if ($this->literal(
"$",
false) && $this->keyword($name)) {
3988 $out = array(
"var", $name);
3995 protected function keyword(&$word, $eatWhitespace = null) {
3996 if ($this->match(
'([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)',
3997 $m, $eatWhitespace))
4006 if ($this->match(
'([\w\-_]+)', $m)) {
4007 $placeholder = $m[1];
4015 if ($this->literal(
';')) {
4017 } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] ==
'}') {
4027 protected function to($what, &$out, $until =
false, $allowNewline =
false) {
4028 if (is_string($allowNewline)) {
4029 $validChars = $allowNewline;
4031 $validChars = $allowNewline ?
"." :
"[^\n]";
4033 if (!$this->match(
'('.$validChars.
'*?)'.$this->preg_quote($what), $m, !$until))
return false;
4034 if ($until) $this->count -= strlen($what);
4040 $count = !isset($count) ? $this->count : $count;
4042 $line = $this->getLineNo($count);
4044 if (!empty($this->sourceName)) {
4045 $loc =
"$this->sourceName on line $line";
4047 $loc =
"line: $line";
4050 if ($this->peek(
"(.*?)(\n|$)", $m, $count)) {
4051 throw new Exception(
"$msg: failed at `$m[1]` $loc");
4053 throw new Exception(
"$msg: $loc");
4058 return 1 + substr_count(substr($this->buffer, 0, $pos),
"\n");
4074 $end = strpos($this->buffer,
"\n", $this->count);
4075 if ($end ===
false || $this->buffer[$end - 1] ==
'\\' || $this->buffer[$end - 2] ==
'\\' && $this->buffer[$end - 1] ==
"\r") {
4076 $end = strlen($this->buffer);
4080 foreach (array(
'#{',
'\\', $delim) as $lookahead) {
4081 $pos = strpos($this->buffer, $lookahead, $this->count);
4082 if ($pos !==
false && $pos < $end) {
4084 $token = $lookahead;
4088 if (!isset($token)) {
4092 $match = substr($this->buffer, $this->count, $end - $this->count);
4098 $this->count = $end + strlen($token);
4104 protected function match($regex, &$out, $eatWhitespace = null) {
4105 if (!isset($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
4107 $r =
'/'.$regex.
'/Ais';
4108 if (preg_match($r, $this->buffer, $out, null, $this->count)) {
4109 $this->count += strlen($out[0]);
4110 if ($eatWhitespace) $this->whitespace();
4119 while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
4120 if ($this->insertComments) {
4121 if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
4122 $this->append(array(
"comment", $m[1]));
4123 $this->commentsSeen[$this->count] =
true;
4126 $this->count += strlen($m[0]);
4132 protected function peek($regex, &$out, $from=null) {
4133 if (!isset($from)) $from = $this->count;
4135 $r =
'/'.$regex.
'/Ais';
4136 $result = preg_match($r, $this->buffer, $out, null, $from);
4141 protected function seek($where = null) {
4142 if ($where === null)
return $this->count;
4143 else $this->count = $where;
4148 return preg_quote($what,
'/');
4152 if ($this->peek(
"(.*?)(\n|$)", $m, $this->count)) {
4160 if ($value[0] ==
"list" && count($value[2]) == 1) {
4161 return $this->flattenList($value[2][0]);
4173 public $indentChar =
" ";
4175 public $break =
"\n";
4176 public $open =
" {";
4177 public $close =
"}";
4178 public $tagSeparator =
", ";
4179 public $assignSeparator =
": ";
4182 $this->indentLevel = 0;
4186 return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
4190 return $name . $this->assignSeparator . $value .
";";
4194 if (empty($block->lines) && empty($block->children))
return;
4196 $inner = $pre = $this->indentStr();
4198 if (!empty($block->selectors)) {
4200 implode($this->tagSeparator, $block->selectors) .
4201 $this->open . $this->break;
4202 $this->indentLevel++;
4203 $inner = $this->indentStr();
4206 if (!empty($block->lines)) {
4207 $glue = $this->
break.$inner;
4208 echo $inner . implode($glue, $block->lines);
4209 if (!empty($block->children)) {
4214 foreach ($block->children as $child) {
4215 $this->block($child);
4218 if (!empty($block->selectors)) {
4219 $this->indentLevel--;
4220 if (empty($block->children)) echo $this->break;
4221 echo $pre . $this->close . $this->break;
4227 $this->block($block);
4228 $out = ob_get_clean();
4240 public $close =
" }";
4245 $children = array();
4246 foreach ($block->children as $i => $child) {
4247 if (empty($child->lines) && empty($child->children)) {
4248 if (isset($block->children[$i + 1])) {
4249 $block->children[$i + 1]->depth = $child->depth;
4253 $children[] = $child;
4256 $count = count($children);
4257 for ($i = 0; $i < $count; $i++) {
4258 $depth = $children[$i]->depth;
4260 if (isset($children[$j]) && $depth < $children[$j]->depth) {
4261 $childDepth = $children[$j]->depth;
4262 for (; $j < $count; $j++) {
4263 if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
4264 $children[$j]->depth = $depth + 1;
4270 $block->children = $children;
4273 foreach ($block->children as $child) {
4274 $this->adjustAllChildren($child);
4275 $child->depth = $child->depth - $block->depth;
4280 if ($block->type ==
"root") {
4281 $this->adjustAllChildren($block);
4284 $inner = $pre = $this->indentStr($block->depth - 1);
4285 if (!empty($block->selectors)) {
4287 implode($this->tagSeparator, $block->selectors) .
4288 $this->open . $this->break;
4289 $this->indentLevel++;
4290 $inner = $this->indentStr($block->depth - 1);
4293 if (!empty($block->lines)) {
4294 $glue = $this->
break.$inner;
4295 echo $inner . implode($glue, $block->lines);
4296 if (!empty($block->children)) echo $this->break;
4299 foreach ($block->children as $i => $child) {
4301 $this->block($child);
4302 if ($i < count($block->children) - 1) {
4305 if (isset($block->children[$i + 1])) {
4306 $next = $block->children[$i + 1];
4307 if ($next->depth == max($block->depth, 1) && $child->depth >= $next->depth) {
4314 if (!empty($block->selectors)) {
4315 $this->indentLevel--;
4319 if ($block->type ==
"root") {
4332 public $tagSeparator =
",";
4333 public $assignSeparator =
":";
4355 protected function join($left, $right) {
4356 return rtrim($left,
'/\\') . DIRECTORY_SEPARATOR . ltrim($right,
'/\\');
4366 case isset($_GET[
'p']):
4368 case isset($_SERVER[
'PATH_INFO']):
4369 return $_SERVER[
'PATH_INFO'];
4370 case isset($_SERVER[
'DOCUMENT_URI']):
4371 return substr($_SERVER[
'DOCUMENT_URI'], strlen($_SERVER[
'SCRIPT_NAME']));
4381 if (($input = $this->inputName())
4382 && strpos($input,
'..') ===
false 4383 && substr($input, -5) ===
'.scss' 4385 $name = $this->join($this->dir, $input);
4387 if (is_file($name) && is_readable($name)) {
4401 return $this->join($this->cacheDir, md5($fname) .
'.css');
4410 return $out .
'.imports';
4422 if (!is_file($out))
return true;
4424 $mtime = filemtime($out);
4425 if (filemtime($in) > $mtime)
return true;
4428 $icache = $this->importsCacheName($out);
4429 if (is_readable($icache)) {
4430 $imports = unserialize(file_get_contents($icache));
4431 foreach ($imports as $import) {
4432 if (filemtime($import) > $mtime)
return true;
4445 $modifiedSince =
'';
4447 if (isset($_SERVER[
'HTTP_IF_MODIFIED_SINCE'])) {
4448 $modifiedSince = $_SERVER[
'HTTP_IF_MODIFIED_SINCE'];
4450 if (
false !== ($semicolonPos = strpos($modifiedSince,
';'))) {
4451 $modifiedSince = substr($modifiedSince, 0, $semicolonPos);
4455 return $modifiedSince;
4467 $start = microtime(
true);
4468 $css = $this->scss->compile(file_get_contents($in), $in);
4469 $elapsed = round((microtime(
true) - $start), 4);
4473 $css =
"/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
4475 file_put_contents($out, $css);
4476 file_put_contents($this->importsCacheName($out),
4477 serialize($this->scss->getParsedFiles()));
4487 $protocol = isset($_SERVER[
'SERVER_PROTOCOL'])
4488 ? $_SERVER[
'SERVER_PROTOCOL']
4491 if ($input = $this->findInput()) {
4492 $output = $this->cacheName($salt . $input);
4494 if ($this->needsCompile($input, $output)) {
4496 $css = $this->
compile($input, $output);
4498 $lastModified = gmdate(
'D, d M Y H:i:s', filemtime($output)) .
' GMT';
4500 header(
'Last-Modified: ' . $lastModified);
4501 header(
'Content-type: text/css');
4506 }
catch (Exception $e) {
4507 header($protocol .
' 500 Internal Server Error');
4508 header(
'Content-type: text/plain');
4510 echo
'Parse error: ' . $e->getMessage() .
"\n";
4514 header(
'X-SCSS-Cache: true');
4515 header(
'Content-type: text/css');
4517 $modifiedSince = $this->getModifiedSinceHeader();
4518 $mtime = filemtime($output);
4520 if (@strtotime($modifiedSince) === $mtime) {
4521 header($protocol .
' 304 Not Modified');
4526 $lastModified = gmdate(
'D, d M Y H:i:s', $mtime) .
' GMT';
4527 header(
'Last-Modified: ' . $lastModified);
4529 echo file_get_contents($output);
4534 header($protocol .
' 404 Not Found');
4535 header(
'Content-type: text/plain');
4538 echo
"/* INPUT NOT FOUND scss $v */\n";
4551 if (!isset($cacheDir)) {
4552 $cacheDir = $this->join($dir,
'scss_cache');
4555 $this->cacheDir = $cacheDir;
4556 if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755,
true);
4558 if (!isset($scss)) {
4559 $scss =
new scssc();
4560 $scss->setImportPaths($this->dir);
4562 $this->scss = $scss;
4571 $server =
new self($path);
static $lib_transparentize
coerceForExpression($value)
op_mul_number_number($left, $right)
unregisterFunction($name)
setExisting($name, $value, $env=null)
adjust_color_helper($base, $alter, $i)
keyword(&$word, $eatWhitespace=null)
op_and($left, $right, $shouldEval)
makeOutputBlock($type, $selectors=null)
throwParseError($msg="parse error", $count=null)
scale_color_helper($base, $scale, $i)
parseChunk()
Parse a single chunk off the head of the buffer and append it to the current parse environment...
sortArgs($prototype, $args)
adjustHsl($color, $idx, $amount)
change_color_helper($base, $alter, $i)
inputName()
Get name of requested .scss file.
join($left, $right)
Join path components.
registerFunction($name, $func)
static makeOperatorStr($operators)
interpolation(&$out, $lookWhite=true)
callBuiltin($name, $args, &$returnValue)
importsCacheName($out)
Get path to cached imports.
findInput()
Get path to requested .scss file.
compileValue($value)
Compiles a primitive value into a CSS property value.
compileMediaQuery($queryList)
hasSelectorPlaceholder($selector)
op_color_number($op, $left, $right)
op_number_color($op, $left, $right)
mergeMediaTypes($type1, $type2)
reduce($value, $inExp=false)
compileNestedBlock($block, $selectors)
op_add_number_number($left, $right)
listSeparatorForJoin($list1, $sep)
match($regex, &$out, $eatWhitespace=null)
__construct($sourceName=null, $rootParser=true)
Constructor.
compileStringContent($string)
compileBlock($block)
Recursively compiles a block.
unsetVariable($name)
Unset variable.
matchExtendsSingle($single, &$outOrigin)
op_sub_number_number($left, $right)
op_color_color($op, $left, $right)
serve($salt= '')
Compile requested scss and serve css.
injectVariables(array $args)
applyArguments($argDef, $argValues)
compileSelectorPart($piece)
op_or($left, $right, $shouldEval)
SCSS compiler written in PHP.
joinSelectors($parent, $child)
flattenSelectors($block, $parentKey=null)
append($statement, $pos=null)
op_mod_number_number($left, $right)
compile($code, $name=null)
Compile scss.
needsCompile($in, $out)
Determine whether .scss file needs to be re-compiled.
placeholder(&$placeholder)
compileChildren($stms, $out)
toHSL($red, $green, $blue)
setNumberPrecision($numberPrecision)
genericList(&$out, $parseItem, $delim="", $flatten=true)
static serveFrom($path)
Helper method to serve compiled scss.
op_gt_number_number($left, $right)
op_lte_number_number($left, $right)
combineSelectorSingle($base, $other)
getModifiedSinceHeader()
Get If-Modified-Since header from client request.
multiplyMedia($env, $childQueries=null)
static $cssColors
CSS Colors.
valueList(&$out)
Parse list.
parse($buffer)
Parser buffer.
compileSelector($selector)
setVariables(array $variables)
Set variables.
compileChild($child, $out)
op_gte_number_number($left, $right)
getNormalizedNumbers($args)
to($what, &$out, $until=false, $allowNewline=false)
cacheName($fname)
Get path to cached .css file.
op_lt_number_number($left, $right)
compile($in, $out)
Compile .scss file.
op_div_number_number($left, $right)
literal($what, $eatWhitespace=null)
__construct($dir, $cacheDir=null, $scss=null)
Constructor.
lib_counter($args)
Workaround IE7's content counter bug.
lib_transparentize($args)
matchExtends($selector, &$out, $from=0, $initial=true)
coerceUnit($number, $unit)
toRGB($hue, $saturation, $lightness)
coerceList($item, $delim=",")
openString($end, &$out, $nestingOpen=null)
extractInterpolation($list)
flattenSelectorSingle($single)
matchString(&$m, $delim)
Match string looking for either ending delim, escape, or string interpolation.
isSelfExtend($target, $origin)
pushExtends($target, $origin)
setFormatter($formatterName)
compileImport($rawPath, $out)
peek($regex, &$out, $from=null)