CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

scssc.php
Go to the documentation of this file.
1 <?php
45 class scssc {
46 static public $VERSION = 'v0.0.12';
47
48 static protected $operatorNames = array(
49 '+' => "add",
50 '-' => "sub",
51 '*' => "mul",
52 '/' => "div",
53 '%' => "mod",
54
55 '==' => "eq",
56 '!=' => "neq",
57 '<' => "lt",
58 '>' => "gt",
59
60 '<=' => "lte",
61 '>=' => "gte",
62 );
63
64 static protected $namespaces = array(
65 "special" => "%",
66 "mixin" => "@",
67 "function" => "^",
68 );
69
70 static protected $unitTable = array(
71 "in" => array(
72 "in" => 1,
73 "pt" => 72,
74 "pc" => 6,
75 "cm" => 2.54,
76 "mm" => 25.4,
77 "px" => 96,
78 )
79 );
80
81 static public $true = array("keyword", "true");
82 static public $false = array("keyword", "false");
83 static public $null = array("null");
84
85 static public $defaultValue = array("keyword", "");
86 static public $selfSelector = array("self");
87
88 protected $importPaths = array("");
89 protected $importCache = array();
90
91 protected $userFunctions = array();
92 protected $registeredVars = array();
93
94 protected $numberPrecision = 5;
95
96 protected $formatter = "scss_formatter_nested";
97
106 public function compile($code, $name = null)
107 {
108 $this->indentLevel = -1;
109 $this->commentsSeen = array();
110 $this->extends = array();
111 $this->extendsMap = array();
112 $this->parsedFiles = array();
113 $this->env = null;
114 $this->scope = null;
115
116 $locale = setlocale(LC_NUMERIC, 0);
117 setlocale(LC_NUMERIC, "C");
118
119 $this->parser = new scss_parser($name);
120
121 $tree = $this->parser->parse($code);
122
123 $this->formatter = new $this->formatter();
124
125 $this->pushEnv($tree);
126 $this->injectVariables($this->registeredVars);
127 $this->compileRoot($tree);
128 $this->popEnv();
129
130 $out = $this->formatter->format($this->scope);
131
132 setlocale(LC_NUMERIC, $locale);
133
134 return $out;
135 }
136
137 protected function isSelfExtend($target, $origin) {
138 foreach ($origin as $sel) {
139 if (in_array($target, $sel)) {
140 return true;
141 }
142 }
143
144 return false;
145 }
146
147 protected function pushExtends($target, $origin) {
148 if ($this->isSelfExtend($target, $origin)) {
149 return;
150 }
151
152 $i = count($this->extends);
153 $this->extends[] = array($target, $origin);
154
155 foreach ($target as $part) {
156 if (isset($this->extendsMap[$part])) {
157 $this->extendsMap[$part][] = $i;
158 } else {
159 $this->extendsMap[$part] = array($i);
160 }
161 }
162 }
163
164 protected function makeOutputBlock($type, $selectors = null) {
165 $out = new stdClass;
166 $out->type = $type;
167 $out->lines = array();
168 $out->children = array();
169 $out->parent = $this->scope;
170 $out->selectors = $selectors;
171 $out->depth = $this->env->depth;
172
173 return $out;
174 }
175
176 protected function matchExtendsSingle($single, &$outOrigin) {
177 $counts = array();
178 foreach ($single as $part) {
179 if (!is_string($part)) return false; // hmm
180
181 if (isset($this->extendsMap[$part])) {
182 foreach ($this->extendsMap[$part] as $idx) {
183 $counts[$idx] =
184 isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
185 }
186 }
187 }
188
189 $outOrigin = array();
190 $found = false;
191
192 foreach ($counts as $idx => $count) {
193 list($target, $origin) = $this->extends[$idx];
194
195 // check count
196 if ($count != count($target)) continue;
197
198 // check if target is subset of single
199 if (array_diff(array_intersect($single, $target), $target)) continue;
200
201 $rem = array_diff($single, $target);
202
203 foreach ($origin as $j => $new) {
204 // prevent infinite loop when target extends itself
205 foreach ($new as $new_selector) {
206 if (!array_diff($single, $new_selector)) {
207 continue 2;
208 }
209 }
210
211 $origin[$j][count($origin[$j]) - 1] = $this->combineSelectorSingle(end($new), $rem);
212 }
213
214 $outOrigin = array_merge($outOrigin, $origin);
215
216 $found = true;
217 }
218
219 return $found;
220 }
221
222 protected function combineSelectorSingle($base, $other) {
223 $tag = null;
224 $out = array();
225
226 foreach (array($base, $other) as $single) {
227 foreach ($single as $part) {
228 if (preg_match('/^[^\[.#:]/', $part)) {
229 $tag = $part;
230 } else {
231 $out[] = $part;
232 }
233 }
234 }
235
236 if ($tag) {
237 array_unshift($out, $tag);
238 }
239
240 return $out;
241 }
242
243 protected function matchExtends($selector, &$out, $from = 0, $initial=true) {
244 foreach ($selector as $i => $part) {
245 if ($i < $from) continue;
246
247 if ($this->matchExtendsSingle($part, $origin)) {
248 $before = array_slice($selector, 0, $i);
249 $after = array_slice($selector, $i + 1);
250
251 foreach ($origin as $new) {
252 $k = 0;
253
254 // remove shared parts
255 if ($initial) {
256 foreach ($before as $k => $val) {
257 if (!isset($new[$k]) || $val != $new[$k]) {
258 break;
259 }
260 }
261 }
262
263 $result = array_merge(
264 $before,
265 $k > 0 ? array_slice($new, $k) : $new,
266 $after);
267
268
269 if ($result == $selector) continue;
270 $out[] = $result;
271
272 // recursively check for more matches
273 $this->matchExtends($result, $out, $i, false);
274
275 // selector sequence merging
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),
281 $after);
282
283 $out[] = $result2;
284 }
285 }
286 }
287 }
288 }
289
290 protected function flattenSelectors($block, $parentKey = null) {
291 if ($block->selectors) {
292 $selectors = array();
293 foreach ($block->selectors as $s) {
294 $selectors[] = $s;
295 if (!is_array($s)) continue;
296 // check extends
297 if (!empty($this->extendsMap)) {
298 $this->matchExtends($s, $selectors);
299 }
300 }
301
302 $block->selectors = array();
303 $placeholderSelector = false;
304 foreach ($selectors as $selector) {
305 if ($this->hasSelectorPlaceholder($selector)) {
306 $placeholderSelector = true;
307 continue;
308 }
309 $block->selectors[] = $this->compileSelector($selector);
310 }
311
312 if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) {
313 unset($block->parent->children[$parentKey]);
314 return;
315 }
316 }
317
318 foreach ($block->children as $key => $child) {
319 $this->flattenSelectors($child, $key);
320 }
321 }
322
323 protected function compileRoot($rootBlock)
324 {
325 $this->scope = $this->makeOutputBlock('root');
326
327 $this->compileChildren($rootBlock->children, $this->scope);
328 $this->flattenSelectors($this->scope);
329 }
330
331 protected function compileMedia($media) {
332 $this->pushEnv($media);
333
334 $mediaQuery = $this->compileMediaQuery($this->multiplyMedia($this->env));
335
336 if (!empty($mediaQuery)) {
337
338 $this->scope = $this->makeOutputBlock("media", array($mediaQuery));
339
340 $parentScope = $this->mediaParent($this->scope);
341
342 $parentScope->children[] = $this->scope;
343
344 // top level properties in a media cause it to be wrapped
345 $needsWrap = false;
346 foreach ($media->children as $child) {
347 $type = $child[0];
348 if ($type !== 'block' && $type !== 'media' && $type !== 'directive') {
349 $needsWrap = true;
350 break;
351 }
352 }
353
354 if ($needsWrap) {
355 $wrapped = (object)array(
356 "selectors" => array(),
357 "children" => $media->children
358 );
359 $media->children = array(array("block", $wrapped));
360 }
361
362 $this->compileChildren($media->children, $this->scope);
363
364 $this->scope = $this->scope->parent;
365 }
366
367 $this->popEnv();
368 }
369
370 protected function mediaParent($scope) {
371 while (!empty($scope->parent)) {
372 if (!empty($scope->type) && $scope->type != "media") {
373 break;
374 }
375 $scope = $scope->parent;
376 }
377
378 return $scope;
379 }
380
381 // TODO refactor compileNestedBlock and compileMedia into same thing
382 protected function compileNestedBlock($block, $selectors) {
383 $this->pushEnv($block);
384
385 $this->scope = $this->makeOutputBlock($block->type, $selectors);
386 $this->scope->parent->children[] = $this->scope;
387 $this->compileChildren($block->children, $this->scope);
388
389 $this->scope = $this->scope->parent;
390 $this->popEnv();
391 }
392
411 protected function compileBlock($block) {
412 $env = $this->pushEnv($block);
413
414 $env->selectors =
415 array_map(array($this, "evalSelector"), $block->selectors);
416
417 $out = $this->makeOutputBlock(null, $this->multiplySelectors($env));
418 $this->scope->children[] = $out;
419 $this->compileChildren($block->children, $out);
420
421 $this->popEnv();
422 }
423
424 // joins together .classes and #ids
425 protected function flattenSelectorSingle($single) {
426 $joined = array();
427 foreach ($single as $part) {
428 if (empty($joined) ||
429 !is_string($part) ||
430 preg_match('/[\[.:#%]/', $part))
431 {
432 $joined[] = $part;
433 continue;
434 }
435
436 if (is_array(end($joined))) {
437 $joined[] = $part;
438 } else {
439 $joined[count($joined) - 1] .= $part;
440 }
441 }
442
443 return $joined;
444 }
445
446 // replaces all the interpolates
447 protected function evalSelector($selector) {
448 return array_map(array($this, "evalSelectorPart"), $selector);
449 }
450
451 protected function evalSelectorPart($piece) {
452 foreach ($piece as &$p) {
453 if (!is_array($p)) continue;
454
455 switch ($p[0]) {
456 case "interpolate":
457 $p = $this->compileValue($p);
458 break;
459 case "string":
460 $p = $this->compileValue($p);
461 break;
462 }
463 }
464
465 return $this->flattenSelectorSingle($piece);
466 }
467
468 // compiles to string
469 // self(&) should have been replaced by now
470 protected function compileSelector($selector) {
471 if (!is_array($selector)) return $selector; // media and the like
472
473 return implode(" ", array_map(
474 array($this, "compileSelectorPart"), $selector));
475 }
476
477 protected function compileSelectorPart($piece) {
478 foreach ($piece as &$p) {
479 if (!is_array($p)) continue;
480
481 switch ($p[0]) {
482 case "self":
483 $p = "&";
484 break;
485 default:
486 $p = $this->compileValue($p);
487 break;
488 }
489 }
490
491 return implode($piece);
492 }
493
494 protected function hasSelectorPlaceholder($selector)
495 {
496 if (!is_array($selector)) return false;
497
498 foreach ($selector as $parts) {
499 foreach ($parts as $part) {
500 if ('%' == $part[0]) {
501 return true;
502 }
503 }
504 }
505
506 return false;
507 }
508
509 protected function compileChildren($stms, $out) {
510 foreach ($stms as $stm) {
511 $ret = $this->compileChild($stm, $out);
512 if (isset($ret)) return $ret;
513 }
514 }
515
516 protected function compileMediaQuery($queryList) {
517 $out = "@media";
518 $first = true;
519 foreach ($queryList as $query){
520 $type = null;
521 $parts = array();
522 foreach ($query as $q) {
523 switch ($q[0]) {
524 case "mediaType":
525 if ($type) {
526 $type = $this->mergeMediaTypes($type, array_map(array($this, "compileValue"), array_slice($q, 1)));
527 if (empty($type)) { // merge failed
528 return null;
529 }
530 } else {
531 $type = array_map(array($this, "compileValue"), array_slice($q, 1));
532 }
533 break;
534 case "mediaExp":
535 if (isset($q[2])) {
536 $parts[] = "(". $this->compileValue($q[1]) . $this->formatter->assignSeparator . $this->compileValue($q[2]) . ")";
537 } else {
538 $parts[] = "(" . $this->compileValue($q[1]) . ")";
539 }
540 break;
541 }
542 }
543 if ($type) {
544 array_unshift($parts, implode(' ', array_filter($type)));
545 }
546 if (!empty($parts)) {
547 if ($first) {
548 $first = false;
549 $out .= " ";
550 } else {
551 $out .= $this->formatter->tagSeparator;
552 }
553 $out .= implode(" and ", $parts);
554 }
555 }
556 return $out;
557 }
558
559 protected function mergeMediaTypes($type1, $type2) {
560 if (empty($type1)) {
561 return $type2;
562 }
563 if (empty($type2)) {
564 return $type1;
565 }
566 $m1 = '';
567 $t1 = '';
568 if (count($type1) > 1) {
569 $m1= strtolower($type1[0]);
570 $t1= strtolower($type1[1]);
571 } else {
572 $t1 = strtolower($type1[0]);
573 }
574 $m2 = '';
575 $t2 = '';
576 if (count($type2) > 1) {
577 $m2 = strtolower($type2[0]);
578 $t2 = strtolower($type2[1]);
579 } else {
580 $t2 = strtolower($type2[0]);
581 }
582 if (($m1 == 'not') ^ ($m2 == 'not')) {
583 if ($t1 == $t2) {
584 return null;
585 }
586 return array(
587 $m1 == 'not' ? $m2 : $m1,
588 $m1 == 'not' ? $t2 : $t1
589 );
590 } elseif ($m1 == 'not' && $m2 == 'not') {
591 # CSS has no way of representing "neither screen nor print"
592 if ($t1 != $t2) {
593 return null;
594 }
595 return array('not', $t1);
596 } elseif ($t1 != $t2) {
597 return null;
598 } else { // t1 == t2, neither m1 nor m2 are "not"
599 return array(empty($m1)? $m2 : $m1, $t1);
600 }
601 }
602
603 // returns true if the value was something that could be imported
604 protected function compileImport($rawPath, $out) {
605 if ($rawPath[0] == "string") {
606 $path = $this->compileStringContent($rawPath);
607 if ($path = $this->findImport($path)) {
608 $this->importFile($path, $out);
609 return true;
610 }
611 return false;
612 }
613 if ($rawPath[0] == "list") {
614 // handle a list of strings
615 if (count($rawPath[2]) == 0) return false;
616 foreach ($rawPath[2] as $path) {
617 if ($path[0] != "string") return false;
618 }
619
620 foreach ($rawPath[2] as $path) {
621 $this->compileImport($path, $out);
622 }
623
624 return true;
625 }
626
627 return false;
628 }
629
630 // return a value to halt execution
631 protected function compileChild($child, $out) {
632 $this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
633 $this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
634
635 switch ($child[0]) {
636 case "import":
637 list(,$rawPath) = $child;
638 $rawPath = $this->reduce($rawPath);
639 if (!$this->compileImport($rawPath, $out)) {
640 $out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
641 }
642 break;
643 case "directive":
644 list(, $directive) = $child;
645 $s = "@" . $directive->name;
646 if (!empty($directive->value)) {
647 $s .= " " . $this->compileValue($directive->value);
648 }
649 $this->compileNestedBlock($directive, array($s));
650 break;
651 case "media":
652 $this->compileMedia($child[1]);
653 break;
654 case "block":
655 $this->compileBlock($child[1]);
656 break;
657 case "charset":
658 $out->lines[] = "@charset ".$this->compileValue($child[1]).";";
659 break;
660 case "assign":
661 list(,$name, $value) = $child;
662 if ($name[0] == "var") {
663 $isDefault = !empty($child[3]);
664
665 if ($isDefault) {
666 $existingValue = $this->get($name[1], true);
667 $shouldSet = $existingValue === true || $existingValue == self::$null;
668 }
669
670 if (!$isDefault || $shouldSet) {
671 $this->set($name[1], $this->reduce($value));
672 }
673 break;
674 }
675
676 // if the value reduces to null from something else then
677 // the property should be discarded
678 if ($value[0] != "null") {
679 $value = $this->reduce($value);
680 if ($value[0] == "null") {
681 break;
682 }
683 }
684
685 $compiledValue = $this->compileValue($value);
686 $out->lines[] = $this->formatter->property(
687 $this->compileValue($name),
688 $compiledValue);
689 break;
690 case "comment":
691 $out->lines[] = $child[1];
692 break;
693 case "mixin":
694 case "function":
695 list(,$block) = $child;
696 $this->set(self::$namespaces[$block->type] . $block->name, $block);
697 break;
698 case "extend":
699 list(, $selectors) = $child;
700 foreach ($selectors as $sel) {
701 // only use the first one
702 $sel = current($this->evalSelector($sel));
703 $this->pushExtends($sel, $out->selectors);
704 }
705 break;
706 case "if":
707 list(, $if) = $child;
708 if ($this->isTruthy($this->reduce($if->cond, true))) {
709 return $this->compileChildren($if->children, $out);
710 } else {
711 foreach ($if->cases as $case) {
712 if ($case->type == "else" ||
713 $case->type == "elseif" && $this->isTruthy($this->reduce($case->cond)))
714 {
715 return $this->compileChildren($case->children, $out);
716 }
717 }
718 }
719 break;
720 case "return":
721 return $this->reduce($child[1], true);
722 case "each":
723 list(,$each) = $child;
724 $list = $this->coerceList($this->reduce($each->list));
725 foreach ($list[2] as $item) {
726 $this->pushEnv();
727 $this->set($each->var, $item);
728 // TODO: allow return from here
729 $this->compileChildren($each->children, $out);
730 $this->popEnv();
731 }
732 break;
733 case "while":
734 list(,$while) = $child;
735 while ($this->isTruthy($this->reduce($while->cond, true))) {
736 $ret = $this->compileChildren($while->children, $out);
737 if ($ret) return $ret;
738 }
739 break;
740 case "for":
741 list(,$for) = $child;
742 $start = $this->reduce($for->start, true);
743 $start = $start[1];
744 $end = $this->reduce($for->end, true);
745 $end = $end[1];
746 $d = $start < $end ? 1 : -1;
747
748 while (true) {
749 if ((!$for->until && $start - $d == $end) ||
750 ($for->until && $start == $end))
751 {
752 break;
753 }
754
755 $this->set($for->var, array("number", $start, ""));
756 $start += $d;
757
758 $ret = $this->compileChildren($for->children, $out);
759 if ($ret) return $ret;
760 }
761
762 break;
763 case "nestedprop":
764 list(,$prop) = $child;
765 $prefixed = array();
766 $prefix = $this->compileValue($prop->prefix) . "-";
767 foreach ($prop->children as $child) {
768 if ($child[0] == "assign") {
769 array_unshift($child[1][2], $prefix);
770 }
771 if ($child[0] == "nestedprop") {
772 array_unshift($child[1]->prefix[2], $prefix);
773 }
774 $prefixed[] = $child;
775 }
776 $this->compileChildren($prefixed, $out);
777 break;
778 case "include": // including a mixin
779 list(,$name, $argValues, $content) = $child;
780 $mixin = $this->get(self::$namespaces["mixin"] . $name, false);
781 if (!$mixin) {
782 $this->throwError("Undefined mixin $name");
783 }
784
785 $callingScope = $this->env;
786
787 // push scope, apply args
788 $this->pushEnv();
789 if ($this->env->depth > 0) {
790 $this->env->depth--;
791 }
792
793 if (isset($content)) {
794 $content->scope = $callingScope;
795 $this->setRaw(self::$namespaces["special"] . "content", $content);
796 }
797
798 if (isset($mixin->args)) {
799 $this->applyArguments($mixin->args, $argValues);
800 }
801
802 foreach ($mixin->children as $child) {
803 $this->compileChild($child, $out);
804 }
805
806 $this->popEnv();
807
808 break;
809 case "mixin_content":
810 $content = $this->get(self::$namespaces["special"] . "content");
811 if (!isset($content)) {
812 $this->throwError("Expected @content inside of mixin");
813 }
814
815 $strongTypes = array('include', 'block', 'for', 'while');
816 foreach ($content->children as $child) {
817 $this->storeEnv = (in_array($child[0], $strongTypes))
818 ? null
819 : $content->scope;
820
821 $this->compileChild($child, $out);
822 }
823
824 unset($this->storeEnv);
825 break;
826 case "debug":
827 list(,$value, $pos) = $child;
828 $line = $this->parser->getLineNo($pos);
829 $value = $this->compileValue($this->reduce($value, true));
830 fwrite(STDERR, "Line $line DEBUG: $value\n");
831 break;
832 default:
833 $this->throwError("unknown child type: $child[0]");
834 }
835 }
836
837 protected function expToString($exp) {
838 list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
839 $content = array($this->reduce($left));
840 if ($whiteLeft) $content[] = " ";
841 $content[] = $op;
842 if ($whiteRight) $content[] = " ";
843 $content[] = $this->reduce($right);
844 return array("string", "", $content);
845 }
846
847 protected function isTruthy($value) {
848 return $value != self::$false && $value != self::$null;
849 }
850
851 // should $value cause its operand to eval
852 protected function shouldEval($value) {
853 switch ($value[0]) {
854 case "exp":
855 if ($value[1] == "/") {
856 return $this->shouldEval($value[2], $value[3]);
857 }
858 case "var":
859 case "fncall":
860 return true;
861 }
862 return false;
863 }
864
865 protected function reduce($value, $inExp = false) {
866 list($type) = $value;
867 switch ($type) {
868 case "exp":
869 list(, $op, $left, $right, $inParens) = $value;
870 $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
871
872 $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
873
874 $left = $this->reduce($left, true);
875 $right = $this->reduce($right, true);
876
877 // only do division in special cases
878 if ($opName == "div" && !$inParens && !$inExp) {
879 if ($left[0] != "color" && $right[0] != "color") {
880 return $this->expToString($value);
881 }
882 }
883
884 $left = $this->coerceForExpression($left);
885 $right = $this->coerceForExpression($right);
886
887 $ltype = $left[0];
888 $rtype = $right[0];
889
890 // this tries:
891 // 1. op_[op name]_[left type]_[right type]
892 // 2. op_[left type]_[right type] (passing the op as first arg
893 // 3. op_[op name]
894 $fn = "op_${opName}_${ltype}_${rtype}";
895 if (is_callable(array($this, $fn)) ||
896 (($fn = "op_${ltype}_${rtype}") &&
897 is_callable(array($this, $fn)) &&
898 $passOp = true) ||
899 (($fn = "op_${opName}") &&
900 is_callable(array($this, $fn)) &&
901 $genOp = true))
902 {
903 $unitChange = false;
904 if (!isset($genOp) &&
905 $left[0] == "number" && $right[0] == "number")
906 {
907 if ($opName == "mod" && $right[2] != "") {
908 $this->throwError("Cannot modulo by a number with units: $right[1]$right[2].");
909 }
910
911 $unitChange = true;
912 $emptyUnit = $left[2] == "" || $right[2] == "";
913 $targetUnit = "" != $left[2] ? $left[2] : $right[2];
914
915 if ($opName != "mul") {
916 $left[2] = "" != $left[2] ? $left[2] : $targetUnit;
917 $right[2] = "" != $right[2] ? $right[2] : $targetUnit;
918 }
919
920 if ($opName != "mod") {
921 $left = $this->normalizeNumber($left);
922 $right = $this->normalizeNumber($right);
923 }
924
925 if ($opName == "div" && !$emptyUnit && $left[2] == $right[2]) {
926 $targetUnit = "";
927 }
928
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]) {
933 $left[2] = "";
934 $right[2] = "";
935 }
936 }
937
938 $shouldEval = $inParens || $inExp;
939 if (isset($passOp)) {
940 $out = $this->$fn($op, $left, $right, $shouldEval);
941 } else {
942 $out = $this->$fn($left, $right, $shouldEval);
943 }
944
945 if (isset($out)) {
946 if ($unitChange && $out[0] == "number") {
947 $out = $this->coerceUnit($out, $targetUnit);
948 }
949 return $out;
950 }
951 }
952
953 return $this->expToString($value);
954 case "unary":
955 list(, $op, $exp, $inParens) = $value;
956 $inExp = $inExp || $this->shouldEval($exp);
957
958 $exp = $this->reduce($exp);
959 if ($exp[0] == "number") {
960 switch ($op) {
961 case "+":
962 return $exp;
963 case "-":
964 $exp[1] *= -1;
965 return $exp;
966 }
967 }
968
969 if ($op == "not") {
970 if ($inExp || $inParens) {
971 if ($exp == self::$false) {
972 return self::$true;
973 } else {
974 return self::$false;
975 }
976 } else {
977 $op = $op . " ";
978 }
979 }
980
981 return array("string", "", array($op, $exp));
982 case "var":
983 list(, $name) = $value;
984 return $this->reduce($this->get($name));
985 case "list":
986 foreach ($value[2] as &$item) {
987 $item = $this->reduce($item);
988 }
989 return $value;
990 case "string":
991 foreach ($value[2] as &$item) {
992 if (is_array($item)) {
993 $item = $this->reduce($item);
994 }
995 }
996 return $value;
997 case "interpolate":
998 $value[1] = $this->reduce($value[1]);
999 return $value;
1000 case "fncall":
1001 list(,$name, $argValues) = $value;
1002
1003 // user defined function?
1004 $func = $this->get(self::$namespaces["function"] . $name, false);
1005 if ($func) {
1006 $this->pushEnv();
1007
1008 // set the args
1009 if (isset($func->args)) {
1010 $this->applyArguments($func->args, $argValues);
1011 }
1012
1013 // throw away lines and children
1014 $tmp = (object)array(
1015 "lines" => array(),
1016 "children" => array()
1017 );
1018 $ret = $this->compileChildren($func->children, $tmp);
1019 $this->popEnv();
1020
1021 return !isset($ret) ? self::$defaultValue : $ret;
1022 }
1023
1024 // built in function
1025 if ($this->callBuiltin($name, $argValues, $returnValue)) {
1026 return $returnValue;
1027 }
1028
1029 // need to flatten the arguments into a list
1030 $listArgs = array();
1031 foreach ((array)$argValues as $arg) {
1032 if (empty($arg[0])) {
1033 $listArgs[] = $this->reduce($arg[1]);
1034 }
1035 }
1036 return array("function", $name, array("list", ",", $listArgs));
1037 default:
1038 return $value;
1039 }
1040 }
1041
1042 public function normalizeValue($value) {
1043 $value = $this->coerceForExpression($this->reduce($value));
1044 list($type) = $value;
1045
1046 switch ($type) {
1047 case "list":
1048 $value = $this->extractInterpolation($value);
1049 if ($value[0] != "list") {
1050 return array("keyword", $this->compileValue($value));
1051 }
1052 foreach ($value[2] as $key => $item) {
1053 $value[2][$key] = $this->normalizeValue($item);
1054 }
1055 return $value;
1056 case "number":
1057 return $this->normalizeNumber($value);
1058 default:
1059 return $value;
1060 }
1061 }
1062
1063 // just does physical lengths for now
1064 protected function normalizeNumber($number) {
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");
1069 }
1070 return $number;
1071 }
1072
1073 // $number should be normalized
1074 protected function coerceUnit($number, $unit) {
1075 list(, $value, $baseUnit) = $number;
1076 if (isset(self::$unitTable[$baseUnit][$unit])) {
1077 $value = $value * self::$unitTable[$baseUnit][$unit];
1078 }
1079
1080 return array("number", $value, $unit);
1081 }
1082
1083 protected function op_add_number_number($left, $right) {
1084 return array("number", $left[1] + $right[1], $left[2]);
1085 }
1086
1087 protected function op_mul_number_number($left, $right) {
1088 return array("number", $left[1] * $right[1], $left[2]);
1089 }
1090
1091 protected function op_sub_number_number($left, $right) {
1092 return array("number", $left[1] - $right[1], $left[2]);
1093 }
1094
1095 protected function op_div_number_number($left, $right) {
1096 return array("number", $left[1] / $right[1], $left[2]);
1097 }
1098
1099 protected function op_mod_number_number($left, $right) {
1100 return array("number", $left[1] % $right[1], $left[2]);
1101 }
1102
1103 // adding strings
1104 protected function op_add($left, $right) {
1105 if ($strLeft = $this->coerceString($left)) {
1106 if ($right[0] == "string") {
1107 $right[1] = "";
1108 }
1109 $strLeft[2][] = $right;
1110 return $strLeft;
1111 }
1112
1113 if ($strRight = $this->coerceString($right)) {
1114 if ($left[0] == "string") {
1115 $left[1] = "";
1116 }
1117 array_unshift($strRight[2], $left);
1118 return $strRight;
1119 }
1120 }
1121
1122 protected function op_and($left, $right, $shouldEval) {
1123 if (!$shouldEval) return;
1124 if ($left != self::$false) return $right;
1125 return $left;
1126 }
1127
1128 protected function op_or($left, $right, $shouldEval) {
1129 if (!$shouldEval) return;
1130 if ($left != self::$false) return $left;
1131 return $right;
1132 }
1133
1134 protected function op_color_color($op, $left, $right) {
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;
1139 switch ($op) {
1140 case '+':
1141 $out[] = $lval + $rval;
1142 break;
1143 case '-':
1144 $out[] = $lval - $rval;
1145 break;
1146 case '*':
1147 $out[] = $lval * $rval;
1148 break;
1149 case '%':
1150 $out[] = $lval % $rval;
1151 break;
1152 case '/':
1153 if ($rval == 0) {
1154 $this->throwError("color: Can't divide by zero");
1155 }
1156 $out[] = $lval / $rval;
1157 break;
1158 case "==":
1159 return $this->op_eq($left, $right);
1160 case "!=":
1161 return $this->op_neq($left, $right);
1162 default:
1163 $this->throwError("color: unknown op $op");
1164 }
1165 }
1166
1167 if (isset($left[4])) $out[4] = $left[4];
1168 elseif (isset($right[4])) $out[4] = $right[4];
1169
1170 return $this->fixColor($out);
1171 }
1172
1173 protected function op_color_number($op, $left, $right) {
1174 $value = $right[1];
1175 return $this->op_color_color($op, $left,
1176 array("color", $value, $value, $value));
1177 }
1178
1179 protected function op_number_color($op, $left, $right) {
1180 $value = $left[1];
1181 return $this->op_color_color($op,
1182 array("color", $value, $value, $value), $right);
1183 }
1184
1185 protected function op_eq($left, $right) {
1186 if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
1187 $lStr[1] = "";
1188 $rStr[1] = "";
1189 return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr));
1190 }
1191
1192 return $this->toBool($left == $right);
1193 }
1194
1195 protected function op_neq($left, $right) {
1196 return $this->toBool($left != $right);
1197 }
1198
1199 protected function op_gte_number_number($left, $right) {
1200 return $this->toBool($left[1] >= $right[1]);
1201 }
1202
1203 protected function op_gt_number_number($left, $right) {
1204 return $this->toBool($left[1] > $right[1]);
1205 }
1206
1207 protected function op_lte_number_number($left, $right) {
1208 return $this->toBool($left[1] <= $right[1]);
1209 }
1210
1211 protected function op_lt_number_number($left, $right) {
1212 return $this->toBool($left[1] < $right[1]);
1213 }
1214
1215 public function toBool($thing) {
1216 return $thing ? self::$true : self::$false;
1217 }
1218
1232 protected function compileValue($value) {
1233 $value = $this->reduce($value);
1234
1235 list($type) = $value;
1236 switch ($type) {
1237 case "keyword":
1238 return $value[1];
1239 case "color":
1240 // [1] - red component (either number for a %)
1241 // [2] - green component
1242 // [3] - blue component
1243 // [4] - optional alpha component
1244 list(, $r, $g, $b) = $value;
1245
1246 $r = round($r);
1247 $g = round($g);
1248 $b = round($b);
1249
1250 if (count($value) == 5 && $value[4] != 1) { // rgba
1251 return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')';
1252 }
1253
1254 $h = sprintf("#%02x%02x%02x", $r, $g, $b);
1255
1256 // Converting hex color to short notation (e.g. #003399 to #039)
1257 if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
1258 $h = '#' . $h[1] . $h[3] . $h[5];
1259 }
1260
1261 return $h;
1262 case "number":
1263 return round($value[1], $this->numberPrecision) . $value[2];
1264 case "string":
1265 return $value[1] . $this->compileStringContent($value) . $value[1];
1266 case "function":
1267 $args = !empty($value[2]) ? $this->compileValue($value[2]) : "";
1268 return "$value[1]($args)";
1269 case "list":
1270 $value = $this->extractInterpolation($value);
1271 if ($value[0] != "list") return $this->compileValue($value);
1272
1273 list(, $delim, $items) = $value;
1274
1275 $filtered = array();
1276 foreach ($items as $item) {
1277 if ($item[0] == "null") continue;
1278 $filtered[] = $this->compileValue($item);
1279 }
1280
1281 return implode("$delim ", $filtered);
1282 case "interpolated": # node created by extractInterpolation
1283 list(, $interpolate, $left, $right) = $value;
1284 list(,, $whiteLeft, $whiteRight) = $interpolate;
1285
1286 $left = count($left[2]) > 0 ?
1287 $this->compileValue($left).$whiteLeft : "";
1288
1289 $right = count($right[2]) > 0 ?
1290 $whiteRight.$this->compileValue($right) : "";
1291
1292 return $left.$this->compileValue($interpolate).$right;
1293
1294 case "interpolate": # raw parse node
1295 list(, $exp) = $value;
1296
1297 // strip quotes if it's a string
1298 $reduced = $this->reduce($exp);
1299 switch ($reduced[0]) {
1300 case "string":
1301 $reduced = array("keyword",
1302 $this->compileStringContent($reduced));
1303 break;
1304 case "null":
1305 $reduced = array("keyword", "");
1306 }
1307
1308 return $this->compileValue($reduced);
1309 case "null":
1310 return "null";
1311 default:
1312 $this->throwError("unknown value type: $type");
1313 }
1314 }
1315
1316 protected function compileStringContent($string) {
1317 $parts = array();
1318 foreach ($string[2] as $part) {
1319 if (is_array($part)) {
1320 $parts[] = $this->compileValue($part);
1321 } else {
1322 $parts[] = $part;
1323 }
1324 }
1325
1326 return implode($parts);
1327 }
1328
1329 // doesn't need to be recursive, compileValue will handle that
1330 protected function extractInterpolation($list) {
1331 $items = $list[2];
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);
1337 }
1338 }
1339 return $list;
1340 }
1341
1342 // find the final set of selectors
1343 protected function multiplySelectors($env) {
1344 $envs = array();
1345 while (null !== $env) {
1346 if (!empty($env->selectors)) {
1347 $envs[] = $env;
1348 }
1349 $env = $env->parent;
1350 };
1351
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) {
1358 $selectors[] = $this->joinSelectors($parent, $selector);
1359 }
1360 }
1361 $parentSelectors = $selectors;
1362 }
1363
1364 return $selectors;
1365 }
1366
1367 // looks for & to replace, or append parent before child
1368 protected function joinSelectors($parent, $child) {
1369 $setSelf = false;
1370 $out = array();
1371 foreach ($child as $part) {
1372 $newPart = array();
1373 foreach ($part as $p) {
1374 if ($p == self::$selfSelector) {
1375 $setSelf = true;
1376 foreach ($parent as $i => $parentPart) {
1377 if ($i > 0) {
1378 $out[] = $newPart;
1379 $newPart = array();
1380 }
1381
1382 foreach ($parentPart as $pp) {
1383 $newPart[] = $pp;
1384 }
1385 }
1386 } else {
1387 $newPart[] = $p;
1388 }
1389 }
1390
1391 $out[] = $newPart;
1392 }
1393
1394 return $setSelf ? $out : array_merge($parent, $child);
1395 }
1396
1397 protected function multiplyMedia($env, $childQueries = null) {
1398 if (!isset($env) ||
1399 !empty($env->block->type) && $env->block->type != "media")
1400 {
1401 return $childQueries;
1402 }
1403
1404 // plain old block, skip
1405 if (empty($env->block->type)) {
1406 return $this->multiplyMedia($env->parent, $childQueries);
1407 }
1408
1409 $parentQueries = $env->block->queryList;
1410 if ($childQueries == null) {
1411 $childQueries = $parentQueries;
1412 } else {
1413 $originalQueries = $childQueries;
1414 $childQueries = array();
1415
1416 foreach ($parentQueries as $parentQuery){
1417 foreach ($originalQueries as $childQuery) {
1418 $childQueries []= array_merge($parentQuery, $childQuery);
1419 }
1420 }
1421 }
1422
1423 return $this->multiplyMedia($env->parent, $childQueries);
1424 }
1425
1426 // convert something to list
1427 protected function coerceList($item, $delim = ",") {
1428 if (isset($item) && $item[0] == "list") {
1429 return $item;
1430 }
1431
1432 return array("list", $delim, !isset($item) ? array(): array($item));
1433 }
1434
1435 protected function applyArguments($argDef, $argValues) {
1436 $hasVariable = false;
1437 $args = array();
1438 foreach ($argDef as $i => $arg) {
1439 list($name, $default, $isVariable) = $argDef[$i];
1440 $args[$name] = array($i, $name, $default, $isVariable);
1441 $hasVariable |= $isVariable;
1442 }
1443
1444 $keywordArgs = array();
1445 $deferredKeywordArgs = array();
1446 $remaining = array();
1447 // assign the keyword args
1448 foreach ((array) $argValues as $arg) {
1449 if (!empty($arg[0])) {
1450 if (!isset($args[$arg[0][1]])) {
1451 if ($hasVariable) {
1452 $deferredKeywordArgs[$arg[0][1]] = $arg[1];
1453 } else {
1454 $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
1455 }
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]);
1458 } else {
1459 $keywordArgs[$arg[0][1]] = $arg[1];
1460 }
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;
1469 } else {
1470 $remaining[] = $item;
1471 }
1472 }
1473 } else {
1474 $remaining[] = $val;
1475 }
1476 } else {
1477 $remaining[] = $arg[1];
1478 }
1479 }
1480
1481 foreach ($args as $arg) {
1482 list($i, $name, $default, $isVariable) = $arg;
1483 if ($isVariable) {
1484 $val = array("list", ",", array());
1485 for ($count = count($remaining); $i < $count; $i++) {
1486 $val[2][] = $remaining[$i];
1487 }
1488 foreach ($deferredKeywordArgs as $itemName => $item) {
1489 $val[2][$itemName] = $item;
1490 }
1491 } elseif (isset($remaining[$i])) {
1492 $val = $remaining[$i];
1493 } elseif (isset($keywordArgs[$name])) {
1494 $val = $keywordArgs[$name];
1495 } elseif (!empty($default)) {
1496 $val = $default;
1497 } else {
1498 $this->throwError("Missing argument $name");
1499 }
1500
1501 $this->set($name, $this->reduce($val, true), true);
1502 }
1503 }
1504
1505 protected function pushEnv($block=null) {
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;
1511
1512 $this->env = $env;
1513 return $env;
1514 }
1515
1516 protected function normalizeName($name) {
1517 return str_replace("-", "_", $name);
1518 }
1519
1520 protected function getStoreEnv() {
1521 return isset($this->storeEnv) ? $this->storeEnv : $this->env;
1522 }
1523
1524 protected function set($name, $value, $shadow=false) {
1525 $name = $this->normalizeName($name);
1526
1527 if ($shadow) {
1528 $this->setRaw($name, $value);
1529 } else {
1530 $this->setExisting($name, $value);
1531 }
1532 }
1533
1534 protected function setExisting($name, $value, $env = null) {
1535 if (!isset($env)) $env = $this->getStoreEnv();
1536
1537 if (isset($env->store[$name]) || !isset($env->parent)) {
1538 $env->store[$name] = $value;
1539 } else {
1540 $this->setExisting($name, $value, $env->parent);
1541 }
1542 }
1543
1544 protected function setRaw($name, $value) {
1545 $env = $this->getStoreEnv();
1546 $env->store[$name] = $value;
1547 }
1548
1549 public function get($name, $defaultValue = null, $env = null) {
1550 $name = $this->normalizeName($name);
1551
1552 if (!isset($env)) $env = $this->getStoreEnv();
1553 if (!isset($defaultValue)) $defaultValue = self::$defaultValue;
1554
1555 if (isset($env->store[$name])) {
1556 return $env->store[$name];
1557 } elseif (isset($env->parent)) {
1558 return $this->get($name, $defaultValue, $env->parent);
1559 }
1560
1561 return $defaultValue; // found nothing
1562 }
1563
1564 protected function injectVariables(array $args)
1565 {
1566 if (empty($args)) {
1567 return;
1568 }
1569
1570 $parser = new scss_parser(__METHOD__, false);
1571
1572 foreach ($args as $name => $strValue) {
1573 if ($name[0] === '$') {
1574 $name = substr($name, 1);
1575 }
1576
1577 $parser->env = null;
1578 $parser->count = 0;
1579 $parser->buffer = (string) $strValue;
1580 $parser->inParens = false;
1581 $parser->eatWhiteDefault = true;
1582 $parser->insertComments = true;
1583
1584 if ( ! $parser->valueList($value)) {
1585 throw new Exception("failed to parse passed in variable $name: $strValue");
1586 }
1587
1588 $this->set($name, $value);
1589 }
1590 }
1591
1597 public function setVariables(array $variables)
1598 {
1599 $this->registeredVars = array_merge($this->registeredVars, $variables);
1600 }
1601
1607 public function unsetVariable($name)
1608 {
1609 unset($this->registeredVars[$name]);
1610 }
1611
1612 protected function popEnv() {
1613 $env = $this->env;
1614 $this->env = $this->env->parent;
1615 return $env;
1616 }
1617
1618 public function getParsedFiles() {
1619 return $this->parsedFiles;
1620 }
1621
1622 public function addImportPath($path) {
1623 $this->importPaths[] = $path;
1624 }
1625
1626 public function setImportPaths($path) {
1627 $this->importPaths = (array)$path;
1628 }
1629
1630 public function setNumberPrecision($numberPrecision) {
1631 $this->numberPrecision = $numberPrecision;
1632 }
1633
1634 public function setFormatter($formatterName) {
1635 $this->formatter = $formatterName;
1636 }
1637
1638 public function registerFunction($name, $func) {
1639 $this->userFunctions[$this->normalizeName($name)] = $func;
1640 }
1641
1642 public function unregisterFunction($name) {
1643 unset($this->userFunctions[$this->normalizeName($name)]);
1644 }
1645
1646 protected function importFile($path, $out) {
1647 // see if tree is cached
1648 $realPath = realpath($path);
1649 if (isset($this->importCache[$realPath])) {
1650 $tree = $this->importCache[$realPath];
1651 } else {
1652 $code = file_get_contents($path);
1653 $parser = new scss_parser($path, false);
1654 $tree = $parser->parse($code);
1655 $this->parsedFiles[] = $path;
1656
1657 $this->importCache[$realPath] = $tree;
1658 }
1659
1660 $pi = pathinfo($path);
1661 array_unshift($this->importPaths, $pi['dirname']);
1662 $this->compileChildren($tree->children, $out);
1663 array_shift($this->importPaths);
1664 }
1665
1666 // results the file path for an import url if it exists
1667 public function findImport($url) {
1668 $urls = array();
1669
1670 // for "normal" scss imports (ignore vanilla css and external requests)
1671 if (!preg_match('/\.css|^http:\/\/$/', $url)) {
1672 // try both normal and the _partial filename
1673 $urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url));
1674 }
1675
1676 foreach ($this->importPaths as $dir) {
1677 if (is_string($dir)) {
1678 // check urls for normal import paths
1679 foreach ($urls as $full) {
1680 $full = $dir .
1681 (!empty($dir) && substr($dir, -1) != '/' ? '/' : '') .
1682 $full;
1683
1684 if ($this->fileExists($file = $full.'.scss') ||
1685 $this->fileExists($file = $full))
1686 {
1687 return $file;
1688 }
1689 }
1690 } else {
1691 // check custom callback for import path
1692 $file = call_user_func($dir,$url,$this);
1693 if ($file !== null) {
1694 return $file;
1695 }
1696 }
1697 }
1698
1699 return null;
1700 }
1701
1702 protected function fileExists($name) {
1703 return is_file($name);
1704 }
1705
1706 protected function callBuiltin($name, $args, &$returnValue) {
1707 // try a lib function
1708 $name = $this->normalizeName($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);
1716 }
1717 $returnValue = call_user_func($f, $sorted, $this);
1718 } elseif (isset($this->userFunctions[$name])) {
1719 // see if we can find a user function
1720 $fn = $this->userFunctions[$name];
1721
1722 foreach ($args as &$val) {
1723 $val = $this->reduce($val[1], true);
1724 }
1725
1726 $returnValue = call_user_func($fn, $args, $this);
1727 }
1728
1729 if (isset($returnValue)) {
1730 // coerce a php value into a scss one
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);
1737 }
1738
1739 return true;
1740 }
1741
1742 return false;
1743 }
1744
1745 // sorts any keyword arguments
1746 // TODO: merge with apply arguments
1747 protected function sortArgs($prototype, $args) {
1748 $keyArgs = array();
1749 $posArgs = array();
1750
1751 foreach ($args as $arg) {
1752 list($key, $value) = $arg;
1753 $key = $key[1];
1754 if (empty($key)) {
1755 $posArgs[] = $value;
1756 } else {
1757 $keyArgs[$key] = $value;
1758 }
1759 }
1760
1761 if (!isset($prototype)) return $posArgs;
1762
1763 $finalArgs = array();
1764 foreach ($prototype as $i => $names) {
1765 if (isset($posArgs[$i])) {
1766 $finalArgs[] = $posArgs[$i];
1767 continue;
1768 }
1769
1770 $set = false;
1771 foreach ((array)$names as $name) {
1772 if (isset($keyArgs[$name])) {
1773 $finalArgs[] = $keyArgs[$name];
1774 $set = true;
1775 break;
1776 }
1777 }
1778
1779 if (!$set) {
1780 $finalArgs[] = null;
1781 }
1782 }
1783
1784 return $finalArgs;
1785 }
1786
1787 protected function coerceForExpression($value) {
1788 if ($color = $this->coerceColor($value)) {
1789 return $color;
1790 }
1791
1792 return $value;
1793 }
1794
1795 protected function coerceColor($value) {
1796 switch ($value[0]) {
1797 case "color": return $value;
1798 case "keyword":
1799 $name = $value[1];
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]);
1805 }
1806 return null;
1807 }
1808
1809 return null;
1810 }
1811
1812 protected function coerceString($value) {
1813 switch ($value[0]) {
1814 case "string":
1815 return $value;
1816 case "keyword":
1817 return array("string", "", array($value[1]));
1818 }
1819 return null;
1820 }
1821
1822 public function assertList($value) {
1823 if ($value[0] != "list")
1824 $this->throwError("expecting list");
1825 return $value;
1826 }
1827
1828 public function assertColor($value) {
1829 if ($color = $this->coerceColor($value)) return $color;
1830 $this->throwError("expecting color");
1831 }
1832
1833 public function assertNumber($value) {
1834 if ($value[0] != "number")
1835 $this->throwError("expecting number");
1836 return $value[1];
1837 }
1838
1839 protected function coercePercent($value) {
1840 if ($value[0] == "number") {
1841 if ($value[2] == "%") {
1842 return $value[1] / 100;
1843 }
1844 return $value[1];
1845 }
1846 return 0;
1847 }
1848
1849 // make sure a color's components don't go out of bounds
1850 protected function fixColor($c) {
1851 foreach (range(1, 3) as $i) {
1852 if ($c[$i] < 0) $c[$i] = 0;
1853 if ($c[$i] > 255) $c[$i] = 255;
1854 }
1855
1856 return $c;
1857 }
1858
1859 public function toHSL($red, $green, $blue) {
1860 $min = min($red, $green, $blue);
1861 $max = max($red, $green, $blue);
1862
1863 $l = $min + $max;
1864
1865 if ($min == $max) {
1866 $s = $h = 0;
1867 } else {
1868 $d = $max - $min;
1869
1870 if ($l < 255)
1871 $s = $d / $l;
1872 else
1873 $s = $d / (510 - $l);
1874
1875 if ($red == $max)
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;
1881 }
1882
1883 return array('hsl', fmod($h, 360), $s * 100, $l / 5.1);
1884 }
1885
1886 public function hueToRGB($m1, $m2, $h) {
1887 if ($h < 0)
1888 $h += 1;
1889 elseif ($h > 1)
1890 $h -= 1;
1891
1892 if ($h * 6 < 1)
1893 return $m1 + ($m2 - $m1) * $h * 6;
1894
1895 if ($h * 2 < 1)
1896 return $m2;
1897
1898 if ($h * 3 < 2)
1899 return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
1900
1901 return $m1;
1902 }
1903
1904 // H from 0 to 360, S and L from 0 to 100
1905 public function toRGB($hue, $saturation, $lightness) {
1906 if ($hue < 0) {
1907 $hue += 360;
1908 }
1909
1910 $h = $hue / 360;
1911 $s = min(100, max(0, $saturation)) / 100;
1912 $l = min(100, max(0, $lightness)) / 100;
1913
1914 $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
1915 $m1 = $l * 2 - $m2;
1916
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;
1920
1921 $out = array('color', $r, $g, $b);
1922 return $out;
1923 }
1924
1925 // Built in functions
1926
1927 protected static $lib_if = array("condition", "if-true", "if-false");
1928 protected function lib_if($args) {
1929 list($cond,$t, $f) = $args;
1930 if (!$this->isTruthy($cond)) return $f;
1931 return $t;
1932 }
1933
1934 protected static $lib_index = array("list", "value");
1935 protected function lib_index($args) {
1936 list($list, $value) = $args;
1937 $list = $this->assertList($list);
1938
1939 $values = array();
1940 foreach ($list[2] as $item) {
1941 $values[] = $this->normalizeValue($item);
1942 }
1943 $key = array_search($this->normalizeValue($value), $values);
1944
1945 return false === $key ? false : $key + 1;
1946 }
1947
1948 protected static $lib_rgb = array("red", "green", "blue");
1949 protected function lib_rgb($args) {
1950 list($r,$g,$b) = $args;
1951 return array("color", $r[1], $g[1], $b[1]);
1952 }
1953
1954 protected static $lib_rgba = array(
1955 array("red", "color"),
1956 "green", "blue", "alpha");
1957 protected function lib_rgba($args) {
1958 if ($color = $this->coerceColor($args[0])) {
1959 $num = !isset($args[1]) ? $args[3] : $args[1];
1960 $alpha = $this->assertNumber($num);
1961 $color[4] = $alpha;
1962 return $color;
1963 }
1964
1965 list($r,$g,$b, $a) = $args;
1966 return array("color", $r[1], $g[1], $b[1], $a[1]);
1967 }
1968
1969 // helper function for adjust_color, change_color, and scale_color
1970 protected function alter_color($args, $fn) {
1971 $color = $this->assertColor($args[0]);
1972
1973 foreach (array(1,2,3,7) as $i) {
1974 if (isset($args[$i])) {
1975 $val = $this->assertNumber($args[$i]);
1976 $ii = $i == 7 ? 4 : $i; // alpha
1977 $color[$ii] =
1978 $this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
1979 }
1980 }
1981
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])) {
1986 $val = $this->assertNumber($args[$i]);
1987 $hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
1988 }
1989 }
1990
1991 $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
1992 if (isset($color[4])) $rgb[4] = $color[4];
1993 $color = $rgb;
1994 }
1995
1996 return $color;
1997 }
1998
1999 protected static $lib_adjust_color = array(
2000 "color", "red", "green", "blue",
2001 "hue", "saturation", "lightness", "alpha"
2002 );
2003 protected function adjust_color_helper($base, $alter, $i) {
2004 return $base += $alter;
2005 }
2006 protected function lib_adjust_color($args) {
2007 return $this->alter_color($args, "adjust_color_helper");
2008 }
2009
2010 protected static $lib_change_color = array(
2011 "color", "red", "green", "blue",
2012 "hue", "saturation", "lightness", "alpha"
2013 );
2014 protected function change_color_helper($base, $alter, $i) {
2015 return $alter;
2016 }
2017 protected function lib_change_color($args) {
2018 return $this->alter_color($args, "change_color_helper");
2019 }
2020
2021 protected static $lib_scale_color = array(
2022 "color", "red", "green", "blue",
2023 "hue", "saturation", "lightness", "alpha"
2024 );
2025 protected function scale_color_helper($base, $scale, $i) {
2026 // 1,2,3 - rgb
2027 // 4, 5, 6 - hsl
2028 // 7 - a
2029 switch ($i) {
2030 case 1:
2031 case 2:
2032 case 3:
2033 $max = 255; break;
2034 case 4:
2035 $max = 360; break;
2036 case 7:
2037 $max = 1; break;
2038 default:
2039 $max = 100;
2040 }
2041
2042 $scale = $scale / 100;
2043 if ($scale < 0) {
2044 return $base * $scale + $base;
2045 } else {
2046 return ($max - $base) * $scale + $base;
2047 }
2048 }
2049 protected function lib_scale_color($args) {
2050 return $this->alter_color($args, "scale_color_helper");
2051 }
2052
2053 protected static $lib_ie_hex_str = array("color");
2054 protected function lib_ie_hex_str($args) {
2055 $color = $this->coerceColor($args[0]);
2056 $color[4] = isset($color[4]) ? round(255*$color[4]) : 255;
2057
2058 return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
2059 }
2060
2061 protected static $lib_red = array("color");
2062 protected function lib_red($args) {
2063 $color = $this->coerceColor($args[0]);
2064 return $color[1];
2065 }
2066
2067 protected static $lib_green = array("color");
2068 protected function lib_green($args) {
2069 $color = $this->coerceColor($args[0]);
2070 return $color[2];
2071 }
2072
2073 protected static $lib_blue = array("color");
2074 protected function lib_blue($args) {
2075 $color = $this->coerceColor($args[0]);
2076 return $color[3];
2077 }
2078
2079 protected static $lib_alpha = array("color");
2080 protected function lib_alpha($args) {
2081 if ($color = $this->coerceColor($args[0])) {
2082 return isset($color[4]) ? $color[4] : 1;
2083 }
2084
2085 // this might be the IE function, so return value unchanged
2086 return null;
2087 }
2088
2089 protected static $lib_opacity = array("color");
2090 protected function lib_opacity($args) {
2091 $value = $args[0];
2092 if ($value[0] === 'number') return null;
2093 return $this->lib_alpha($args);
2094 }
2095
2096 // mix two colors
2097 protected static $lib_mix = array("color-1", "color-2", "weight");
2098 protected function lib_mix($args) {
2099 list($first, $second, $weight) = $args;
2100 $first = $this->assertColor($first);
2101 $second = $this->assertColor($second);
2102
2103 if (!isset($weight)) {
2104 $weight = 0.5;
2105 } else {
2106 $weight = $this->coercePercent($weight);
2107 }
2108
2109 $firstAlpha = isset($first[4]) ? $first[4] : 1;
2110 $secondAlpha = isset($second[4]) ? $second[4] : 1;
2111
2112 $w = $weight * 2 - 1;
2113 $a = $firstAlpha - $secondAlpha;
2114
2115 $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
2116 $w2 = 1.0 - $w1;
2117
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],
2122 );
2123
2124 if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
2125 $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
2126 }
2127
2128 return $this->fixColor($new);
2129 }
2130
2131 protected static $lib_hsl = array("hue", "saturation", "lightness");
2132 protected function lib_hsl($args) {
2133 list($h, $s, $l) = $args;
2134 return $this->toRGB($h[1], $s[1], $l[1]);
2135 }
2136
2137 protected static $lib_hsla = array("hue", "saturation",
2138 "lightness", "alpha");
2139 protected function lib_hsla($args) {
2140 list($h, $s, $l, $a) = $args;
2141 $color = $this->toRGB($h[1], $s[1], $l[1]);
2142 $color[4] = $a[1];
2143 return $color;
2144 }
2145
2146 protected static $lib_hue = array("color");
2147 protected function lib_hue($args) {
2148 $color = $this->assertColor($args[0]);
2149 $hsl = $this->toHSL($color[1], $color[2], $color[3]);
2150 return array("number", $hsl[1], "deg");
2151 }
2152
2153 protected static $lib_saturation = array("color");
2154 protected function lib_saturation($args) {
2155 $color = $this->assertColor($args[0]);
2156 $hsl = $this->toHSL($color[1], $color[2], $color[3]);
2157 return array("number", $hsl[2], "%");
2158 }
2159
2160 protected static $lib_lightness = array("color");
2161 protected function lib_lightness($args) {
2162 $color = $this->assertColor($args[0]);
2163 $hsl = $this->toHSL($color[1], $color[2], $color[3]);
2164 return array("number", $hsl[3], "%");
2165 }
2166
2167 protected function adjustHsl($color, $idx, $amount) {
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];
2172 return $out;
2173 }
2174
2175 protected static $lib_adjust_hue = array("color", "degrees");
2176 protected function lib_adjust_hue($args) {
2177 $color = $this->assertColor($args[0]);
2178 $degrees = $this->assertNumber($args[1]);
2179 return $this->adjustHsl($color, 1, $degrees);
2180 }
2181
2182 protected static $lib_lighten = array("color", "amount");
2183 protected function lib_lighten($args) {
2184 $color = $this->assertColor($args[0]);
2185 $amount = 100*$this->coercePercent($args[1]);
2186 return $this->adjustHsl($color, 3, $amount);
2187 }
2188
2189 protected static $lib_darken = array("color", "amount");
2190 protected function lib_darken($args) {
2191 $color = $this->assertColor($args[0]);
2192 $amount = 100*$this->coercePercent($args[1]);
2193 return $this->adjustHsl($color, 3, -$amount);
2194 }
2195
2196 protected static $lib_saturate = array("color", "amount");
2197 protected function lib_saturate($args) {
2198 $value = $args[0];
2199 if ($value[0] === 'number') return null;
2200 $color = $this->assertColor($value);
2201 $amount = 100*$this->coercePercent($args[1]);
2202 return $this->adjustHsl($color, 2, $amount);
2203 }
2204
2205 protected static $lib_desaturate = array("color", "amount");
2206 protected function lib_desaturate($args) {
2207 $color = $this->assertColor($args[0]);
2208 $amount = 100*$this->coercePercent($args[1]);
2209 return $this->adjustHsl($color, 2, -$amount);
2210 }
2211
2212 protected static $lib_grayscale = array("color");
2213 protected function lib_grayscale($args) {
2214 $value = $args[0];
2215 if ($value[0] === 'number') return null;
2216 return $this->adjustHsl($this->assertColor($value), 2, -100);
2217 }
2218
2219 protected static $lib_complement = array("color");
2220 protected function lib_complement($args) {
2221 return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
2222 }
2223
2224 protected static $lib_invert = array("color");
2225 protected function lib_invert($args) {
2226 $value = $args[0];
2227 if ($value[0] === 'number') return null;
2228 $color = $this->assertColor($value);
2229 $color[1] = 255 - $color[1];
2230 $color[2] = 255 - $color[2];
2231 $color[3] = 255 - $color[3];
2232 return $color;
2233 }
2234
2235 // increases opacity by amount
2236 protected static $lib_opacify = array("color", "amount");
2237 protected function lib_opacify($args) {
2238 $color = $this->assertColor($args[0]);
2239 $amount = $this->coercePercent($args[1]);
2240
2241 $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
2242 $color[4] = min(1, max(0, $color[4]));
2243 return $color;
2244 }
2245
2246 protected static $lib_fade_in = array("color", "amount");
2247 protected function lib_fade_in($args) {
2248 return $this->lib_opacify($args);
2249 }
2250
2251 // decreases opacity by amount
2252 protected static $lib_transparentize = array("color", "amount");
2253 protected function lib_transparentize($args) {
2254 $color = $this->assertColor($args[0]);
2255 $amount = $this->coercePercent($args[1]);
2256
2257 $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
2258 $color[4] = min(1, max(0, $color[4]));
2259 return $color;
2260 }
2261
2262 protected static $lib_fade_out = array("color", "amount");
2263 protected function lib_fade_out($args) {
2264 return $this->lib_transparentize($args);
2265 }
2266
2267 protected static $lib_unquote = array("string");
2268 protected function lib_unquote($args) {
2269 $str = $args[0];
2270 if ($str[0] == "string") $str[1] = "";
2271 return $str;
2272 }
2273
2274 protected static $lib_quote = array("string");
2275 protected function lib_quote($args) {
2276 $value = $args[0];
2277 if ($value[0] == "string" && !empty($value[1]))
2278 return $value;
2279 return array("string", '"', array($value));
2280 }
2281
2282 protected static $lib_percentage = array("value");
2283 protected function lib_percentage($args) {
2284 return array("number",
2285 $this->coercePercent($args[0]) * 100,
2286 "%");
2287 }
2288
2289 protected static $lib_round = array("value");
2290 protected function lib_round($args) {
2291 $num = $args[0];
2292 $num[1] = round($num[1]);
2293 return $num;
2294 }
2295
2296 protected static $lib_floor = array("value");
2297 protected function lib_floor($args) {
2298 $num = $args[0];
2299 $num[1] = floor($num[1]);
2300 return $num;
2301 }
2302
2303 protected static $lib_ceil = array("value");
2304 protected function lib_ceil($args) {
2305 $num = $args[0];
2306 $num[1] = ceil($num[1]);
2307 return $num;
2308 }
2309
2310 protected static $lib_abs = array("value");
2311 protected function lib_abs($args) {
2312 $num = $args[0];
2313 $num[1] = abs($num[1]);
2314 return $num;
2315 }
2316
2317 protected function lib_min($args) {
2318 $numbers = $this->getNormalizedNumbers($args);
2319 $min = null;
2320 foreach ($numbers as $key => $number) {
2321 if (null === $min || $number[1] <= $min[1]) {
2322 $min = array($key, $number[1]);
2323 }
2324 }
2325
2326 return $args[$min[0]];
2327 }
2328
2329 protected function lib_max($args) {
2330 $numbers = $this->getNormalizedNumbers($args);
2331 $max = null;
2332 foreach ($numbers as $key => $number) {
2333 if (null === $max || $number[1] >= $max[1]) {
2334 $max = array($key, $number[1]);
2335 }
2336 }
2337
2338 return $args[$max[0]];
2339 }
2340
2341 protected function getNormalizedNumbers($args) {
2342 $unit = null;
2343 $originalUnit = null;
2344 $numbers = array();
2345 foreach ($args as $key => $item) {
2346 if ('number' != $item[0]) {
2347 $this->throwError("%s is not a number", $item[0]);
2348 }
2349 $number = $this->normalizeNumber($item);
2350
2351 if (null === $unit) {
2352 $unit = $number[2];
2353 $originalUnit = $item[2];
2354 } elseif ($unit !== $number[2]) {
2355 $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
2356 }
2357
2358 $numbers[$key] = $number;
2359 }
2360
2361 return $numbers;
2362 }
2363
2364 protected static $lib_length = array("list");
2365 protected function lib_length($args) {
2366 $list = $this->coerceList($args[0]);
2367 return count($list[2]);
2368 }
2369
2370 protected static $lib_nth = array("list", "n");
2371 protected function lib_nth($args) {
2372 $list = $this->coerceList($args[0]);
2373 $n = $this->assertNumber($args[1]) - 1;
2374 return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
2375 }
2376
2377 protected function listSeparatorForJoin($list1, $sep) {
2378 if (!isset($sep)) return $list1[1];
2379 switch ($this->compileValue($sep)) {
2380 case "comma":
2381 return ",";
2382 case "space":
2383 return "";
2384 default:
2385 return $list1[1];
2386 }
2387 }
2388
2389 protected static $lib_join = array("list1", "list2", "separator");
2390 protected function lib_join($args) {
2391 list($list1, $list2, $sep) = $args;
2392 $list1 = $this->coerceList($list1, " ");
2393 $list2 = $this->coerceList($list2, " ");
2394 $sep = $this->listSeparatorForJoin($list1, $sep);
2395 return array("list", $sep, array_merge($list1[2], $list2[2]));
2396 }
2397
2398 protected static $lib_append = array("list", "val", "separator");
2399 protected function lib_append($args) {
2400 list($list1, $value, $sep) = $args;
2401 $list1 = $this->coerceList($list1, " ");
2402 $sep = $this->listSeparatorForJoin($list1, $sep);
2403 return array("list", $sep, array_merge($list1[2], array($value)));
2404 }
2405
2406 protected function lib_zip($args) {
2407 foreach ($args as $arg) {
2408 $this->assertList($arg);
2409 }
2410
2411 $lists = array();
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];
2418 } else {
2419 break 2;
2420 }
2421 }
2422 $lists[] = $list;
2423 }
2424
2425 return array("list", ",", $lists);
2426 }
2427
2428 protected static $lib_type_of = array("value");
2429 protected function lib_type_of($args) {
2430 $value = $args[0];
2431 switch ($value[0]) {
2432 case "keyword":
2433 if ($value == self::$true || $value == self::$false) {
2434 return "bool";
2435 }
2436
2437 if ($this->coerceColor($value)) {
2438 return "color";
2439 }
2440
2441 return "string";
2442 default:
2443 return $value[0];
2444 }
2445 }
2446
2447 protected static $lib_unit = array("number");
2448 protected function lib_unit($args) {
2449 $num = $args[0];
2450 if ($num[0] == "number") {
2451 return array("string", '"', array($num[2]));
2452 }
2453 return "";
2454 }
2455
2456 protected static $lib_unitless = array("number");
2457 protected function lib_unitless($args) {
2458 $value = $args[0];
2459 return $value[0] == "number" && empty($value[2]);
2460 }
2461
2462 protected static $lib_comparable = array("number-1", "number-2");
2463 protected function lib_comparable($args) {
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"');
2467 }
2468
2469 $number1 = $this->normalizeNumber($number1);
2470 $number2 = $this->normalizeNumber($number2);
2471
2472 return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == "";
2473 }
2474
2480 protected function lib_counter($args) {
2481 $list = array_map(array($this, 'compileValue'), $args);
2482 return array('string', '', array('counter(' . implode(',', $list) . ')'));
2483 }
2484
2485 public function throwError($msg = null) {
2486 if (func_num_args() > 1) {
2487 $msg = call_user_func_array("sprintf", func_get_args());
2488 }
2489
2490 if ($this->sourcePos >= 0 && isset($this->sourceParser)) {
2491 $this->sourceParser->throwParseError($msg, $this->sourcePos);
2492 }
2493
2494 throw new Exception($msg);
2495 }
2496
2502 static protected $cssColors = array(
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',
2510 'black' => '0,0,0',
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',
2622 'red' => '255,0,0',
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'
2651 );
2652 }
2653
2659 class scss_parser {
2660 static protected $precedence = array(
2661 "or" => 0,
2662 "and" => 1,
2663
2664 '==' => 2,
2665 '!=' => 2,
2666 '<=' => 2,
2667 '>=' => 2,
2668 '=' => 2,
2669 '<' => 3,
2670 '>' => 2,
2671
2672 '+' => 3,
2673 '-' => 3,
2674 '*' => 4,
2675 '/' => 4,
2676 '%' => 4,
2677 );
2678
2679 static protected $operators = array("+", "-", "*", "/", "%",
2680 "==", "!=", "<=", ">=", "<", ">", "and", "or");
2681
2682 static protected $operatorStr;
2683 static protected $whitePattern;
2684 static protected $commentMulti;
2685
2686 static protected $commentSingle = "//";
2687 static protected $commentMultiLeft = "/*";
2688 static protected $commentMultiRight = "*/";
2689
2696 public function __construct($sourceName = null, $rootParser = true) {
2697 $this->sourceName = $sourceName;
2698 $this->rootParser = $rootParser;
2699
2700 if (empty(self::$operatorStr)) {
2701 self::$operatorStr = $this->makeOperatorStr(self::$operators);
2702
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';
2708 }
2709 }
2710
2711 static protected function makeOperatorStr($operators) {
2712 return '('.implode('|', array_map(array('scss_parser','preg_quote'),
2713 $operators)).')';
2714 }
2715
2723 public function parse($buffer)
2724 {
2725 $this->count = 0;
2726 $this->env = null;
2727 $this->inParens = false;
2728 $this->eatWhiteDefault = true;
2729 $this->insertComments = true;
2730 $this->buffer = $buffer;
2731
2732 $this->pushBlock(null); // root block
2733 $this->whitespace();
2734
2735 while (false !== $this->parseChunk())
2736 ;
2737
2738 if ($this->count != strlen($this->buffer)) {
2739 $this->throwParseError();
2740 }
2741
2742 if (!empty($this->env->parent)) {
2743 $this->throwParseError("unclosed block");
2744 }
2745
2746 $this->env->isRoot = true;
2747
2748 return $this->env;
2749 }
2750
2790 protected function parseChunk() {
2791 $s = $this->seek();
2792
2793 // the directives
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];
2798 return true;
2799 } else {
2800 $this->seek($s);
2801 }
2802
2803 if ($this->literal("@mixin") &&
2804 $this->keyword($mixinName) &&
2805 ($this->argumentDef($args) || true) &&
2806 $this->literal("{"))
2807 {
2808 $mixin = $this->pushSpecialBlock("mixin");
2809 $mixin->name = $mixinName;
2810 $mixin->args = $args;
2811 return true;
2812 } else {
2813 $this->seek($s);
2814 }
2815
2816 if ($this->literal("@include") &&
2817 $this->keyword($mixinName) &&
2818 ($this->literal("(") &&
2819 ($this->argValues($argValues) || true) &&
2820 $this->literal(")") || true) &&
2821 ($this->end() ||
2822 $this->literal("{") && $hasBlock = true))
2823 {
2824 $child = array("include",
2825 $mixinName, isset($argValues) ? $argValues : null, null);
2826
2827 if (!empty($hasBlock)) {
2828 $include = $this->pushSpecialBlock("include");
2829 $include->child = $child;
2830 } else {
2831 $this->append($child, $s);
2832 }
2833
2834 return true;
2835 } else {
2836 $this->seek($s);
2837 }
2838
2839 if ($this->literal("@import") &&
2840 $this->valueList($importPath) &&
2841 $this->end())
2842 {
2843 $this->append(array("import", $importPath), $s);
2844 return true;
2845 } else {
2846 $this->seek($s);
2847 }
2848
2849 if ($this->literal("@extend") &&
2850 $this->selectors($selector) &&
2851 $this->end())
2852 {
2853 $this->append(array("extend", $selector), $s);
2854 return true;
2855 } else {
2856 $this->seek($s);
2857 }
2858
2859 if ($this->literal("@function") &&
2860 $this->keyword($fnName) &&
2861 $this->argumentDef($args) &&
2862 $this->literal("{"))
2863 {
2864 $func = $this->pushSpecialBlock("function");
2865 $func->name = $fnName;
2866 $func->args = $args;
2867 return true;
2868 } else {
2869 $this->seek($s);
2870 }
2871
2872 if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) {
2873 $this->append(array("return", $retVal), $s);
2874 return true;
2875 } else {
2876 $this->seek($s);
2877 }
2878
2879 if ($this->literal("@each") &&
2880 $this->variable($varName) &&
2881 $this->literal("in") &&
2882 $this->valueList($list) &&
2883 $this->literal("{"))
2884 {
2885 $each = $this->pushSpecialBlock("each");
2886 $each->var = $varName[1];
2887 $each->list = $list;
2888 return true;
2889 } else {
2890 $this->seek($s);
2891 }
2892
2893 if ($this->literal("@while") &&
2894 $this->expression($cond) &&
2895 $this->literal("{"))
2896 {
2897 $while = $this->pushSpecialBlock("while");
2898 $while->cond = $cond;
2899 return true;
2900 } else {
2901 $this->seek($s);
2902 }
2903
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("{"))
2912 {
2913 $for = $this->pushSpecialBlock("for");
2914 $for->var = $varName[1];
2915 $for->start = $start;
2916 $for->end = $end;
2917 $for->until = isset($forUntil);
2918 return true;
2919 } else {
2920 $this->seek($s);
2921 }
2922
2923 if ($this->literal("@if") && $this->valueList($cond) && $this->literal("{")) {
2924 $if = $this->pushSpecialBlock("if");
2925 $if->cond = $cond;
2926 $if->cases = array();
2927 return true;
2928 } else {
2929 $this->seek($s);
2930 }
2931
2932 if (($this->literal("@debug") || $this->literal("@warn")) &&
2933 $this->valueList($value) &&
2934 $this->end()) {
2935 $this->append(array("debug", $value, $s), $s);
2936 return true;
2937 } else {
2938 $this->seek($s);
2939 }
2940
2941 if ($this->literal("@content") && $this->end()) {
2942 $this->append(array("mixin_content"), $s);
2943 return true;
2944 } else {
2945 $this->seek($s);
2946 }
2947
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;
2957 }
2958
2959 if (isset($else)) {
2960 $else->dontAppend = true;
2961 $if->cases[] = $else;
2962 return true;
2963 }
2964 }
2965
2966 $this->seek($s);
2967 }
2968
2969 if ($this->literal("@charset") &&
2970 $this->valueList($charset) && $this->end())
2971 {
2972 $this->append(array("charset", $charset), $s);
2973 return true;
2974 } else {
2975 $this->seek($s);
2976 }
2977
2978 // doesn't match built in directive, do generic one
2979 if ($this->literal("@", false) && $this->keyword($dirName) &&
2980 ($this->openString("{", $dirValue) || true) &&
2981 $this->literal("{"))
2982 {
2983 $directive = $this->pushSpecialBlock("directive");
2984 $directive->name = $dirName;
2985 if (isset($dirValue)) $directive->value = $dirValue;
2986 return true;
2987 }
2988
2989 $this->seek($s);
2990 return false;
2991 }
2992
2993 // property shortcut
2994 // captures most properties before having to parse a selector
2995 if ($this->keyword($name, false) &&
2996 $this->literal(": ") &&
2997 $this->valueList($value) &&
2998 $this->end())
2999 {
3000 $name = array("string", "", array($name));
3001 $this->append(array("assign", $name, $value), $s);
3002 return true;
3003 } else {
3004 $this->seek($s);
3005 }
3006
3007 // variable assigns
3008 if ($this->variable($name) &&
3009 $this->literal(":") &&
3010 $this->valueList($value) && $this->end())
3011 {
3012 // check for !default
3013 $defaultVar = $value[0] == "list" && $this->stripDefault($value);
3014 $this->append(array("assign", $name, $value, $defaultVar), $s);
3015 return true;
3016 } else {
3017 $this->seek($s);
3018 }
3019
3020 // misc
3021 if ($this->literal("-->")) {
3022 return true;
3023 }
3024
3025 // opening css block
3026 $oldComments = $this->insertComments;
3027 $this->insertComments = false;
3028 if ($this->selectors($selectors) && $this->literal("{")) {
3029 $this->pushBlock($selectors);
3030 $this->insertComments = $oldComments;
3031 return true;
3032 } else {
3033 $this->seek($s);
3034 }
3035 $this->insertComments = $oldComments;
3036
3037 // property assign, or nested assign
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;
3043 }
3044
3045 if ($this->literal("{")) {
3046 $propBlock = $this->pushSpecialBlock("nestedprop");
3047 $propBlock->prefix = $name;
3048 $foundSomething = true;
3049 } elseif ($foundSomething) {
3050 $foundSomething = $this->end();
3051 }
3052
3053 if ($foundSomething) {
3054 return true;
3055 }
3056
3057 $this->seek($s);
3058 } else {
3059 $this->seek($s);
3060 }
3061
3062 // closing a block
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);
3073 }
3074 return true;
3075 }
3076
3077 // extra stuff
3078 if ($this->literal(";") ||
3079 $this->literal("<!--"))
3080 {
3081 return true;
3082 }
3083
3084 return false;
3085 }
3086
3087 protected function stripDefault(&$value) {
3088 $def = end($value[2]);
3089 if ($def[0] == "keyword" && $def[1] == "!default") {
3090 array_pop($value[2]);
3091 $value = $this->flattenList($value);
3092 return true;
3093 }
3094
3095 if ($def[0] == "list") {
3096 return $this->stripDefault($value[2][count($value[2]) - 1]);
3097 }
3098
3099 return false;
3100 }
3101
3102 protected function literal($what, $eatWhitespace = null) {
3103 if (!isset($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
3104
3105 // shortcut on single letter
3106 if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3107 if ($this->buffer[$this->count] == $what) {
3108 if (!$eatWhitespace) {
3109 $this->count++;
3110 return true;
3111 }
3112 // goes below...
3113 } else {
3114 return false;
3115 }
3116 }
3117
3118 return $this->match($this->preg_quote($what), $m, $eatWhitespace);
3119 }
3120
3121 // tree builders
3122
3123 protected function pushBlock($selectors) {
3124 $b = new stdClass;
3125 $b->parent = $this->env; // not sure if we need this yet
3126
3127 $b->selectors = $selectors;
3128 $b->children = array();
3129
3130 $this->env = $b;
3131 return $b;
3132 }
3133
3134 protected function pushSpecialBlock($type) {
3135 $block = $this->pushBlock(null);
3136 $block->type = $type;
3137 return $block;
3138 }
3139
3140 protected function popBlock() {
3141 if (empty($this->env->parent)) {
3142 $this->throwParseError("unexpected }");
3143 }
3144
3145 $old = $this->env;
3146 $this->env = $this->env->parent;
3147 unset($old->parent);
3148 return $old;
3149 }
3150
3151 protected function append($statement, $pos=null) {
3152 if ($pos !== null) {
3153 $statement[-1] = $pos;
3154 if (!$this->rootParser) $statement[-2] = $this;
3155 }
3156 $this->env->children[] = $statement;
3157 }
3158
3159 // last child that was appended
3160 protected function last() {
3161 $i = count($this->env->children) - 1;
3162 if (isset($this->env->children[$i]))
3163 return $this->env->children[$i];
3164 }
3165
3166 // high level parsers (they return parts of ast)
3167
3168 protected function mediaQueryList(&$out) {
3169 return $this->genericList($out, "mediaQuery", ",", false);
3170 }
3171
3172 protected function mediaQuery(&$out) {
3173 $s = $this->seek();
3174
3175 $expressions = null;
3176 $parts = array();
3177
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;
3186 } else {
3187 $media[2][] = array("keyword", $type);
3188 }
3189 }
3190 $prop[] = $media;
3191 $parts[] = $prop;
3192 }
3193
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]);
3197 }
3198
3199 $out = $parts;
3200 return true;
3201 }
3202
3203 protected function mediaExpression(&$out) {
3204 $s = $this->seek();
3205 $value = null;
3206 if ($this->literal("(") &&
3207 $this->expression($feature) &&
3208 ($this->literal(":") && $this->expression($value) || true) &&
3209 $this->literal(")"))
3210 {
3211 $out = array("mediaExp", $feature);
3212 if ($value) $out[] = $value;
3213 return true;
3214 }
3215
3216 $this->seek($s);
3217 return false;
3218 }
3219
3220 protected function argValues(&$out) {
3221 if ($this->genericList($list, "argValue", ",", false)) {
3222 $out = $list[2];
3223 return true;
3224 }
3225 return false;
3226 }
3227
3228 protected function argValue(&$out) {
3229 $s = $this->seek();
3230
3231 $keyword = null;
3232 if (!$this->variable($keyword) || !$this->literal(":")) {
3233 $this->seek($s);
3234 $keyword = null;
3235 }
3236
3237 if ($this->genericList($value, "expression")) {
3238 $out = array($keyword, $value, false);
3239 $s = $this->seek();
3240 if ($this->literal("...")) {
3241 $out[2] = true;
3242 } else {
3243 $this->seek($s);
3244 }
3245 return true;
3246 }
3247
3248 return false;
3249 }
3250
3258 public function valueList(&$out)
3259 {
3260 return $this->genericList($out, 'spaceList', ',');
3261 }
3262
3263 protected function spaceList(&$out)
3264 {
3265 return $this->genericList($out, 'expression');
3266 }
3267
3268 protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3269 $s = $this->seek();
3270 $items = array();
3271 while ($this->$parseItem($value)) {
3272 $items[] = $value;
3273 if ($delim) {
3274 if (!$this->literal($delim)) break;
3275 }
3276 }
3277
3278 if (count($items) == 0) {
3279 $this->seek($s);
3280 return false;
3281 }
3282
3283 if ($flatten && count($items) == 1) {
3284 $out = $items[0];
3285 } else {
3286 $out = array("list", $delim, $items);
3287 }
3288
3289 return true;
3290 }
3291
3292 protected function expression(&$out) {
3293 $s = $this->seek();
3294
3295 if ($this->literal("(")) {
3296 if ($this->literal(")")) {
3297 $out = array("list", "", array());
3298 return true;
3299 }
3300
3301 if ($this->valueList($out) && $this->literal(')') && $out[0] == "list") {
3302 return true;
3303 }
3304
3305 $this->seek($s);
3306 }
3307
3308 if ($this->value($lhs)) {
3309 $out = $this->expHelper($lhs, 0);
3310 return true;
3311 }
3312
3313 return false;
3314 }
3315
3316 protected function expHelper($lhs, $minP) {
3317 $opstr = self::$operatorStr;
3318
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]);
3325
3326 $op = $m[1];
3327
3328 // don't turn negative numbers into expressions
3329 if ($op == "-" && $whiteBefore) {
3330 if (!$whiteAfter) break;
3331 }
3332
3333 if (!$this->value($rhs)) break;
3334
3335 // peek and see if rhs belongs to next operator
3336 if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) {
3337 $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
3338 }
3339
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]);
3344 }
3345
3346 $this->seek($ss);
3347 return $lhs;
3348 }
3349
3350 protected function value(&$out) {
3351 $s = $this->seek();
3352
3353 if ($this->literal("not", false) && $this->whitespace() && $this->value($inner)) {
3354 $out = array("unary", "not", $inner, $this->inParens);
3355 return true;
3356 } else {
3357 $this->seek($s);
3358 }
3359
3360 if ($this->literal("+") && $this->value($inner)) {
3361 $out = array("unary", "+", $inner, $this->inParens);
3362 return true;
3363 } else {
3364 $this->seek($s);
3365 }
3366
3367 // negation
3368 if ($this->literal("-", false) &&
3369 ($this->variable($inner) ||
3370 $this->unit($inner) ||
3371 $this->parenValue($inner)))
3372 {
3373 $out = array("unary", "-", $inner, $this->inParens);
3374 return true;
3375 } else {
3376 $this->seek($s);
3377 }
3378
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;
3387
3388 if ($this->keyword($keyword)) {
3389 if ($keyword == "null") {
3390 $out = array("null");
3391 } else {
3392 $out = array("keyword", $keyword);
3393 }
3394 return true;
3395 }
3396
3397 return false;
3398 }
3399
3400 // value wrappen in parentheses
3401 protected function parenValue(&$out) {
3402 $s = $this->seek();
3403
3404 $inParens = $this->inParens;
3405 if ($this->literal("(") &&
3406 ($this->inParens = true) && $this->expression($exp) &&
3407 $this->literal(")"))
3408 {
3409 $out = $exp;
3410 $this->inParens = $inParens;
3411 return true;
3412 } else {
3413 $this->inParens = $inParens;
3414 $this->seek($s);
3415 }
3416
3417 return false;
3418 }
3419
3420 protected function progid(&$out) {
3421 $s = $this->seek();
3422 if ($this->literal("progid:", false) &&
3423 $this->openString("(", $fn) &&
3424 $this->literal("("))
3425 {
3426 $this->openString(")", $args, "(");
3427 if ($this->literal(")")) {
3428 $out = array("string", "", array(
3429 "progid:", $fn, "(", $args, ")"
3430 ));
3431 return true;
3432 }
3433 }
3434
3435 $this->seek($s);
3436 return false;
3437 }
3438
3439 protected function func(&$func) {
3440 $s = $this->seek();
3441
3442 if ($this->keyword($name, false) &&
3443 $this->literal("("))
3444 {
3445 if ($name == "alpha" && $this->argumentList($args)) {
3446 $func = array("function", $name, array("string", "", $args));
3447 return true;
3448 }
3449
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);
3454 return true;
3455 }
3456 $this->seek($ss);
3457 }
3458
3459 if (($this->openString(")", $str, "(") || true ) &&
3460 $this->literal(")"))
3461 {
3462 $args = array();
3463 if (!empty($str)) {
3464 $args[] = array(null, array("string", "", array($str)));
3465 }
3466
3467 $func = array("fncall", $name, $args);
3468 return true;
3469 }
3470 }
3471
3472 $this->seek($s);
3473 return false;
3474 }
3475
3476 protected function argumentList(&$out) {
3477 $s = $this->seek();
3478 $this->literal("(");
3479
3480 $args = array();
3481 while ($this->keyword($var)) {
3482 $ss = $this->seek();
3483
3484 if ($this->literal("=") && $this->expression($exp)) {
3485 $args[] = array("string", "", array($var."="));
3486 $arg = $exp;
3487 } else {
3488 break;
3489 }
3490
3491 $args[] = $arg;
3492
3493 if (!$this->literal(",")) break;
3494
3495 $args[] = array("string", "", array(", "));
3496 }
3497
3498 if (!$this->literal(")") || !count($args)) {
3499 $this->seek($s);
3500 return false;
3501 }
3502
3503 $out = $args;
3504 return true;
3505 }
3506
3507 protected function argumentDef(&$out) {
3508 $s = $this->seek();
3509 $this->literal("(");
3510
3511 $args = array();
3512 while ($this->variable($var)) {
3513 $arg = array($var[1], null, false);
3514
3515 $ss = $this->seek();
3516 if ($this->literal(":") && $this->genericList($defaultVal, "expression")) {
3517 $arg[1] = $defaultVal;
3518 } else {
3519 $this->seek($ss);
3520 }
3521
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");
3527 }
3528 $arg[2] = true;
3529 $this->seek($sss);
3530 } else {
3531 $this->seek($ss);
3532 }
3533
3534 $args[] = $arg;
3535 if (!$this->literal(",")) break;
3536 }
3537
3538 if (!$this->literal(")")) {
3539 $this->seek($s);
3540 return false;
3541 }
3542
3543 $out = $args;
3544 return true;
3545 }
3546
3547 protected function color(&$out) {
3548 $color = array('color');
3549
3550 if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
3551 if (isset($m[3])) {
3552 $num = $m[3];
3553 $width = 16;
3554 } else {
3555 $num = $m[2];
3556 $width = 256;
3557 }
3558
3559 $num = hexdec($num);
3560 foreach (array(3,2,1) as $i) {
3561 $t = $num % $width;
3562 $num /= $width;
3563
3564 $color[$i] = $t * (256/$width) + $t * floor(16/$width);
3565 }
3566
3567 $out = $color;
3568 return true;
3569 }
3570
3571 return false;
3572 }
3573
3574 protected function unit(&$unit) {
3575 if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
3576 $unit = array("number", $m[1], empty($m[3]) ? "" : $m[3]);
3577 return true;
3578 }
3579 return false;
3580 }
3581
3582 protected function string(&$out) {
3583 $s = $this->seek();
3584 if ($this->literal('"', false)) {
3585 $delim = '"';
3586 } elseif ($this->literal("'", false)) {
3587 $delim = "'";
3588 } else {
3589 return false;
3590 }
3591
3592 $content = array();
3593 $oldWhite = $this->eatWhiteDefault;
3594 $this->eatWhiteDefault = false;
3595
3596 while ($this->matchString($m, $delim)) {
3597 $content[] = $m[1];
3598 if ($m[2] == "#{") {
3599 $this->count -= strlen($m[2]);
3600 if ($this->interpolation($inter, false)) {
3601 $content[] = $inter;
3602 } else {
3603 $this->count += strlen($m[2]);
3604 $content[] = "#{"; // ignore it
3605 }
3606 } elseif ($m[2] == '\\') {
3607 $content[] = $m[2];
3608 if ($this->literal($delim, false)) {
3609 $content[] = $delim;
3610 }
3611 } else {
3612 $this->count -= strlen($delim);
3613 break; // delim
3614 }
3615 }
3616
3617 $this->eatWhiteDefault = $oldWhite;
3618
3619 if ($this->literal($delim)) {
3620 $out = array("string", $delim, $content);
3621 return true;
3622 }
3623
3624 $this->seek($s);
3625 return false;
3626 }
3627
3628 protected function mixedKeyword(&$out) {
3629 $s = $this->seek();
3630
3631 $parts = array();
3632
3633 $oldWhite = $this->eatWhiteDefault;
3634 $this->eatWhiteDefault = false;
3635
3636 while (true) {
3637 if ($this->keyword($key)) {
3638 $parts[] = $key;
3639 continue;
3640 }
3641
3642 if ($this->interpolation($inter)) {
3643 $parts[] = $inter;
3644 continue;
3645 }
3646
3647 break;
3648 }
3649
3650 $this->eatWhiteDefault = $oldWhite;
3651
3652 if (count($parts) == 0) return false;
3653
3654 if ($this->eatWhiteDefault) {
3655 $this->whitespace();
3656 }
3657
3658 $out = $parts;
3659 return true;
3660 }
3661
3662 // an unbounded string stopped by $end
3663 protected function openString($end, &$out, $nestingOpen=null) {
3664 $oldWhite = $this->eatWhiteDefault;
3665 $this->eatWhiteDefault = false;
3666
3667 $stop = array("'", '"', "#{", $end);
3668 $stop = array_map(array($this, "preg_quote"), $stop);
3669 $stop[] = self::$commentMulti;
3670
3671 $patt = '(.*?)('.implode("|", $stop).')';
3672
3673 $nestingLevel = 0;
3674
3675 $content = array();
3676 while ($this->match($patt, $m, false)) {
3677 if (isset($m[1]) && $m[1] !== '') {
3678 $content[] = $m[1];
3679 if ($nestingOpen) {
3680 $nestingLevel += substr_count($m[1], $nestingOpen);
3681 }
3682 }
3683
3684 $tok = $m[2];
3685
3686 $this->count-= strlen($tok);
3687 if ($tok == $end) {
3688 if ($nestingLevel == 0) {
3689 break;
3690 } else {
3691 $nestingLevel--;
3692 }
3693 }
3694
3695 if (($tok == "'" || $tok == '"') && $this->string($str)) {
3696 $content[] = $str;
3697 continue;
3698 }
3699
3700 if ($tok == "#{" && $this->interpolation($inter)) {
3701 $content[] = $inter;
3702 continue;
3703 }
3704
3705 $content[] = $tok;
3706 $this->count+= strlen($tok);
3707 }
3708
3709 $this->eatWhiteDefault = $oldWhite;
3710
3711 if (count($content) == 0) return false;
3712
3713 // trim the end
3714 if (is_string(end($content))) {
3715 $content[count($content) - 1] = rtrim(end($content));
3716 }
3717
3718 $out = array("string", "", $content);
3719 return true;
3720 }
3721
3722 // $lookWhite: save information about whitespace before and after
3723 protected function interpolation(&$out, $lookWhite=true) {
3724 $oldWhite = $this->eatWhiteDefault;
3725 $this->eatWhiteDefault = true;
3726
3727 $s = $this->seek();
3728 if ($this->literal("#{") && $this->valueList($value) && $this->literal("}", false)) {
3729
3730 // TODO: don't error if out of bounds
3731
3732 if ($lookWhite) {
3733 $left = preg_match('/\s/', $this->buffer[$s - 1]) ? " " : "";
3734 $right = preg_match('/\s/', $this->buffer[$this->count]) ? " ": "";
3735 } else {
3736 $left = $right = false;
3737 }
3738
3739 $out = array("interpolate", $value, $left, $right);
3740 $this->eatWhiteDefault = $oldWhite;
3741 if ($this->eatWhiteDefault) $this->whitespace();
3742 return true;
3743 }
3744
3745 $this->seek($s);
3746 $this->eatWhiteDefault = $oldWhite;
3747 return false;
3748 }
3749
3750 // low level parsers
3751
3752 // returns an array of parts or a string
3753 protected function propertyName(&$out) {
3754 $s = $this->seek();
3755 $parts = array();
3756
3757 $oldWhite = $this->eatWhiteDefault;
3758 $this->eatWhiteDefault = false;
3759
3760 while (true) {
3761 if ($this->interpolation($inter)) {
3762 $parts[] = $inter;
3763 } elseif ($this->keyword($text)) {
3764 $parts[] = $text;
3765 } elseif (count($parts) == 0 && $this->match('[:.#]', $m, false)) {
3766 // css hacks
3767 $parts[] = $m[0];
3768 } else {
3769 break;
3770 }
3771 }
3772
3773 $this->eatWhiteDefault = $oldWhite;
3774 if (count($parts) == 0) return false;
3775
3776 // match comment hack
3777 if (preg_match(self::$whitePattern,
3778 $this->buffer, $m, null, $this->count))
3779 {
3780 if (!empty($m[0])) {
3781 $parts[] = $m[0];
3782 $this->count += strlen($m[0]);
3783 }
3784 }
3785
3786 $this->whitespace(); // get any extra whitespace
3787
3788 $out = array("string", "", $parts);
3789 return true;
3790 }
3791
3792 // comma separated list of selectors
3793 protected function selectors(&$out) {
3794 $s = $this->seek();
3795 $selectors = array();
3796 while ($this->selector($sel)) {
3797 $selectors[] = $sel;
3798 if (!$this->literal(",")) break;
3799 while ($this->literal(",")); // ignore extra
3800 }
3801
3802 if (count($selectors) == 0) {
3803 $this->seek($s);
3804 return false;
3805 }
3806
3807 $out = $selectors;
3808 return true;
3809 }
3810
3811 // whitespace separated list of selectorSingle
3812 protected function selector(&$out) {
3813 $selector = array();
3814
3815 while (true) {
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]);
3823 } else {
3824 break;
3825 }
3826
3827 }
3828
3829 if (count($selector) == 0) {
3830 return false;
3831 }
3832
3833 $out = $selector;
3834 return true;
3835 }
3836
3837 // the parts that make up
3838 // div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3839 protected function selectorSingle(&$out) {
3840 $oldWhite = $this->eatWhiteDefault;
3841 $this->eatWhiteDefault = false;
3842
3843 $parts = array();
3844
3845 if ($this->literal("*", false)) {
3846 $parts[] = "*";
3847 }
3848
3849 while (true) {
3850 // see if we can stop early
3851 if ($this->match("\s*[{,]", $m)) {
3852 $this->count--;
3853 break;
3854 }
3855
3856 $s = $this->seek();
3857 // self
3858 if ($this->literal("&", false)) {
3859 $parts[] = scssc::$selfSelector;
3860 continue;
3861 }
3862
3863 if ($this->literal(".", false)) {
3864 $parts[] = ".";
3865 continue;
3866 }
3867
3868 if ($this->literal("|", false)) {
3869 $parts[] = "|";
3870 continue;
3871 }
3872
3873 // for keyframes
3874 if ($this->unit($unit)) {
3875 $parts[] = $unit;
3876 continue;
3877 }
3878
3879 if ($this->keyword($name)) {
3880 $parts[] = $name;
3881 continue;
3882 }
3883
3884 if ($this->interpolation($inter)) {
3885 $parts[] = $inter;
3886 continue;
3887 }
3888
3889 if ($this->literal('%', false) && $this->placeholder($placeholder)) {
3890 $parts[] = '%';
3891 $parts[] = $placeholder;
3892 continue;
3893 }
3894
3895 if ($this->literal("#", false)) {
3896 $parts[] = "#";
3897 continue;
3898 }
3899
3900 // a pseudo selector
3901 if ($this->match("::?", $m) && $this->mixedKeyword($nameParts)) {
3902 $parts[] = $m[0];
3903 foreach ($nameParts as $sub) {
3904 $parts[] = $sub;
3905 }
3906
3907 $ss = $this->seek();
3908 if ($this->literal("(") &&
3909 ($this->openString(")", $str, "(") || true ) &&
3910 $this->literal(")"))
3911 {
3912 $parts[] = "(";
3913 if (!empty($str)) $parts[] = $str;
3914 $parts[] = ")";
3915 } else {
3916 $this->seek($ss);
3917 }
3918
3919 continue;
3920 } else {
3921 $this->seek($s);
3922 }
3923
3924 // attribute selector
3925 // TODO: replace with open string?
3926 if ($this->literal("[", false)) {
3927 $attrParts = array("[");
3928 // keyword, string, operator
3929 while (true) {
3930 if ($this->literal("]", false)) {
3931 $this->count--;
3932 break; // get out early
3933 }
3934
3935 if ($this->match('\s+', $m)) {
3936 $attrParts[] = " ";
3937 continue;
3938 }
3939 if ($this->string($str)) {
3940 $attrParts[] = $str;
3941 continue;
3942 }
3943
3944 if ($this->keyword($word)) {
3945 $attrParts[] = $word;
3946 continue;
3947 }
3948
3949 if ($this->interpolation($inter, false)) {
3950 $attrParts[] = $inter;
3951 continue;
3952 }
3953
3954 // operator, handles attr namespace too
3955 if ($this->match('[|-~\$\*\^=]+', $m)) {
3956 $attrParts[] = $m[0];
3957 continue;
3958 }
3959
3960 break;
3961 }
3962
3963 if ($this->literal("]", false)) {
3964 $attrParts[] = "]";
3965 foreach ($attrParts as $part) {
3966 $parts[] = $part;
3967 }
3968 continue;
3969 }
3970 $this->seek($s);
3971 // should just break here?
3972 }
3973
3974 break;
3975 }
3976
3977 $this->eatWhiteDefault = $oldWhite;
3978
3979 if (count($parts) == 0) return false;
3980
3981 $out = $parts;
3982 return true;
3983 }
3984
3985 protected function variable(&$out) {
3986 $s = $this->seek();
3987 if ($this->literal("$", false) && $this->keyword($name)) {
3988 $out = array("var", $name);
3989 return true;
3990 }
3991 $this->seek($s);
3992 return false;
3993 }
3994
3995 protected function keyword(&$word, $eatWhitespace = null) {
3996 if ($this->match('([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)',
3997 $m, $eatWhitespace))
3998 {
3999 $word = $m[1];
4000 return true;
4001 }
4002 return false;
4003 }
4004
4005 protected function placeholder(&$placeholder) {
4006 if ($this->match('([\w\-_]+)', $m)) {
4007 $placeholder = $m[1];
4008 return true;
4009 }
4010 return false;
4011 }
4012
4013 // consume an end of statement delimiter
4014 protected function end() {
4015 if ($this->literal(';')) {
4016 return true;
4017 } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
4018 // if there is end of file or a closing block next then we don't need a ;
4019 return true;
4020 }
4021 return false;
4022 }
4023
4024 // advance counter to next occurrence of $what
4025 // $until - don't include $what in advance
4026 // $allowNewline, if string, will be used as valid char set
4027 protected function to($what, &$out, $until = false, $allowNewline = false) {
4028 if (is_string($allowNewline)) {
4029 $validChars = $allowNewline;
4030 } else {
4031 $validChars = $allowNewline ? "." : "[^\n]";
4032 }
4033 if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
4034 if ($until) $this->count -= strlen($what); // give back $what
4035 $out = $m[1];
4036 return true;
4037 }
4038
4039 public function throwParseError($msg = "parse error", $count = null) {
4040 $count = !isset($count) ? $this->count : $count;
4041
4042 $line = $this->getLineNo($count);
4043
4044 if (!empty($this->sourceName)) {
4045 $loc = "$this->sourceName on line $line";
4046 } else {
4047 $loc = "line: $line";
4048 }
4049
4050 if ($this->peek("(.*?)(\n|$)", $m, $count)) {
4051 throw new Exception("$msg: failed at `$m[1]` $loc");
4052 } else {
4053 throw new Exception("$msg: $loc");
4054 }
4055 }
4056
4057 public function getLineNo($pos) {
4058 return 1 + substr_count(substr($this->buffer, 0, $pos), "\n");
4059 }
4060
4071 protected function matchString(&$m, $delim) {
4072 $token = null;
4073
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);
4077 }
4078
4079 // look for either ending delim, escape, or string interpolation
4080 foreach (array('#{', '\\', $delim) as $lookahead) {
4081 $pos = strpos($this->buffer, $lookahead, $this->count);
4082 if ($pos !== false && $pos < $end) {
4083 $end = $pos;
4084 $token = $lookahead;
4085 }
4086 }
4087
4088 if (!isset($token)) {
4089 return false;
4090 }
4091
4092 $match = substr($this->buffer, $this->count, $end - $this->count);
4093 $m = array(
4094 $match . $token,
4095 $match,
4096 $token
4097 );
4098 $this->count = $end + strlen($token);
4099
4100 return true;
4101 }
4102
4103 // try to match something on head of buffer
4104 protected function match($regex, &$out, $eatWhitespace = null) {
4105 if (!isset($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
4106
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();
4111 return true;
4112 }
4113 return false;
4114 }
4115
4116 // match some whitespace
4117 protected function whitespace() {
4118 $gotWhite = false;
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;
4124 }
4125 }
4126 $this->count += strlen($m[0]);
4127 $gotWhite = true;
4128 }
4129 return $gotWhite;
4130 }
4131
4132 protected function peek($regex, &$out, $from=null) {
4133 if (!isset($from)) $from = $this->count;
4134
4135 $r = '/'.$regex.'/Ais';
4136 $result = preg_match($r, $this->buffer, $out, null, $from);
4137
4138 return $result;
4139 }
4140
4141 protected function seek($where = null) {
4142 if ($where === null) return $this->count;
4143 else $this->count = $where;
4144 return true;
4145 }
4146
4147 static function preg_quote($what) {
4148 return preg_quote($what, '/');
4149 }
4150
4151 protected function show() {
4152 if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
4153 return $m[1];
4154 }
4155 return "";
4156 }
4157
4158 // turn list of length 1 into value type
4159 protected function flattenList($value) {
4160 if ($value[0] == "list" && count($value[2]) == 1) {
4161 return $this->flattenList($value[2][0]);
4162 }
4163 return $value;
4164 }
4165 }
4166
4172 class scss_formatter {
4173 public $indentChar = " ";
4174
4175 public $break = "\n";
4176 public $open = " {";
4177 public $close = "}";
4178 public $tagSeparator = ", ";
4179 public $assignSeparator = ": ";
4180
4181 public function __construct() {
4182 $this->indentLevel = 0;
4183 }
4184
4185 public function indentStr($n = 0) {
4186 return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
4187 }
4188
4189 public function property($name, $value) {
4190 return $name . $this->assignSeparator . $value . ";";
4191 }
4192
4193 protected function block($block) {
4194 if (empty($block->lines) && empty($block->children)) return;
4195
4196 $inner = $pre = $this->indentStr();
4197
4198 if (!empty($block->selectors)) {
4199 echo $pre .
4200 implode($this->tagSeparator, $block->selectors) .
4201 $this->open . $this->break;
4202 $this->indentLevel++;
4203 $inner = $this->indentStr();
4204 }
4205
4206 if (!empty($block->lines)) {
4207 $glue = $this->break.$inner;
4208 echo $inner . implode($glue, $block->lines);
4209 if (!empty($block->children)) {
4210 echo $this->break;
4211 }
4212 }
4213
4214 foreach ($block->children as $child) {
4215 $this->block($child);
4216 }
4217
4218 if (!empty($block->selectors)) {
4219 $this->indentLevel--;
4220 if (empty($block->children)) echo $this->break;
4221 echo $pre . $this->close . $this->break;
4222 }
4223 }
4224
4225 public function format($block) {
4226 ob_start();
4227 $this->block($block);
4228 $out = ob_get_clean();
4229
4230 return $out;
4231 }
4232 }
4233
4239 class scss_formatter_nested extends scss_formatter {
4240 public $close = " }";
4241
4242 // adjust the depths of all children, depth first
4243 public function adjustAllChildren($block) {
4244 // flatten empty nested blocks
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;
4250 }
4251 continue;
4252 }
4253 $children[] = $child;
4254 }
4255
4256 $count = count($children);
4257 for ($i = 0; $i < $count; $i++) {
4258 $depth = $children[$i]->depth;
4259 $j = $i + 1;
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;
4265 }
4266 }
4267 }
4268 }
4269
4270 $block->children = $children;
4271
4272 // make relative to parent
4273 foreach ($block->children as $child) {
4274 $this->adjustAllChildren($child);
4275 $child->depth = $child->depth - $block->depth;
4276 }
4277 }
4278
4279 protected function block($block) {
4280 if ($block->type == "root") {
4281 $this->adjustAllChildren($block);
4282 }
4283
4284 $inner = $pre = $this->indentStr($block->depth - 1);
4285 if (!empty($block->selectors)) {
4286 echo $pre .
4287 implode($this->tagSeparator, $block->selectors) .
4288 $this->open . $this->break;
4289 $this->indentLevel++;
4290 $inner = $this->indentStr($block->depth - 1);
4291 }
4292
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;
4297 }
4298
4299 foreach ($block->children as $i => $child) {
4300 // echo "*** block: ".$block->depth." child: ".$child->depth."\n";
4301 $this->block($child);
4302 if ($i < count($block->children) - 1) {
4303 echo $this->break;
4304
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) {
4308 echo $this->break;
4309 }
4310 }
4311 }
4312 }
4313
4314 if (!empty($block->selectors)) {
4315 $this->indentLevel--;
4316 echo $this->close;
4317 }
4318
4319 if ($block->type == "root") {
4320 echo $this->break;
4321 }
4322 }
4323 }
4324
4330 class scss_formatter_compressed extends scss_formatter {
4331 public $open = "{";
4332 public $tagSeparator = ",";
4333 public $assignSeparator = ":";
4334 public $break = "";
4335
4336 public function indentStr($n = 0) {
4337 return "";
4338 }
4339 }
4340
4346 class scss_server {
4355 protected function join($left, $right) {
4356 return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
4357 }
4358
4364 protected function inputName() {
4365 switch (true) {
4366 case isset($_GET['p']):
4367 return $_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']));
4372 }
4373 }
4374
4380 protected function findInput() {
4381 if (($input = $this->inputName())
4382 && strpos($input, '..') === false
4383 && substr($input, -5) === '.scss'
4384 ) {
4385 $name = $this->join($this->dir, $input);
4386
4387 if (is_file($name) && is_readable($name)) {
4388 return $name;
4389 }
4390 }
4391
4392 return false;
4393 }
4394
4400 protected function cacheName($fname) {
4401 return $this->join($this->cacheDir, md5($fname) . '.css');
4402 }
4403
4409 protected function importsCacheName($out) {
4410 return $out . '.imports';
4411 }
4412
4421 protected function needsCompile($in, $out) {
4422 if (!is_file($out)) return true;
4423
4424 $mtime = filemtime($out);
4425 if (filemtime($in) > $mtime) return true;
4426
4427 // look for modified imports
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;
4433 }
4434 }
4435 return false;
4436 }
4437
4443 protected function getModifiedSinceHeader()
4444 {
4445 $modifiedSince = '';
4446
4447 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
4448 $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
4449
4450 if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) {
4451 $modifiedSince = substr($modifiedSince, 0, $semicolonPos);
4452 }
4453 }
4454
4455 return $modifiedSince;
4456 }
4457
4466 protected function compile($in, $out) {
4467 $start = microtime(true);
4468 $css = $this->scss->compile(file_get_contents($in), $in);
4469 $elapsed = round((microtime(true) - $start), 4);
4470
4471 $v = scssc::$VERSION;
4472 $t = @date('r');
4473 $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
4474
4475 file_put_contents($out, $css);
4476 file_put_contents($this->importsCacheName($out),
4477 serialize($this->scss->getParsedFiles()));
4478 return $css;
4479 }
4480
4486 public function serve($salt = '') {
4487 $protocol = isset($_SERVER['SERVER_PROTOCOL'])
4488 ? $_SERVER['SERVER_PROTOCOL']
4489 : 'HTTP/1.0';
4490
4491 if ($input = $this->findInput()) {
4492 $output = $this->cacheName($salt . $input);
4493
4494 if ($this->needsCompile($input, $output)) {
4495 try {
4496 $css = $this->compile($input, $output);
4497
4498 $lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT';
4499
4500 header('Last-Modified: ' . $lastModified);
4501 header('Content-type: text/css');
4502
4503 echo $css;
4504
4505 return;
4506 } catch (Exception $e) {
4507 header($protocol . ' 500 Internal Server Error');
4508 header('Content-type: text/plain');
4509
4510 echo 'Parse error: ' . $e->getMessage() . "\n";
4511 }
4512 }
4513
4514 header('X-SCSS-Cache: true');
4515 header('Content-type: text/css');
4516
4517 $modifiedSince = $this->getModifiedSinceHeader();
4518 $mtime = filemtime($output);
4519
4520 if (@strtotime($modifiedSince) === $mtime) {
4521 header($protocol . ' 304 Not Modified');
4522
4523 return;
4524 }
4525
4526 $lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
4527 header('Last-Modified: ' . $lastModified);
4528
4529 echo file_get_contents($output);
4530
4531 return;
4532 }
4533
4534 header($protocol . ' 404 Not Found');
4535 header('Content-type: text/plain');
4536
4537 $v = scssc::$VERSION;
4538 echo "/* INPUT NOT FOUND scss $v */\n";
4539 }
4540
4548 public function __construct($dir, $cacheDir=null, $scss=null) {
4549 $this->dir = $dir;
4550
4551 if (!isset($cacheDir)) {
4552 $cacheDir = $this->join($dir, 'scss_cache');
4553 }
4554
4555 $this->cacheDir = $cacheDir;
4556 if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true);
4557
4558 if (!isset($scss)) {
4559 $scss = new scssc();
4560 $scss->setImportPaths($this->dir);
4561 }
4562 $this->scss = $scss;
4563 }
4564
4570 static public function serveFrom($path) {
4571 $server = new self($path);
4572 $server->serve();
4573 }
4574 }
scss_parser\mediaQuery
mediaQuery(&$out)
Definition: scssc.php:3172
scssc\op_eq
op_eq($left, $right)
Definition: scssc.php:1185
scssc\lib_blue
lib_blue($args)
Definition: scssc.php:2074
scssc\$lib_transparentize
static $lib_transparentize
Definition: scssc.php:2252
scssc\lib_adjust_hue
lib_adjust_hue($args)
Definition: scssc.php:2176
scssc\$lib_unquote
static $lib_unquote
Definition: scssc.php:2267
scssc\coerceForExpression
coerceForExpression($value)
Definition: scssc.php:1787
scssc\op_mul_number_number
op_mul_number_number($left, $right)
Definition: scssc.php:1087
scssc\unregisterFunction
unregisterFunction($name)
Definition: scssc.php:1642
scssc\$lib_grayscale
static $lib_grayscale
Definition: scssc.php:2212
scss_parser\last
last()
Definition: scssc.php:3160
scssc\lib_scale_color
lib_scale_color($args)
Definition: scssc.php:2049
scssc\lib_unquote
lib_unquote($args)
Definition: scssc.php:2268
scssc\setExisting
setExisting($name, $value, $env=null)
Definition: scssc.php:1534
scssc\adjust_color_helper
adjust_color_helper($base, $alter, $i)
Definition: scssc.php:2003
scss_parser\parenValue
parenValue(&$out)
Definition: scssc.php:3401
scss_formatter_nested\$close
$close
Definition: scssc.php:4240
scssc\$lib_round
static $lib_round
Definition: scssc.php:2289
scss_formatter_compressed\$tagSeparator
$tagSeparator
Definition: scssc.php:4332
scssc\$true
static $true
Definition: scssc.php:81
scssc\$lib_complement
static $lib_complement
Definition: scssc.php:2219
scss_formatter_nested\block
block($block)
Definition: scssc.php:4279
scssc\$namespaces
static $namespaces
Definition: scssc.php:64
scss_formatter\$tagSeparator
$tagSeparator
Definition: scssc.php:4178
scss_parser\keyword
keyword(&$word, $eatWhitespace=null)
Definition: scssc.php:3995
scssc\op_and
op_and($left, $right, $shouldEval)
Definition: scssc.php:1122
scssc\makeOutputBlock
makeOutputBlock($type, $selectors=null)
Definition: scssc.php:164
scss_parser\throwParseError
throwParseError($msg="parse error", $count=null)
Definition: scssc.php:4039
scssc\$defaultValue
static $defaultValue
Definition: scssc.php:85
scssc\$lib_change_color
static $lib_change_color
Definition: scssc.php:2010
scssc\lib_max
lib_max($args)
Definition: scssc.php:2329
scssc\throwError
throwError($msg=null)
Definition: scssc.php:2485
scssc\scale_color_helper
scale_color_helper($base, $scale, $i)
Definition: scssc.php:2025
scssc\$lib_red
static $lib_red
Definition: scssc.php:2061
scssc\lib_desaturate
lib_desaturate($args)
Definition: scssc.php:2206
scss_parser\parseChunk
parseChunk()
Parse a single chunk off the head of the buffer and append it to the current parse environment...
Definition: scssc.php:2790
scssc\$importCache
$importCache
Definition: scssc.php:89
scssc\$userFunctions
$userFunctions
Definition: scssc.php:91
scssc\lib_if
lib_if($args)
Definition: scssc.php:1928
scssc\lib_unitless
lib_unitless($args)
Definition: scssc.php:2457
scss_formatter\block
block($block)
Definition: scssc.php:4193
scssc\sortArgs
sortArgs($prototype, $args)
Definition: scssc.php:1747
scssc\adjustHsl
adjustHsl($color, $idx, $amount)
Definition: scssc.php:2167
scssc\change_color_helper
change_color_helper($base, $alter, $i)
Definition: scssc.php:2014
scssc\lib_unit
lib_unit($args)
Definition: scssc.php:2448
scssc\$lib_fade_in
static $lib_fade_in
Definition: scssc.php:2246
scssc\normalizeName
normalizeName($name)
Definition: scssc.php:1516
scss_server\inputName
inputName()
Get name of requested .scss file.
Definition: scssc.php:4364
scss_server\join
join($left, $right)
Join path components.
Definition: scssc.php:4355
scssc\registerFunction
registerFunction($name, $func)
Definition: scssc.php:1638
scssc\lib_comparable
lib_comparable($args)
Definition: scssc.php:2463
scssc\lib_darken
lib_darken($args)
Definition: scssc.php:2190
scss_parser\value
value(&$out)
Definition: scssc.php:3350
scss_parser\makeOperatorStr
static makeOperatorStr($operators)
Definition: scssc.php:2711
scssc\$lib_quote
static $lib_quote
Definition: scssc.php:2274
scssc\lib_percentage
lib_percentage($args)
Definition: scssc.php:2283
scss_parser\interpolation
interpolation(&$out, $lookWhite=true)
Definition: scssc.php:3723
scssc\callBuiltin
callBuiltin($name, $args, &$returnValue)
Definition: scssc.php:1706
scss_parser\show
show()
Definition: scssc.php:4151
scssc\$unitTable
static $unitTable
Definition: scssc.php:70
scssc\coerceColor
coerceColor($value)
Definition: scssc.php:1795
scss_server\importsCacheName
importsCacheName($out)
Get path to cached imports.
Definition: scssc.php:4409
scss_server\findInput
findInput()
Get path to requested .scss file.
Definition: scssc.php:4380
scssc\lib_hsl
lib_hsl($args)
Definition: scssc.php:2132
scssc\lib_red
lib_red($args)
Definition: scssc.php:2062
scssc\compileValue
compileValue($value)
Compiles a primitive value into a CSS property value.
Definition: scssc.php:1232
scssc\$false
static $false
Definition: scssc.php:82
scss_parser\argumentDef
argumentDef(&$out)
Definition: scssc.php:3507
scss_parser\stripDefault
stripDefault(&$value)
Definition: scssc.php:3087
scssc\compileMediaQuery
compileMediaQuery($queryList)
Definition: scssc.php:516
scssc\lib_saturation
lib_saturation($args)
Definition: scssc.php:2154
scssc\lib_round
lib_round($args)
Definition: scssc.php:2290
scssc\hasSelectorPlaceholder
hasSelectorPlaceholder($selector)
Definition: scssc.php:494
scssc\op_color_number
op_color_number($op, $left, $right)
Definition: scssc.php:1173
scssc\lib_rgb
lib_rgb($args)
Definition: scssc.php:1949
scss_parser\string
string(&$out)
Definition: scssc.php:3582
scss_parser\$commentMultiRight
static $commentMultiRight
Definition: scssc.php:2688
scssc\op_number_color
op_number_color($op, $left, $right)
Definition: scssc.php:1179
scssc\mergeMediaTypes
mergeMediaTypes($type1, $type2)
Definition: scssc.php:559
scssc\mediaParent
mediaParent($scope)
Definition: scssc.php:370
scssc\reduce
reduce($value, $inExp=false)
Definition: scssc.php:865
scss_parser\end
end()
Definition: scssc.php:4014
scss_formatter_compressed\indentStr
indentStr($n=0)
Definition: scssc.php:4336
scss_server
SCSS server.
Definition: scssc.php:4346
scssc\coerceString
coerceString($value)
Definition: scssc.php:1812
scss_parser\pushBlock
pushBlock($selectors)
Definition: scssc.php:3123
scssc\compileNestedBlock
compileNestedBlock($block, $selectors)
Definition: scssc.php:382
scss_parser\$precedence
static $precedence
Definition: scssc.php:2660
scssc\importFile
importFile($path, $out)
Definition: scssc.php:1646
scss_formatter\$close
$close
Definition: scssc.php:4177
scssc\$formatter
$formatter
Definition: scssc.php:96
scssc\op_add_number_number
op_add_number_number($left, $right)
Definition: scssc.php:1083
scssc\listSeparatorForJoin
listSeparatorForJoin($list1, $sep)
Definition: scssc.php:2377
scss_parser\propertyName
propertyName(&$out)
Definition: scssc.php:3753
scss_parser\match
match($regex, &$out, $eatWhitespace=null)
Definition: scssc.php:4104
scssc\lib_zip
lib_zip($args)
Definition: scssc.php:2406
scssc\$lib_index
static $lib_index
Definition: scssc.php:1934
scssc\pushEnv
pushEnv($block=null)
Definition: scssc.php:1505
scss_formatter_compressed\$open
$open
Definition: scssc.php:4331
scss_parser\__construct
__construct($sourceName=null, $rootParser=true)
Constructor.
Definition: scssc.php:2696
scssc\normalizeValue
normalizeValue($value)
Definition: scssc.php:1042
scssc\$lib_unitless
static $lib_unitless
Definition: scssc.php:2456
scssc\$lib_append
static $lib_append
Definition: scssc.php:2398
scssc\compileStringContent
compileStringContent($string)
Definition: scssc.php:1316
scssc\set
set($name, $value, $shadow=false)
Definition: scssc.php:1524
scssc\lib_abs
lib_abs($args)
Definition: scssc.php:2311
scssc\compileBlock
compileBlock($block)
Recursively compiles a block.
Definition: scssc.php:411
scss_parser\popBlock
popBlock()
Definition: scssc.php:3140
scssc\$null
static $null
Definition: scssc.php:83
scss_parser\$commentMulti
static $commentMulti
Definition: scssc.php:2684
scss_parser\$commentSingle
static $commentSingle
Definition: scssc.php:2686
scssc\setImportPaths
setImportPaths($path)
Definition: scssc.php:1626
scssc\$lib_nth
static $lib_nth
Definition: scssc.php:2370
scss_formatter_compressed\$break
$break
Definition: scssc.php:4334
scss_parser\mixedKeyword
mixedKeyword(&$out)
Definition: scssc.php:3628
scss_parser\selectorSingle
selectorSingle(&$out)
Definition: scssc.php:3839
scssc\unsetVariable
unsetVariable($name)
Unset variable.
Definition: scssc.php:1607
scssc\lib_length
lib_length($args)
Definition: scssc.php:2365
scssc\lib_min
lib_min($args)
Definition: scssc.php:2317
scssc\matchExtendsSingle
matchExtendsSingle($single, &$outOrigin)
Definition: scssc.php:176
scssc\op_sub_number_number
op_sub_number_number($left, $right)
Definition: scssc.php:1091
scss_parser\unit
unit(&$unit)
Definition: scssc.php:3574
scssc\$lib_hue
static $lib_hue
Definition: scssc.php:2146
scss_parser\$operatorStr
static $operatorStr
Definition: scssc.php:2682
scssc\lib_alpha
lib_alpha($args)
Definition: scssc.php:2080
scssc\lib_adjust_color
lib_adjust_color($args)
Definition: scssc.php:2006
scssc\op_color_color
op_color_color($op, $left, $right)
Definition: scssc.php:1134
scss_server\serve
serve($salt= '')
Compile requested scss and serve css.
Definition: scssc.php:4486
scssc\injectVariables
injectVariables(array $args)
Definition: scssc.php:1564
scssc\$lib_alpha
static $lib_alpha
Definition: scssc.php:2079
scssc\lib_lighten
lib_lighten($args)
Definition: scssc.php:2183
scssc\lib_append
lib_append($args)
Definition: scssc.php:2399
scssc\compileRoot
compileRoot($rootBlock)
Definition: scssc.php:323
scssc\$lib_lightness
static $lib_lightness
Definition: scssc.php:2160
scssc\applyArguments
applyArguments($argDef, $argValues)
Definition: scssc.php:1435
scssc\compileSelectorPart
compileSelectorPart($piece)
Definition: scssc.php:477
scssc\op_or
op_or($left, $right, $shouldEval)
Definition: scssc.php:1128
scssc
SCSS compiler written in PHP.
Definition: scssc.php:45
scssc\fixColor
fixColor($c)
Definition: scssc.php:1850
scssc\compileMedia
compileMedia($media)
Definition: scssc.php:331
scssc\joinSelectors
joinSelectors($parent, $child)
Definition: scssc.php:1368
scssc\flattenSelectors
flattenSelectors($block, $parentKey=null)
Definition: scssc.php:290
scssc\assertList
assertList($value)
Definition: scssc.php:1822
scss_parser\flattenList
flattenList($value)
Definition: scssc.php:4159
scss_formatter\$indentChar
$indentChar
Definition: scssc.php:4173
scssc\expToString
expToString($exp)
Definition: scssc.php:837
scss_parser\argValues
argValues(&$out)
Definition: scssc.php:3220
scssc\normalizeNumber
normalizeNumber($number)
Definition: scssc.php:1064
scss_parser\selectors
selectors(&$out)
Definition: scssc.php:3793
scssc\$lib_opacity
static $lib_opacity
Definition: scssc.php:2089
scssc\$lib_desaturate
static $lib_desaturate
Definition: scssc.php:2205
scssc\op_neq
op_neq($left, $right)
Definition: scssc.php:1195
scssc\$lib_ie_hex_str
static $lib_ie_hex_str
Definition: scssc.php:2053
scss_formatter\property
property($name, $value)
Definition: scssc.php:4189
scss_parser\append
append($statement, $pos=null)
Definition: scssc.php:3151
scss_parser\$operators
static $operators
Definition: scssc.php:2679
scss_formatter\$assignSeparator
$assignSeparator
Definition: scssc.php:4179
scssc\lib_rgba
lib_rgba($args)
Definition: scssc.php:1957
scssc\$lib_scale_color
static $lib_scale_color
Definition: scssc.php:2021
scssc\op_mod_number_number
op_mod_number_number($left, $right)
Definition: scssc.php:1099
scssc\compile
compile($code, $name=null)
Compile scss.
Definition: scssc.php:106
scssc\assertColor
assertColor($value)
Definition: scssc.php:1828
scssc\lib_invert
lib_invert($args)
Definition: scssc.php:2225
scss_formatter\$break
$break
Definition: scssc.php:4175
scssc\$lib_lighten
static $lib_lighten
Definition: scssc.php:2182
scss_parser\color
color(&$out)
Definition: scssc.php:3547
scss_formatter
SCSS base formatter.
Definition: scssc.php:4172
scssc\$lib_join
static $lib_join
Definition: scssc.php:2389
scss_parser\selector
selector(&$out)
Definition: scssc.php:3812
scss_parser\preg_quote
static preg_quote($what)
Definition: scssc.php:4147
scss_server\needsCompile
needsCompile($in, $out)
Determine whether .scss file needs to be re-compiled.
Definition: scssc.php:4421
scssc\popEnv
popEnv()
Definition: scssc.php:1612
scssc\$lib_floor
static $lib_floor
Definition: scssc.php:2296
scss_parser\placeholder
placeholder(&$placeholder)
Definition: scssc.php:4005
scssc\$operatorNames
static $operatorNames
Definition: scssc.php:48
scssc\$lib_adjust_hue
static $lib_adjust_hue
Definition: scssc.php:2175
scssc\lib_floor
lib_floor($args)
Definition: scssc.php:2297
scssc\$lib_unit
static $lib_unit
Definition: scssc.php:2447
scssc\lib_join
lib_join($args)
Definition: scssc.php:2390
scssc\compileChildren
compileChildren($stms, $out)
Definition: scssc.php:509
scss_formatter_nested
SCSS nested formatter.
Definition: scssc.php:4239
scssc\toHSL
toHSL($red, $green, $blue)
Definition: scssc.php:1859
scss_parser\pushSpecialBlock
pushSpecialBlock($type)
Definition: scssc.php:3134
scssc\setNumberPrecision
setNumberPrecision($numberPrecision)
Definition: scssc.php:1630
scssc\coercePercent
coercePercent($value)
Definition: scssc.php:1839
scssc\$VERSION
static $VERSION
Definition: scssc.php:46
scssc\$lib_type_of
static $lib_type_of
Definition: scssc.php:2428
scss_formatter_nested\adjustAllChildren
adjustAllChildren($block)
Definition: scssc.php:4243
scss_parser\mediaExpression
mediaExpression(&$out)
Definition: scssc.php:3203
scss_parser\genericList
genericList(&$out, $parseItem, $delim="", $flatten=true)
Definition: scssc.php:3268
scssc\$lib_percentage
static $lib_percentage
Definition: scssc.php:2282
scssc\multiplySelectors
multiplySelectors($env)
Definition: scssc.php:1343
scssc\evalSelectorPart
evalSelectorPart($piece)
Definition: scssc.php:451
scss_parser\mediaQueryList
mediaQueryList(&$out)
Definition: scssc.php:3168
scssc\lib_green
lib_green($args)
Definition: scssc.php:2068
scss_parser\spaceList
spaceList(&$out)
Definition: scssc.php:3263
scss_server\serveFrom
static serveFrom($path)
Helper method to serve compiled scss.
Definition: scssc.php:4570
scssc\hueToRGB
hueToRGB($m1, $m2, $h)
Definition: scssc.php:1886
scssc\op_gt_number_number
op_gt_number_number($left, $right)
Definition: scssc.php:1203
scssc\$lib_blue
static $lib_blue
Definition: scssc.php:2073
scssc\lib_ie_hex_str
lib_ie_hex_str($args)
Definition: scssc.php:2054
scssc\op_lte_number_number
op_lte_number_number($left, $right)
Definition: scssc.php:1207
scssc\combineSelectorSingle
combineSelectorSingle($base, $other)
Definition: scssc.php:222
scss_server\getModifiedSinceHeader
getModifiedSinceHeader()
Get If-Modified-Since header from client request.
Definition: scssc.php:4443
scss_parser\$whitePattern
static $whitePattern
Definition: scssc.php:2683
scssc\multiplyMedia
multiplyMedia($env, $childQueries=null)
Definition: scssc.php:1397
scssc\$cssColors
static $cssColors
CSS Colors.
Definition: scssc.php:2502
scss_parser\valueList
valueList(&$out)
Parse list.
Definition: scssc.php:3258
scssc\lib_change_color
lib_change_color($args)
Definition: scssc.php:2017
scss_parser\parse
parse($buffer)
Parser buffer.
Definition: scssc.php:2723
scssc\alter_color
alter_color($args, $fn)
Definition: scssc.php:1970
scssc\compileSelector
compileSelector($selector)
Definition: scssc.php:470
scss_parser\argumentList
argumentList(&$out)
Definition: scssc.php:3476
scssc\setVariables
setVariables(array $variables)
Set variables.
Definition: scssc.php:1597
scssc\$lib_saturate
static $lib_saturate
Definition: scssc.php:2196
scssc\compileChild
compileChild($child, $out)
Definition: scssc.php:631
scssc\op_gte_number_number
op_gte_number_number($left, $right)
Definition: scssc.php:1199
scssc\$lib_invert
static $lib_invert
Definition: scssc.php:2224
scss_formatter\__construct
__construct()
Definition: scssc.php:4181
scss_formatter\indentStr
indentStr($n=0)
Definition: scssc.php:4185
scssc\$numberPrecision
$numberPrecision
Definition: scssc.php:94
scssc\getNormalizedNumbers
getNormalizedNumbers($args)
Definition: scssc.php:2341
scssc\op_add
op_add($left, $right)
Definition: scssc.php:1104
scssc\lib_hsla
lib_hsla($args)
Definition: scssc.php:2139
scssc\$registeredVars
$registeredVars
Definition: scssc.php:92
scssc\lib_saturate
lib_saturate($args)
Definition: scssc.php:2197
scss_parser\to
to($what, &$out, $until=false, $allowNewline=false)
Definition: scssc.php:4027
scssc\$lib_length
static $lib_length
Definition: scssc.php:2364
scss_formatter\$open
$open
Definition: scssc.php:4176
scss_server\cacheName
cacheName($fname)
Get path to cached .css file.
Definition: scssc.php:4400
scssc\$lib_hsla
static $lib_hsla
Definition: scssc.php:2137
scssc\findImport
findImport($url)
Definition: scssc.php:1667
scssc\lib_complement
lib_complement($args)
Definition: scssc.php:2220
scss_parser\expression
expression(&$out)
Definition: scssc.php:3292
scssc\$lib_opacify
static $lib_opacify
Definition: scssc.php:2236
scssc\$lib_saturation
static $lib_saturation
Definition: scssc.php:2153
scssc\getParsedFiles
getParsedFiles()
Definition: scssc.php:1618
scssc\shouldEval
shouldEval($value)
Definition: scssc.php:852
scssc\$lib_hsl
static $lib_hsl
Definition: scssc.php:2131
scssc\$importPaths
$importPaths
Definition: scssc.php:88
scssc\lib_type_of
lib_type_of($args)
Definition: scssc.php:2429
scssc\op_lt_number_number
op_lt_number_number($left, $right)
Definition: scssc.php:1211
scss_server\compile
compile($in, $out)
Compile .scss file.
Definition: scssc.php:4466
scssc\op_div_number_number
op_div_number_number($left, $right)
Definition: scssc.php:1095
scss_formatter_compressed\$assignSeparator
$assignSeparator
Definition: scssc.php:4333
scssc\setRaw
setRaw($name, $value)
Definition: scssc.php:1544
scss_parser\literal
literal($what, $eatWhitespace=null)
Definition: scssc.php:3102
scssc\isTruthy
isTruthy($value)
Definition: scssc.php:847
scss_parser\getLineNo
getLineNo($pos)
Definition: scssc.php:4057
scssc\fileExists
fileExists($name)
Definition: scssc.php:1702
scssc\$lib_rgb
static $lib_rgb
Definition: scssc.php:1948
scssc\lib_quote
lib_quote($args)
Definition: scssc.php:2275
scssc\lib_opacity
lib_opacity($args)
Definition: scssc.php:2090
scss_server\__construct
__construct($dir, $cacheDir=null, $scss=null)
Constructor.
Definition: scssc.php:4548
scssc\$lib_ceil
static $lib_ceil
Definition: scssc.php:2303
scssc\lib_counter
lib_counter($args)
Workaround IE7's content counter bug.
Definition: scssc.php:2480
scssc\lib_transparentize
lib_transparentize($args)
Definition: scssc.php:2253
scssc\matchExtends
matchExtends($selector, &$out, $from=0, $initial=true)
Definition: scssc.php:243
scssc\lib_lightness
lib_lightness($args)
Definition: scssc.php:2161
scss_parser
SCSS parser.
Definition: scssc.php:2659
scssc\lib_fade_out
lib_fade_out($args)
Definition: scssc.php:2263
scssc\toBool
toBool($thing)
Definition: scssc.php:1215
scss_formatter\format
format($block)
Definition: scssc.php:4225
scssc\lib_nth
lib_nth($args)
Definition: scssc.php:2371
scssc\lib_index
lib_index($args)
Definition: scssc.php:1935
scss_parser\func
func(&$func)
Definition: scssc.php:3439
scssc\$lib_green
static $lib_green
Definition: scssc.php:2067
scssc\coerceUnit
coerceUnit($number, $unit)
Definition: scssc.php:1074
scss_formatter_compressed
SCSS compressed formatter.
Definition: scssc.php:4330
scss_parser\expHelper
expHelper($lhs, $minP)
Definition: scssc.php:3316
scssc\toRGB
toRGB($hue, $saturation, $lightness)
Definition: scssc.php:1905
scss_parser\seek
seek($where=null)
Definition: scssc.php:4141
scssc\addImportPath
addImportPath($path)
Definition: scssc.php:1622
scssc\coerceList
coerceList($item, $delim=",")
Definition: scssc.php:1427
scss_parser\openString
openString($end, &$out, $nestingOpen=null)
Definition: scssc.php:3663
scssc\evalSelector
evalSelector($selector)
Definition: scssc.php:447
scssc\$lib_if
static $lib_if
Definition: scssc.php:1927
scssc\$lib_rgba
static $lib_rgba
Definition: scssc.php:1954
scssc\extractInterpolation
extractInterpolation($list)
Definition: scssc.php:1330
scssc\lib_fade_in
lib_fade_in($args)
Definition: scssc.php:2247
scssc\$lib_darken
static $lib_darken
Definition: scssc.php:2189
scssc\lib_ceil
lib_ceil($args)
Definition: scssc.php:2304
scssc\$lib_comparable
static $lib_comparable
Definition: scssc.php:2462
scssc\$lib_mix
static $lib_mix
Definition: scssc.php:2097
scss_parser\$commentMultiLeft
static $commentMultiLeft
Definition: scssc.php:2687
scssc\getStoreEnv
getStoreEnv()
Definition: scssc.php:1520
scssc\$selfSelector
static $selfSelector
Definition: scssc.php:86
scssc\flattenSelectorSingle
flattenSelectorSingle($single)
Definition: scssc.php:425
scss_parser\matchString
matchString(&$m, $delim)
Match string looking for either ending delim, escape, or string interpolation.
Definition: scssc.php:4071
scssc\lib_hue
lib_hue($args)
Definition: scssc.php:2147
scss_parser\variable
variable(&$out)
Definition: scssc.php:3985
scssc\isSelfExtend
isSelfExtend($target, $origin)
Definition: scssc.php:137
scssc\pushExtends
pushExtends($target, $origin)
Definition: scssc.php:147
scssc\setFormatter
setFormatter($formatterName)
Definition: scssc.php:1634
scssc\$lib_fade_out
static $lib_fade_out
Definition: scssc.php:2262
scss_parser\whitespace
whitespace()
Definition: scssc.php:4117
scss_parser\progid
progid(&$out)
Definition: scssc.php:3420
scssc\lib_mix
lib_mix($args)
Definition: scssc.php:2098
scssc\$lib_abs
static $lib_abs
Definition: scssc.php:2310
scssc\compileImport
compileImport($rawPath, $out)
Definition: scssc.php:604
scss_parser\peek
peek($regex, &$out, $from=null)
Definition: scssc.php:4132
scssc\lib_grayscale
lib_grayscale($args)
Definition: scssc.php:2213
scssc\lib_opacify
lib_opacify($args)
Definition: scssc.php:2237
scss_parser\argValue
argValue(&$out)
Definition: scssc.php:3228
scssc\$lib_adjust_color
static $lib_adjust_color
Definition: scssc.php:1999
scssc\assertNumber
assertNumber($value)
Definition: scssc.php:1833