CWIS Developer Documentation
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 
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 
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 
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 
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 
4331  public $open = "{";
4332  public $tagSeparator = ",";
4333  public $assignSeparator = ":";
4334  public $break = "";
4335 
4336  public function indentStr($n = 0) {
4337  return "";
4338  }
4339 }
4340 
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 }
mediaQuery(&$out)
Definition: scssc.php:3172
op_eq($left, $right)
Definition: scssc.php:1185
lib_blue($args)
Definition: scssc.php:2074
static $lib_transparentize
Definition: scssc.php:2252
lib_adjust_hue($args)
Definition: scssc.php:2176
static $lib_unquote
Definition: scssc.php:2267
coerceForExpression($value)
Definition: scssc.php:1787
op_mul_number_number($left, $right)
Definition: scssc.php:1087
unregisterFunction($name)
Definition: scssc.php:1642
static $lib_grayscale
Definition: scssc.php:2212
lib_scale_color($args)
Definition: scssc.php:2049
lib_unquote($args)
Definition: scssc.php:2268
setExisting($name, $value, $env=null)
Definition: scssc.php:1534
adjust_color_helper($base, $alter, $i)
Definition: scssc.php:2003
parenValue(&$out)
Definition: scssc.php:3401
static $lib_round
Definition: scssc.php:2289
static $true
Definition: scssc.php:81
static $lib_complement
Definition: scssc.php:2219
static $namespaces
Definition: scssc.php:64
keyword(&$word, $eatWhitespace=null)
Definition: scssc.php:3995
op_and($left, $right, $shouldEval)
Definition: scssc.php:1122
makeOutputBlock($type, $selectors=null)
Definition: scssc.php:164
throwParseError($msg="parse error", $count=null)
Definition: scssc.php:4039
static $defaultValue
Definition: scssc.php:85
static $lib_change_color
Definition: scssc.php:2010
lib_max($args)
Definition: scssc.php:2329
throwError($msg=null)
Definition: scssc.php:2485
scale_color_helper($base, $scale, $i)
Definition: scssc.php:2025
static $lib_red
Definition: scssc.php:2061
lib_desaturate($args)
Definition: scssc.php:2206
parseChunk()
Parse a single chunk off the head of the buffer and append it to the current parse environment...
Definition: scssc.php:2790
$importCache
Definition: scssc.php:89
$userFunctions
Definition: scssc.php:91
lib_if($args)
Definition: scssc.php:1928
lib_unitless($args)
Definition: scssc.php:2457
block($block)
Definition: scssc.php:4193
sortArgs($prototype, $args)
Definition: scssc.php:1747
adjustHsl($color, $idx, $amount)
Definition: scssc.php:2167
change_color_helper($base, $alter, $i)
Definition: scssc.php:2014
lib_unit($args)
Definition: scssc.php:2448
static $lib_fade_in
Definition: scssc.php:2246
normalizeName($name)
Definition: scssc.php:1516
inputName()
Get name of requested .scss file.
Definition: scssc.php:4364
join($left, $right)
Join path components.
Definition: scssc.php:4355
registerFunction($name, $func)
Definition: scssc.php:1638
lib_comparable($args)
Definition: scssc.php:2463
lib_darken($args)
Definition: scssc.php:2190
value(&$out)
Definition: scssc.php:3350
static makeOperatorStr($operators)
Definition: scssc.php:2711
static $lib_quote
Definition: scssc.php:2274
lib_percentage($args)
Definition: scssc.php:2283
interpolation(&$out, $lookWhite=true)
Definition: scssc.php:3723
callBuiltin($name, $args, &$returnValue)
Definition: scssc.php:1706
static $unitTable
Definition: scssc.php:70
coerceColor($value)
Definition: scssc.php:1795
importsCacheName($out)
Get path to cached imports.
Definition: scssc.php:4409
findInput()
Get path to requested .scss file.
Definition: scssc.php:4380
lib_hsl($args)
Definition: scssc.php:2132
lib_red($args)
Definition: scssc.php:2062
compileValue($value)
Compiles a primitive value into a CSS property value.
Definition: scssc.php:1232
static $false
Definition: scssc.php:82
argumentDef(&$out)
Definition: scssc.php:3507
stripDefault(&$value)
Definition: scssc.php:3087
compileMediaQuery($queryList)
Definition: scssc.php:516
lib_saturation($args)
Definition: scssc.php:2154
lib_round($args)
Definition: scssc.php:2290
hasSelectorPlaceholder($selector)
Definition: scssc.php:494
op_color_number($op, $left, $right)
Definition: scssc.php:1173
lib_rgb($args)
Definition: scssc.php:1949
string(&$out)
Definition: scssc.php:3582
op_number_color($op, $left, $right)
Definition: scssc.php:1179
mergeMediaTypes($type1, $type2)
Definition: scssc.php:559
mediaParent($scope)
Definition: scssc.php:370
reduce($value, $inExp=false)
Definition: scssc.php:865
SCSS server.
Definition: scssc.php:4346
coerceString($value)
Definition: scssc.php:1812
pushBlock($selectors)
Definition: scssc.php:3123
compileNestedBlock($block, $selectors)
Definition: scssc.php:382
importFile($path, $out)
Definition: scssc.php:1646
$formatter
Definition: scssc.php:96
op_add_number_number($left, $right)
Definition: scssc.php:1083
listSeparatorForJoin($list1, $sep)
Definition: scssc.php:2377
propertyName(&$out)
Definition: scssc.php:3753
match($regex, &$out, $eatWhitespace=null)
Definition: scssc.php:4104
lib_zip($args)
Definition: scssc.php:2406
static $lib_index
Definition: scssc.php:1934
pushEnv($block=null)
Definition: scssc.php:1505
__construct($sourceName=null, $rootParser=true)
Constructor.
Definition: scssc.php:2696
normalizeValue($value)
Definition: scssc.php:1042
static $lib_unitless
Definition: scssc.php:2456
static $lib_append
Definition: scssc.php:2398
compileStringContent($string)
Definition: scssc.php:1316
lib_abs($args)
Definition: scssc.php:2311
compileBlock($block)
Recursively compiles a block.
Definition: scssc.php:411
static $null
Definition: scssc.php:83
static $commentMulti
Definition: scssc.php:2684
setImportPaths($path)
Definition: scssc.php:1626
static $lib_nth
Definition: scssc.php:2370
mixedKeyword(&$out)
Definition: scssc.php:3628
selectorSingle(&$out)
Definition: scssc.php:3839
unsetVariable($name)
Unset variable.
Definition: scssc.php:1607
lib_length($args)
Definition: scssc.php:2365
lib_min($args)
Definition: scssc.php:2317
matchExtendsSingle($single, &$outOrigin)
Definition: scssc.php:176
op_sub_number_number($left, $right)
Definition: scssc.php:1091
unit(&$unit)
Definition: scssc.php:3574
static $lib_hue
Definition: scssc.php:2146
static $operatorStr
Definition: scssc.php:2682
lib_alpha($args)
Definition: scssc.php:2080
lib_adjust_color($args)
Definition: scssc.php:2006
op_color_color($op, $left, $right)
Definition: scssc.php:1134
serve($salt= '')
Compile requested scss and serve css.
Definition: scssc.php:4486
injectVariables(array $args)
Definition: scssc.php:1564
static $lib_alpha
Definition: scssc.php:2079
lib_lighten($args)
Definition: scssc.php:2183
lib_append($args)
Definition: scssc.php:2399
compileRoot($rootBlock)
Definition: scssc.php:323
static $lib_lightness
Definition: scssc.php:2160
applyArguments($argDef, $argValues)
Definition: scssc.php:1435
compileSelectorPart($piece)
Definition: scssc.php:477
op_or($left, $right, $shouldEval)
Definition: scssc.php:1128
SCSS compiler written in PHP.
Definition: scssc.php:45
fixColor($c)
Definition: scssc.php:1850
compileMedia($media)
Definition: scssc.php:331
joinSelectors($parent, $child)
Definition: scssc.php:1368
flattenSelectors($block, $parentKey=null)
Definition: scssc.php:290
assertList($value)
Definition: scssc.php:1822
flattenList($value)
Definition: scssc.php:4159
expToString($exp)
Definition: scssc.php:837
argValues(&$out)
Definition: scssc.php:3220
normalizeNumber($number)
Definition: scssc.php:1064
selectors(&$out)
Definition: scssc.php:3793
static $lib_opacity
Definition: scssc.php:2089
static $lib_desaturate
Definition: scssc.php:2205
op_neq($left, $right)
Definition: scssc.php:1195
static $lib_ie_hex_str
Definition: scssc.php:2053
property($name, $value)
Definition: scssc.php:4189
append($statement, $pos=null)
Definition: scssc.php:3151
lib_rgba($args)
Definition: scssc.php:1957
static $lib_scale_color
Definition: scssc.php:2021
op_mod_number_number($left, $right)
Definition: scssc.php:1099
compile($code, $name=null)
Compile scss.
Definition: scssc.php:106
assertColor($value)
Definition: scssc.php:1828
lib_invert($args)
Definition: scssc.php:2225
static $lib_lighten
Definition: scssc.php:2182
color(&$out)
Definition: scssc.php:3547
SCSS base formatter.
Definition: scssc.php:4172
static $lib_join
Definition: scssc.php:2389
selector(&$out)
Definition: scssc.php:3812
static preg_quote($what)
Definition: scssc.php:4147
needsCompile($in, $out)
Determine whether .scss file needs to be re-compiled.
Definition: scssc.php:4421
popEnv()
Definition: scssc.php:1612
static $lib_floor
Definition: scssc.php:2296
placeholder(&$placeholder)
Definition: scssc.php:4005
static $operatorNames
Definition: scssc.php:48
static $lib_adjust_hue
Definition: scssc.php:2175
lib_floor($args)
Definition: scssc.php:2297
static $lib_unit
Definition: scssc.php:2447
lib_join($args)
Definition: scssc.php:2390
compileChildren($stms, $out)
Definition: scssc.php:509
SCSS nested formatter.
Definition: scssc.php:4239
toHSL($red, $green, $blue)
Definition: scssc.php:1859
pushSpecialBlock($type)
Definition: scssc.php:3134
setNumberPrecision($numberPrecision)
Definition: scssc.php:1630
coercePercent($value)
Definition: scssc.php:1839
static $VERSION
Definition: scssc.php:46
static $lib_type_of
Definition: scssc.php:2428
adjustAllChildren($block)
Definition: scssc.php:4243
mediaExpression(&$out)
Definition: scssc.php:3203
genericList(&$out, $parseItem, $delim="", $flatten=true)
Definition: scssc.php:3268
static $lib_percentage
Definition: scssc.php:2282
multiplySelectors($env)
Definition: scssc.php:1343
evalSelectorPart($piece)
Definition: scssc.php:451
mediaQueryList(&$out)
Definition: scssc.php:3168
lib_green($args)
Definition: scssc.php:2068
spaceList(&$out)
Definition: scssc.php:3263
static serveFrom($path)
Helper method to serve compiled scss.
Definition: scssc.php:4570
hueToRGB($m1, $m2, $h)
Definition: scssc.php:1886
op_gt_number_number($left, $right)
Definition: scssc.php:1203
static $lib_blue
Definition: scssc.php:2073
lib_ie_hex_str($args)
Definition: scssc.php:2054
op_lte_number_number($left, $right)
Definition: scssc.php:1207
combineSelectorSingle($base, $other)
Definition: scssc.php:222
getModifiedSinceHeader()
Get If-Modified-Since header from client request.
Definition: scssc.php:4443
static $whitePattern
Definition: scssc.php:2683
multiplyMedia($env, $childQueries=null)
Definition: scssc.php:1397
static $cssColors
CSS Colors.
Definition: scssc.php:2502
valueList(&$out)
Parse list.
Definition: scssc.php:3258
lib_change_color($args)
Definition: scssc.php:2017
parse($buffer)
Parser buffer.
Definition: scssc.php:2723
alter_color($args, $fn)
Definition: scssc.php:1970
compileSelector($selector)
Definition: scssc.php:470
argumentList(&$out)
Definition: scssc.php:3476
setVariables(array $variables)
Set variables.
Definition: scssc.php:1597
static $lib_saturate
Definition: scssc.php:2196
compileChild($child, $out)
Definition: scssc.php:631
op_gte_number_number($left, $right)
Definition: scssc.php:1199
static $lib_invert
Definition: scssc.php:2224
indentStr($n=0)
Definition: scssc.php:4185
$numberPrecision
Definition: scssc.php:94
getNormalizedNumbers($args)
Definition: scssc.php:2341
op_add($left, $right)
Definition: scssc.php:1104
lib_hsla($args)
Definition: scssc.php:2139
$registeredVars
Definition: scssc.php:92
lib_saturate($args)
Definition: scssc.php:2197
to($what, &$out, $until=false, $allowNewline=false)
Definition: scssc.php:4027
static $lib_length
Definition: scssc.php:2364
cacheName($fname)
Get path to cached .css file.
Definition: scssc.php:4400
static $lib_hsla
Definition: scssc.php:2137
findImport($url)
Definition: scssc.php:1667
lib_complement($args)
Definition: scssc.php:2220
expression(&$out)
Definition: scssc.php:3292
static $lib_opacify
Definition: scssc.php:2236
static $lib_saturation
Definition: scssc.php:2153
getParsedFiles()
Definition: scssc.php:1618
shouldEval($value)
Definition: scssc.php:852
static $lib_hsl
Definition: scssc.php:2131
$importPaths
Definition: scssc.php:88
lib_type_of($args)
Definition: scssc.php:2429
op_lt_number_number($left, $right)
Definition: scssc.php:1211
compile($in, $out)
Compile .scss file.
Definition: scssc.php:4466
op_div_number_number($left, $right)
Definition: scssc.php:1095
setRaw($name, $value)
Definition: scssc.php:1544
literal($what, $eatWhitespace=null)
Definition: scssc.php:3102
isTruthy($value)
Definition: scssc.php:847
getLineNo($pos)
Definition: scssc.php:4057
fileExists($name)
Definition: scssc.php:1702
static $lib_rgb
Definition: scssc.php:1948
lib_quote($args)
Definition: scssc.php:2275
lib_opacity($args)
Definition: scssc.php:2090
__construct($dir, $cacheDir=null, $scss=null)
Constructor.
Definition: scssc.php:4548
static $lib_ceil
Definition: scssc.php:2303
lib_counter($args)
Workaround IE7&#39;s content counter bug.
Definition: scssc.php:2480
lib_transparentize($args)
Definition: scssc.php:2253
matchExtends($selector, &$out, $from=0, $initial=true)
Definition: scssc.php:243
lib_lightness($args)
Definition: scssc.php:2161
SCSS parser.
Definition: scssc.php:2659
lib_fade_out($args)
Definition: scssc.php:2263
toBool($thing)
Definition: scssc.php:1215
format($block)
Definition: scssc.php:4225
lib_nth($args)
Definition: scssc.php:2371
lib_index($args)
Definition: scssc.php:1935
func(&$func)
Definition: scssc.php:3439
static $lib_green
Definition: scssc.php:2067
coerceUnit($number, $unit)
Definition: scssc.php:1074
SCSS compressed formatter.
Definition: scssc.php:4330
expHelper($lhs, $minP)
Definition: scssc.php:3316
toRGB($hue, $saturation, $lightness)
Definition: scssc.php:1905
seek($where=null)
Definition: scssc.php:4141
addImportPath($path)
Definition: scssc.php:1622
coerceList($item, $delim=",")
Definition: scssc.php:1427
openString($end, &$out, $nestingOpen=null)
Definition: scssc.php:3663
evalSelector($selector)
Definition: scssc.php:447
static $lib_if
Definition: scssc.php:1927
static $lib_rgba
Definition: scssc.php:1954
extractInterpolation($list)
Definition: scssc.php:1330
lib_fade_in($args)
Definition: scssc.php:2247
static $lib_darken
Definition: scssc.php:2189
lib_ceil($args)
Definition: scssc.php:2304
static $lib_comparable
Definition: scssc.php:2462
static $lib_mix
Definition: scssc.php:2097
getStoreEnv()
Definition: scssc.php:1520
static $selfSelector
Definition: scssc.php:86
flattenSelectorSingle($single)
Definition: scssc.php:425
matchString(&$m, $delim)
Match string looking for either ending delim, escape, or string interpolation.
Definition: scssc.php:4071
lib_hue($args)
Definition: scssc.php:2147
variable(&$out)
Definition: scssc.php:3985
isSelfExtend($target, $origin)
Definition: scssc.php:137
pushExtends($target, $origin)
Definition: scssc.php:147
setFormatter($formatterName)
Definition: scssc.php:1634
static $lib_fade_out
Definition: scssc.php:2262
whitespace()
Definition: scssc.php:4117
progid(&$out)
Definition: scssc.php:3420
lib_mix($args)
Definition: scssc.php:2098
static $lib_abs
Definition: scssc.php:2310
compileImport($rawPath, $out)
Definition: scssc.php:604
peek($regex, &$out, $from=null)
Definition: scssc.php:4132
lib_grayscale($args)
Definition: scssc.php:2213
lib_opacify($args)
Definition: scssc.php:2237
argValue(&$out)
Definition: scssc.php:3228
static $lib_adjust_color
Definition: scssc.php:1999
assertNumber($value)
Definition: scssc.php:1833