39 public static $VERSION =
"v0.5.0";
41 public static $TRUE = array(
"keyword",
"true");
42 public static $FALSE = array(
"keyword",
"false");
44 protected $libFunctions = array();
45 protected $registeredVars = array();
46 protected $preserveComments =
false;
48 public $vPrefix =
'@';
49 public $mPrefix =
'$';
50 public $parentSelector =
'&';
52 public $importDisabled =
false;
53 public $importDir =
'';
55 protected $numberPrecision = null;
57 protected $allParsedFiles = array();
61 protected $sourceParser = null;
62 protected $sourceLoc = null;
64 protected static $nextImportId = 0;
67 protected function findImport($url) {
68 foreach ((array) $this->importDir as $dir) {
69 $full = $dir.(substr($dir, -1) !=
'/' ?
'/' :
'').$url;
85 return is_file($name);
88 public static function compressList($items, $delim) {
89 if (!isset($items[1]) && isset($items[0]))
return $items[0];
90 else return array(
'list', $delim, $items);
93 public static function preg_quote($what) {
94 return preg_quote($what,
'/');
97 protected function tryImport($importPath, $parentBlock, $out) {
98 if ($importPath[0] ==
"function" && $importPath[1] ==
"url") {
99 $importPath = $this->flattenList($importPath[2]);
102 $str = $this->coerceString($importPath);
103 if ($str === null)
return false;
108 if (substr_compare($url,
'.css', -4, 4) === 0)
return false;
110 $realPath = $this->findImport($url);
112 if ($realPath === null)
return false;
114 if ($this->importDisabled) {
115 return array(
false,
"/* import disabled */");
118 if (isset($this->allParsedFiles[realpath($realPath)])) {
119 return array(
false, null);
122 $this->addParsedFile($realPath);
123 $parser = $this->makeParser($realPath);
124 $root = $parser->parse(file_get_contents($realPath));
127 foreach ($root->props as $prop) {
128 if ($prop[0] ==
"block") {
129 $prop[1]->parent = $parentBlock;
136 foreach ($root->children as $childName => $child) {
137 if (isset($parentBlock->children[$childName])) {
138 $parentBlock->children[$childName] = array_merge(
139 $parentBlock->children[$childName],
142 $parentBlock->children[$childName] = $child;
146 $pi = pathinfo($realPath);
147 $dir = $pi[
"dirname"];
149 list($top, $bottom) = $this->sortProps($root->props,
true);
150 $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
152 return array(
true, $bottom, $parser, $dir);
155 protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
156 $oldSourceParser = $this->sourceParser;
158 $oldImport = $this->importDir;
161 $this->importDir = (array) $this->importDir;
162 array_unshift($this->importDir, $importDir);
164 foreach ($props as $prop) {
165 $this->compileProp($prop, $block, $out);
168 $this->importDir = $oldImport;
169 $this->sourceParser = $oldSourceParser;
194 switch ($block->type) {
196 $this->compileRoot($block);
199 $this->compileCSSBlock($block);
202 $this->compileMedia($block);
205 $name =
"@".$block->name;
206 if (!empty($block->value)) {
207 $name .=
" ".$this->compileValue($this->reduce($block->value));
210 $this->compileNestedBlock($block, array($name));
213 $this->
throwError(
"unknown block type: $block->type\n");
217 protected function compileCSSBlock($block) {
218 $env = $this->pushEnv();
220 $selectors = $this->compileSelectors($block->tags);
221 $env->selectors = $this->multiplySelectors($selectors);
222 $out = $this->makeOutputBlock(null, $env->selectors);
224 $this->scope->children[] = $out;
225 $this->compileProps($block, $out);
227 $block->scope = $env;
231 protected function compileMedia($media) {
232 $env = $this->pushEnv($media);
233 $parentScope = $this->mediaParent($this->scope);
235 $query = $this->compileMediaQuery($this->multiplyMedia($env));
237 $this->scope = $this->makeOutputBlock($media->type, array($query));
238 $parentScope->children[] = $this->scope;
240 $this->compileProps($media, $this->scope);
242 if (count($this->scope->lines) > 0) {
243 $orphanSelelectors = $this->findClosestSelectors();
244 if (!is_null($orphanSelelectors)) {
245 $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
246 $orphan->lines = $this->scope->lines;
247 array_unshift($this->scope->children, $orphan);
248 $this->scope->lines = array();
252 $this->scope = $this->scope->parent;
256 protected function mediaParent($scope) {
257 while (!empty($scope->parent)) {
258 if (!empty($scope->type) && $scope->type !=
"media") {
261 $scope = $scope->parent;
267 protected function compileNestedBlock($block, $selectors) {
268 $this->pushEnv($block);
269 $this->scope = $this->makeOutputBlock($block->type, $selectors);
270 $this->scope->parent->children[] = $this->scope;
272 $this->compileProps($block, $this->scope);
274 $this->scope = $this->scope->parent;
278 protected function compileRoot($root) {
280 $this->scope = $this->makeOutputBlock($root->type);
281 $this->compileProps($root, $this->scope);
285 protected function compileProps($block, $out) {
286 foreach ($this->sortProps($block->props) as $prop) {
287 $this->compileProp($prop, $block, $out);
301 foreach ($lines as $line) {
302 if (strpos($line,
'/*') === 0) {
306 if (!in_array($line, $unique)) {
309 array_splice($unique, array_search($line, $unique), 0, $comments);
312 return array_merge($unique, $comments);
315 protected function sortProps($props, $split =
false) {
321 foreach ($props as $prop) {
328 if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
329 $vars = array_merge($vars, $stack);
331 $other = array_merge($other, $stack);
336 $id = self::$nextImportId++;
339 $imports = array_merge($imports, $stack);
340 $other[] = array(
"import_mixin", $id);
345 $other = array_merge($other, $stack);
350 $other = array_merge($other, $stack);
353 return array(array_merge($imports, $vars), $other);
355 return array_merge($imports, $vars, $other);
359 protected function compileMediaQuery($queries) {
360 $compiledQueries = array();
361 foreach ($queries as $query) {
363 foreach ($query as $q) {
366 $parts[] = implode(
" ", array_slice($q, 1));
370 $parts[] =
"($q[1]: ".
373 $parts[] =
"($q[1])";
382 if (count($parts) > 0) {
383 $compiledQueries[] = implode(
" and ", $parts);
388 if (!empty($parts)) {
390 implode($this->formatter->selectorSeparator, $compiledQueries);
395 protected function multiplyMedia($env, $childQueries = null) {
397 !empty($env->block->type) && $env->block->type !=
"media"
399 return $childQueries;
403 if (empty($env->block->type)) {
404 return $this->multiplyMedia($env->parent, $childQueries);
408 $queries = $env->block->queries;
409 if (is_null($childQueries)) {
412 foreach ($queries as $parent) {
413 foreach ($childQueries as $child) {
414 $out[] = array_merge($parent, $child);
419 return $this->multiplyMedia($env->parent, $out);
422 protected function expandParentSelectors(&$tag, $replace) {
423 $parts = explode(
"$&$", $tag);
425 foreach ($parts as &$part) {
426 $part = str_replace($this->parentSelector, $replace, $part, $c);
429 $tag = implode($this->parentSelector, $parts);
433 protected function findClosestSelectors() {
436 while ($env !== null) {
437 if (isset($env->selectors)) {
438 $selectors = $env->selectors;
449 protected function multiplySelectors($selectors) {
452 $parentSelectors = $this->findClosestSelectors();
453 if (is_null($parentSelectors)) {
455 foreach ($selectors as &$s) {
456 $this->expandParentSelectors($s,
"");
463 foreach ($parentSelectors as $parent) {
464 foreach ($selectors as $child) {
465 $count = $this->expandParentSelectors($child, $parent);
469 $out[] = trim($child);
471 $out[] = trim($parent.
' '.$child);
480 protected function compileSelectors($selectors) {
483 foreach ($selectors as $s) {
486 $out[] = trim($this->
compileValue($this->reduce($value)));
495 protected function eq($left, $right) {
496 return $left == $right;
499 protected function patternMatch($block, $orderedArgs, $keywordArgs) {
502 if (!empty($block->guards)) {
503 $groupPassed =
false;
504 foreach ($block->guards as $guardGroup) {
505 foreach ($guardGroup as $guard) {
507 $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
510 if ($guard[0] ==
"negate") {
515 $passed = $this->reduce($guard) == self::$TRUE;
516 if ($negate) $passed = !$passed;
523 $groupPassed =
false;
528 if ($groupPassed)
break;
536 if (empty($block->args)) {
537 return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
540 $remainingArgs = $block->args;
542 $remainingArgs = array();
543 foreach ($block->args as $arg) {
544 if ($arg[0] ==
"arg" && isset($keywordArgs[$arg[1]])) {
548 $remainingArgs[] = $arg;
554 foreach ($remainingArgs as $i => $arg) {
557 if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
563 if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
573 if ($block->isVararg) {
576 $numMatched = $i + 1;
578 return $numMatched >= count($orderedArgs);
582 protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip = array()) {
584 foreach ($blocks as $block) {
586 if (isset($skip[$block->id]) && !isset($block->args)) {
590 if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
599 protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen = array()) {
600 if ($searchIn == null)
return null;
601 if (isset($seen[$searchIn->id]))
return null;
602 $seen[$searchIn->id] =
true;
606 if (isset($searchIn->children[$name])) {
607 $blocks = $searchIn->children[$name];
608 if (count($path) == 1) {
609 $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
610 if (!empty($matches)) {
617 foreach ($blocks as $subBlock) {
618 $subMatches = $this->findBlocks($subBlock,
619 array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
621 if (!is_null($subMatches)) {
622 foreach ($subMatches as $sm) {
628 return count($matches) > 0 ? $matches : null;
631 if ($searchIn->parent === $searchIn)
return null;
632 return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
637 protected function zipSetArgs($args, $orderedValues, $keywordValues) {
638 $assignedValues = array();
641 foreach ($args as $a) {
642 if ($a[0] ==
"arg") {
643 if (isset($keywordValues[$a[1]])) {
645 $value = $keywordValues[$a[1]];
646 } elseif (isset($orderedValues[$i])) {
648 $value = $orderedValues[$i];
650 } elseif (isset($a[2])) {
654 $this->
throwError(
"Failed to assign arg ".$a[1]);
658 $value = $this->reduce($value);
659 $this->set($a[1], $value);
660 $assignedValues[] = $value;
669 if ($last[0] ==
"rest") {
670 $rest = array_slice($orderedValues, count($args) - 1);
671 $this->set($last[1], $this->reduce(array(
"list",
" ", $rest)));
675 $this->env->arguments = $assignedValues + $orderedValues;
679 protected function compileProp($prop, $block, $out) {
681 $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
685 list(, $name, $value) = $prop;
686 if ($name[0] == $this->vPrefix) {
687 $this->set($name, $value);
689 $out->lines[] = $this->formatter->property($name,
694 list(, $child) = $prop;
698 list(, $path, $args, $suffix) = $prop;
700 $orderedArgs = array();
701 $keywordArgs = array();
702 foreach ((array) $args as $arg) {
706 if (!isset($arg[2])) {
707 $orderedArgs[] = $this->reduce(array(
"variable", $arg[1]));
709 $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
714 $orderedArgs[] = $this->reduce($arg[1]);
717 $this->
throwError(
"Unknown arg type: ".$arg[0]);
721 $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
723 if ($mixins === null) {
724 $this->
throwError(
"{$prop[1][0]} is undefined");
727 foreach ($mixins as $mixin) {
728 if ($mixin === $block && !$orderedArgs) {
733 if (isset($mixin->parent->scope)) {
735 $mixinParentEnv = $this->pushEnv();
736 $mixinParentEnv->storeParent = $mixin->parent->scope;
740 if (isset($mixin->args)) {
743 $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
746 $oldParent = $mixin->parent;
747 if ($mixin != $block) $mixin->parent = $block;
749 foreach ($this->sortProps($mixin->props) as $subProp) {
750 if ($suffix !== null &&
751 $subProp[0] ==
"assign" &&
752 is_string($subProp[1]) &&
753 $subProp[1][0] != $this->vPrefix
757 array($subProp[2], array(
'keyword', $suffix))
761 $this->compileProp($subProp, $mixin, $out);
764 $mixin->parent = $oldParent;
766 if ($haveArgs) $this->popEnv();
767 if ($haveScope) $this->popEnv();
772 $out->lines[] = $prop[1];
775 list(, $name, $value) = $prop;
776 $out->lines[] =
"@$name ".$this->compileValue($this->reduce($value)).
';';
779 $out->lines[] = $prop[1];
782 list(, $importPath, $importId) = $prop;
783 $importPath = $this->reduce($importPath);
785 if (!isset($this->env->imports)) {
786 $this->env->imports = array();
789 $result = $this->tryImport($importPath, $block, $out);
791 $this->env->imports[$importId] = $result ===
false ?
792 array(
false,
"@import ".$this->
compileValue($importPath).
";") : $result;
796 list(,$importId) = $prop;
797 $import = $this->env->imports[$importId];
798 if ($import[0] ===
false) {
799 if (isset($import[1])) {
800 $out->lines[] = $import[1];
803 list(, $bottom, $parser, $importDir) = $import;
804 $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
809 $this->
throwError(
"unknown op: {$prop[0]}\n");
830 return implode($value[1], array_map(array($this,
'compileValue'), $value[2]));
832 if (!empty($this->formatter->compressColors)) {
840 list(, $num, $unit) = $value;
843 if ($this->numberPrecision !== null) {
844 $num = round($num, $this->numberPrecision);
849 list(, $delim, $content) = $value;
850 foreach ($content as &$part) {
851 if (is_array($part)) {
855 return $delim.implode($content).$delim;
861 list(, $r, $g, $b) = $value;
866 if (count($value) == 5 && $value[4] != 1) {
867 return 'rgba('.$r.
','.$g.
','.$b.
','.$value[4].
')';
870 $h = sprintf(
"#%02x%02x%02x", $r, $g, $b);
872 if (!empty($this->formatter->compressColors)) {
874 if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
875 $h =
'#'.$h[1].$h[3].$h[5];
882 list(, $name, $args) = $value;
885 $this->
throwError(
"unknown value type: $value[0]");
889 protected function lib_pow($args) {
890 list($base, $exp) = $this->assertArgs($args, 2,
"pow");
891 return pow($this->assertNumber($base), $this->assertNumber($exp));
894 protected function lib_pi() {
898 protected function lib_mod($args) {
899 list($a, $b) = $this->assertArgs($args, 2,
"mod");
900 return $this->assertNumber($a) % $this->assertNumber($b);
903 protected function lib_tan($num) {
904 return tan($this->assertNumber($num));
907 protected function lib_sin($num) {
908 return sin($this->assertNumber($num));
911 protected function lib_cos($num) {
912 return cos($this->assertNumber($num));
915 protected function lib_atan($num) {
916 $num = atan($this->assertNumber($num));
917 return array(
"number", $num,
"rad");
920 protected function lib_asin($num) {
921 $num = asin($this->assertNumber($num));
922 return array(
"number", $num,
"rad");
925 protected function lib_acos($num) {
926 $num = acos($this->assertNumber($num));
927 return array(
"number", $num,
"rad");
930 protected function lib_sqrt($num) {
931 return sqrt($this->assertNumber($num));
934 protected function lib_extract($value) {
935 list($list, $idx) = $this->assertArgs($value, 2,
"extract");
936 $idx = $this->assertNumber($idx);
938 if ($list[0] ==
"list" && isset($list[2][$idx - 1])) {
939 return $list[2][$idx - 1];
943 protected function lib_isnumber($value) {
944 return $this->toBool($value[0] ==
"number");
947 protected function lib_isstring($value) {
948 return $this->toBool($value[0] ==
"string");
951 protected function lib_iscolor($value) {
952 return $this->toBool($this->coerceColor($value));
955 protected function lib_iskeyword($value) {
956 return $this->toBool($value[0] ==
"keyword");
959 protected function lib_ispixel($value) {
960 return $this->toBool($value[0] ==
"number" && $value[2] ==
"px");
963 protected function lib_ispercentage($value) {
964 return $this->toBool($value[0] ==
"number" && $value[2] ==
"%");
967 protected function lib_isem($value) {
968 return $this->toBool($value[0] ==
"number" && $value[2] ==
"em");
971 protected function lib_isrem($value) {
972 return $this->toBool($value[0] ==
"number" && $value[2] ==
"rem");
975 protected function lib_rgbahex($color) {
976 $color = $this->coerceColor($color);
977 if (is_null($color)) {
978 $this->
throwError(
"color expected for rgbahex");
981 return sprintf(
"#%02x%02x%02x%02x",
982 isset($color[4]) ? $color[4] * 255 : 255,
989 protected function lib_argb($color) {
990 return $this->lib_rgbahex($color);
1000 $mime = ($value[0] ===
'list') ? $value[2][0][2] : null;
1001 $url = ($value[0] ===
'list') ? $value[2][1][2][0] : $value[2][0];
1003 $fullpath = $this->findImport($url);
1005 if ($fullpath && ($fsize = filesize($fullpath)) !==
false) {
1007 if ($fsize / 1024 < 32) {
1008 if (is_null($mime)) {
1009 if (class_exists(
'finfo')) {
1010 $finfo =
new finfo(FILEINFO_MIME);
1011 $mime = explode(
'; ', $finfo->file($fullpath));
1013 } elseif (function_exists(
'mime_content_type')) {
1014 $mime = mime_content_type($fullpath);
1018 if (!is_null($mime))
1019 $url = sprintf(
'data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
1023 return 'url("'.$url.
'")';
1027 protected function lib_e($arg) {
1031 if (isset($items[0])) {
1032 return $this->lib_e($items[0]);
1046 protected function lib__sprintf($args) {
1047 if ($args[0] !=
"list")
return $args;
1049 $string = array_shift($values);
1050 $template = $this->
compileValue($this->lib_e($string));
1054 if (preg_match_all(
'/%[dsa]/', $template, $m)) {
1055 foreach ($m[0] as $match) {
1056 $val = isset($values[$i]) ?
1057 $this->reduce($values[$i]) : array(
'keyword',
'');
1060 if ($color = $this->coerceColor($val)) {
1066 $template = preg_replace(
'/'.self::preg_quote($match).
'/',
1067 $rep, $template, 1);
1071 $d = $string[0] ==
"string" ? $string[1] :
'"';
1072 return array(
"string", $d, array($template));
1075 protected function lib_floor($arg) {
1076 $value = $this->assertNumber($arg);
1077 return array(
"number", floor($value), $arg[2]);
1080 protected function lib_ceil($arg) {
1081 $value = $this->assertNumber($arg);
1082 return array(
"number", ceil($value), $arg[2]);
1085 protected function lib_round($arg) {
1086 if ($arg[0] !=
"list") {
1087 $value = $this->assertNumber($arg);
1088 return array(
"number", round($value), $arg[2]);
1090 $value = $this->assertNumber($arg[2][0]);
1091 $precision = $this->assertNumber($arg[2][1]);
1092 return array(
"number", round($value, $precision), $arg[2][0][2]);
1096 protected function lib_unit($arg) {
1097 if ($arg[0] ==
"list") {
1098 list($number, $newUnit) = $arg[2];
1099 return array(
"number", $this->assertNumber($number),
1102 return array(
"number", $this->assertNumber($arg),
"");
1111 if ($args[0] !=
'list' || count($args[2]) < 2) {
1112 return array(array(
'color', 0, 0, 0), 0);
1114 list($color, $delta) = $args[2];
1115 $color = $this->assertColor($color);
1116 $delta = floatval($delta[1]);
1118 return array($color, $delta);
1121 protected function lib_darken($args) {
1122 list($color, $delta) = $this->
colorArgs($args);
1124 $hsl = $this->toHSL($color);
1125 $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1126 return $this->
toRGB($hsl);
1129 protected function lib_lighten($args) {
1130 list($color, $delta) = $this->
colorArgs($args);
1132 $hsl = $this->toHSL($color);
1133 $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1134 return $this->
toRGB($hsl);
1137 protected function lib_saturate($args) {
1138 list($color, $delta) = $this->
colorArgs($args);
1140 $hsl = $this->toHSL($color);
1141 $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1142 return $this->
toRGB($hsl);
1145 protected function lib_desaturate($args) {
1146 list($color, $delta) = $this->
colorArgs($args);
1148 $hsl = $this->toHSL($color);
1149 $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1150 return $this->
toRGB($hsl);
1153 protected function lib_spin($args) {
1154 list($color, $delta) = $this->
colorArgs($args);
1156 $hsl = $this->toHSL($color);
1158 $hsl[1] = $hsl[1] + $delta % 360;
1163 return $this->
toRGB($hsl);
1166 protected function lib_fadeout($args) {
1167 list($color, $delta) = $this->
colorArgs($args);
1168 $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta / 100);
1172 protected function lib_fadein($args) {
1173 list($color, $delta) = $this->
colorArgs($args);
1174 $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta / 100);
1178 protected function lib_hue($color) {
1179 $hsl = $this->toHSL($this->assertColor($color));
1180 return round($hsl[1]);
1183 protected function lib_saturation($color) {
1184 $hsl = $this->toHSL($this->assertColor($color));
1185 return round($hsl[2]);
1188 protected function lib_lightness($color) {
1189 $hsl = $this->toHSL($this->assertColor($color));
1190 return round($hsl[3]);
1195 protected function lib_alpha($value) {
1196 if (!is_null($color = $this->coerceColor($value))) {
1197 return isset($color[4]) ? $color[4] : 1;
1202 protected function lib_fade($args) {
1203 list($color, $alpha) = $this->
colorArgs($args);
1204 $color[4] = $this->clamp($alpha / 100.0);
1208 protected function lib_percentage($arg) {
1209 $num = $this->assertNumber($arg);
1210 return array(
"number", $num * 100,
"%");
1225 $white = [
'color', 255, 255, 255];
1226 if ($args[0] ==
'color') {
1227 return $this->lib_mix([
'list',
',', [$white, $args]]);
1228 } elseif ($args[0] ==
"list" && count($args[2]) == 2) {
1229 return $this->lib_mix([$args[0], $args[1], [$white, $args[2][0], $args[2][1]]]);
1231 $this->
throwError(
"tint expects (color, weight)");
1247 $black = [
'color', 0, 0, 0];
1248 if ($args[0] ==
'color') {
1249 return $this->lib_mix([
'list',
',', [$black, $args]]);
1250 } elseif ($args[0] ==
"list" && count($args[2]) == 2) {
1251 return $this->lib_mix([$args[0], $args[1], [$black, $args[2][0], $args[2][1]]]);
1253 $this->
throwError(
"shade expects (color, weight)");
1260 protected function lib_mix($args) {
1261 if ($args[0] !=
"list" || count($args[2]) < 2)
1262 $this->
throwError(
"mix expects (color1, color2, weight)");
1264 list($first, $second) = $args[2];
1265 $first = $this->assertColor($first);
1266 $second = $this->assertColor($second);
1268 $first_a = $this->lib_alpha($first);
1269 $second_a = $this->lib_alpha($second);
1271 if (isset($args[2][2])) {
1272 $weight = $args[2][2][1] / 100.0;
1277 $w = $weight * 2 - 1;
1278 $a = $first_a - $second_a;
1280 $w1 = (($w * $a == -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
1283 $new = array(
'color',
1284 $w1 * $first[1] + $w2 * $second[1],
1285 $w1 * $first[2] + $w2 * $second[2],
1286 $w1 * $first[3] + $w2 * $second[3],
1289 if ($first_a != 1.0 || $second_a != 1.0) {
1290 $new[] = $first_a * $weight + $second_a * ($weight - 1);
1293 return $this->fixColor($new);
1296 protected function lib_contrast($args) {
1297 $darkColor = array(
'color', 0, 0, 0);
1298 $lightColor = array(
'color', 255, 255, 255);
1301 if ($args[0] ==
'list') {
1302 $inputColor = (isset($args[2][0])) ? $this->assertColor($args[2][0]) : $lightColor;
1303 $darkColor = (isset($args[2][1])) ? $this->assertColor($args[2][1]) : $darkColor;
1304 $lightColor = (isset($args[2][2])) ? $this->assertColor($args[2][2]) : $lightColor;
1305 $threshold = (isset($args[2][3])) ? $this->assertNumber($args[2][3]) : $threshold;
1308 $inputColor = $this->assertColor($args);
1311 $inputColor = $this->coerceColor($inputColor);
1312 $darkColor = $this->coerceColor($darkColor);
1313 $lightColor = $this->coerceColor($lightColor);
1316 if ($this->toLuma($darkColor) > $this->toLuma($lightColor)) {
1318 $lightColor = $darkColor;
1322 $inputColor_alpha = $this->lib_alpha($inputColor);
1323 if (($this->toLuma($inputColor) * $inputColor_alpha) < $threshold) {
1329 private function toLuma($color) {
1330 list(, $r, $g, $b) = $this->coerceColor($color);
1336 $r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4);
1337 $g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4);
1338 $b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4);
1340 return (0.2126 * $r) + (0.7152 * $g) + (0.0722 * $b);
1343 protected function lib_luma($color) {
1344 return array(
"number", round($this->toLuma($color) * 100, 8),
"%");
1348 public function assertColor($value, $error =
"expected color value") {
1349 $color = $this->coerceColor($value);
1350 if (is_null($color)) $this->
throwError($error);
1354 public function assertNumber($value, $error =
"expecting number") {
1355 if ($value[0] ==
"number")
return $value[1];
1359 public function assertArgs($value, $expectedArgs, $name =
"") {
1360 if ($expectedArgs == 1) {
1363 if ($value[0] !==
"list" || $value[1] !=
",") $this->
throwError(
"expecting list");
1364 $values = $value[2];
1365 $numValues = count($values);
1366 if ($expectedArgs != $numValues) {
1371 $this->
throwError(
"${name}expecting $expectedArgs arguments, got $numValues");
1378 protected function toHSL($color) {
1379 if ($color[0] ===
'hsl') {
1383 $r = $color[1] / 255;
1384 $g = $color[2] / 255;
1385 $b = $color[3] / 255;
1387 $min = min($r, $g, $b);
1388 $max = max($r, $g, $b);
1390 $L = ($min + $max) / 2;
1395 $S = ($max - $min) / ($max + $min);
1397 $S = ($max - $min) / (2.0 - $max - $min);
1400 $H = ($g - $b) / ($max - $min);
1401 } elseif ($g == $max) {
1402 $H = 2.0 + ($b - $r) / ($max - $min);
1403 } elseif ($b == $max) {
1404 $H = 4.0 + ($r - $g) / ($max - $min);
1410 ($H < 0 ? $H + 6 : $H) * 60,
1415 if (count($color) > 4) {
1422 protected function toRGB_helper($comp, $temp1, $temp2) {
1425 } elseif ($comp > 1) {
1429 if (6 * $comp < 1) {
1430 return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1432 if (2 * $comp < 1) {
1435 if (3 * $comp < 2) {
1436 return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6;
1447 if ($color[0] ===
'color') {
1451 $H = $color[1] / 360;
1452 $S = $color[2] / 100;
1453 $L = $color[3] / 100;
1459 $L * (1.0 + $S) : $L + $S - $L * $S;
1461 $temp1 = 2.0 * $L - $temp2;
1463 $r = $this->toRGB_helper($H + 1 / 3, $temp1, $temp2);
1464 $g = $this->toRGB_helper($H, $temp1, $temp2);
1465 $b = $this->toRGB_helper($H - 1 / 3, $temp1, $temp2);
1469 $out = array(
'color', $r * 255, $g * 255, $b * 255);
1470 if (count($color) > 4) {
1477 protected function clamp($v, $max = 1, $min = 0) {
1478 return min($max, max($min, $v));
1487 if ($func[2][0] !=
'list') {
1491 $rawComponents = $func[2][2];
1493 if ($fname ==
'hsl' || $fname ==
'hsla') {
1494 $hsl = array(
'hsl');
1496 foreach ($rawComponents as $c) {
1497 $val = $this->reduce($c);
1498 $val = isset($val[1]) ? floatval($val[1]) : 0;
1508 $hsl[] = $this->clamp($val, $clamp);
1512 while (count($hsl) < 4) {
1515 return $this->
toRGB($hsl);
1517 } elseif ($fname ==
'rgb' || $fname ==
'rgba') {
1518 $components = array();
1520 foreach ($rawComponents as $c) {
1521 $c = $this->reduce($c);
1523 if ($c[0] ==
"number" && $c[2] ==
"%") {
1524 $components[] = 255 * ($c[1] / 100);
1526 $components[] = floatval($c[1]);
1528 } elseif ($i == 4) {
1529 if ($c[0] ==
"number" && $c[2] ==
"%") {
1530 $components[] = 1.0 * ($c[1] / 100);
1532 $components[] = floatval($c[1]);
1538 while (count($components) < 3) {
1541 array_unshift($components,
'color');
1542 return $this->fixColor($components);
1548 protected function reduce($value, $forExpression =
false) {
1549 switch ($value[0]) {
1551 $reduced = $this->reduce($value[1]);
1553 $res = $this->reduce(array(
"variable", $this->vPrefix.$var));
1555 if ($res[0] ==
"raw_color") {
1556 $res = $this->coerceColor($res);
1559 if (empty($value[2])) $res = $this->lib_e($res);
1564 if (is_array($key)) {
1565 $key = $this->reduce($key);
1566 $key = $this->vPrefix.$this->compileValue($this->lib_e($key));
1569 $seen = & $this->env->seenNames;
1571 if (!empty($seen[$key])) {
1572 $this->
throwError(
"infinite loop detected: $key");
1576 $out = $this->reduce($this->
get($key));
1577 $seen[$key] =
false;
1580 foreach ($value[2] as &$item) {
1581 $item = $this->reduce($item, $forExpression);
1585 return $this->evaluate($value);
1587 foreach ($value[2] as &$part) {
1588 if (is_array($part)) {
1589 $strip = $part[0] ==
"variable";
1590 $part = $this->reduce($part);
1591 if ($strip) $part = $this->lib_e($part);
1596 list(,$inner) = $value;
1597 return $this->lib_e($this->reduce($inner));
1600 if ($color)
return $color;
1602 list(, $name, $args) = $value;
1603 if ($name ==
"%") $name =
"_sprintf";
1605 $f = isset($this->libFunctions[$name]) ?
1606 $this->libFunctions[$name] : array($this,
'lib_'.str_replace(
'-',
'_', $name));
1608 if (is_callable($f)) {
1609 if ($args[0] ==
'list')
1610 $args = self::compressList($args[2], $args[1]);
1612 $ret = call_user_func($f, $this->reduce($args,
true), $this);
1614 if (is_null($ret)) {
1615 return array(
"string",
"", array(
1616 $name,
"(", $args,
")"
1621 if (is_numeric($ret)) {
1622 $ret = array(
'number', $ret,
"");
1623 } elseif (!is_array($ret)) {
1624 $ret = array(
'keyword', $ret);
1631 $value[2] = $this->reduce($value[2]);
1634 list(, $op, $exp) = $value;
1635 $exp = $this->reduce($exp);
1637 if ($exp[0] ==
"number") {
1646 return array(
"string",
"", array($op, $exp));
1649 if ($forExpression) {
1650 switch ($value[0]) {
1652 if ($color = $this->coerceColor($value)) {
1657 return $this->coerceColor($value);
1666 protected function coerceColor($value) {
1667 switch ($value[0]) {
1668 case 'color':
return $value;
1670 $c = array(
"color", 0, 0, 0);
1671 $colorStr = substr($value[1], 1);
1672 $num = hexdec($colorStr);
1673 $width = strlen($colorStr) == 3 ? 16 : 256;
1675 for ($i = 3; $i > 0; $i--) {
1679 $c[$i] = $t * (256 / $width) + $t * floor(16 / $width);
1685 if (isset(self::$cssColors[$name])) {
1686 $rgba = explode(
',', self::$cssColors[$name]);
1688 if (isset($rgba[3])) {
1689 return array(
'color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
1691 return array(
'color', $rgba[0], $rgba[1], $rgba[2]);
1698 protected function coerceString($value) {
1699 switch ($value[0]) {
1703 return array(
"string",
"", array($value[1]));
1709 protected function flattenList($value) {
1710 if ($value[0] ==
"list" && count($value[2]) == 1) {
1711 return $this->flattenList($value[2][0]);
1716 public function toBool($a) {
1717 return $a ? self::$TRUE : self::$FALSE;
1721 protected function evaluate($exp) {
1722 list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
1724 $left = $this->reduce($left,
true);
1725 $right = $this->reduce($right,
true);
1727 if ($leftColor = $this->coerceColor($left)) {
1731 if ($rightColor = $this->coerceColor($right)) {
1732 $right = $rightColor;
1740 return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1744 return $this->toBool($this->eq($left, $right));
1747 if ($op ==
"+" && !is_null($str = $this->stringConcatenate($left, $right))) {
1752 $fname =
"op_${ltype}_${rtype}";
1753 if (is_callable(array($this, $fname))) {
1754 $out = $this->$fname($op, $left, $right);
1755 if (!is_null($out))
return $out;
1761 $paddedOp =
" ".$paddedOp;
1767 return array(
"string",
"", array($left, $paddedOp, $right));
1770 protected function stringConcatenate($left, $right) {
1771 if ($strLeft = $this->coerceString($left)) {
1772 if ($right[0] ==
"string") {
1775 $strLeft[2][] = $right;
1779 if ($strRight = $this->coerceString($right)) {
1780 array_unshift($strRight[2], $left);
1787 protected function fixColor($c) {
1788 foreach (range(1, 3) as $i) {
1789 if ($c[$i] < 0) $c[$i] = 0;
1790 if ($c[$i] > 255) $c[$i] = 255;
1796 protected function op_number_color($op, $lft, $rgt) {
1797 if ($op ==
'+' || $op ==
'*') {
1798 return $this->op_color_number($op, $rgt, $lft);
1802 protected function op_color_number($op, $lft, $rgt) {
1803 if ($rgt[0] ==
'%') $rgt[1] /= 100;
1805 return $this->op_color_color($op, $lft,
1806 array_fill(1, count($lft) - 1, $rgt[1]));
1809 protected function op_color_color($op, $left, $right) {
1810 $out = array(
'color');
1811 $max = count($left) > count($right) ? count($left) : count($right);
1812 foreach (range(1, $max - 1) as $i) {
1813 $lval = isset($left[$i]) ? $left[$i] : 0;
1814 $rval = isset($right[$i]) ? $right[$i] : 0;
1817 $out[] = $lval + $rval;
1820 $out[] = $lval - $rval;
1823 $out[] = $lval * $rval;
1826 $out[] = $lval % $rval;
1830 $this->
throwError(
"evaluate error: can't divide by zero");
1832 $out[] = $lval / $rval;
1835 $this->
throwError(
'evaluate error: color op number failed on op '.$op);
1838 return $this->fixColor($out);
1841 public function lib_red($color) {
1842 $color = $this->coerceColor($color);
1843 if (is_null($color)) {
1844 $this->
throwError(
'color expected for red()');
1850 public function lib_green($color) {
1851 $color = $this->coerceColor($color);
1852 if (is_null($color)) {
1853 $this->
throwError(
'color expected for green()');
1859 public function lib_blue($color) {
1860 $color = $this->coerceColor($color);
1861 if (is_null($color)) {
1862 $this->
throwError(
'color expected for blue()');
1870 protected function op_number_number($op, $left, $right) {
1871 $unit = empty($left[2]) ? $right[2] : $left[2];
1876 $value = $left[1] + $right[1];
1879 $value = $left[1] * $right[1];
1882 $value = $left[1] - $right[1];
1885 $value = $left[1] % $right[1];
1888 if ($right[1] == 0) $this->
throwError(
'parse error: divide by zero');
1889 $value = $left[1] / $right[1];
1892 return $this->toBool($left[1] < $right[1]);
1894 return $this->toBool($left[1] > $right[1]);
1896 return $this->toBool($left[1] >= $right[1]);
1898 return $this->toBool($left[1] <= $right[1]);
1900 $this->
throwError(
'parse error: unknown number operator: '.$op);
1903 return array(
"number", $value, $unit);
1909 protected function makeOutputBlock($type, $selectors = null) {
1911 $b->lines = array();
1912 $b->children = array();
1913 $b->selectors = $selectors;
1915 $b->parent = $this->scope;
1920 protected function pushEnv($block = null) {
1922 $e->parent = $this->env;
1923 $e->store = array();
1931 protected function popEnv() {
1933 $this->env = $this->env->parent;
1938 protected function set($name, $value) {
1939 $this->env->store[$name] = $value;
1944 protected function get($name) {
1945 $current = $this->env;
1947 $isArguments = $name == $this->vPrefix.
'arguments';
1949 if ($isArguments && isset($current->arguments)) {
1950 return array(
'list',
' ', $current->arguments);
1953 if (isset($current->store[$name])) {
1954 return $current->store[$name];
1957 $current = isset($current->storeParent) ?
1958 $current->storeParent : $current->parent;
1961 $this->
throwError(
"variable $name is undefined");
1965 protected function injectVariables($args) {
1968 foreach ($args as $name => $strValue) {
1969 if ($name[0] !==
'@') {
1973 $parser->buffer = (string) $strValue;
1974 if (!$parser->propertyValue($value)) {
1975 throw new Exception(
"failed to parse passed in variable $name: $strValue");
1978 $this->set($name, $value);
1987 if ($fname !== null) {
1989 $this->_parseFile = $fname;
1993 public function compile($string, $name = null) {
1994 $locale = setlocale(LC_NUMERIC, 0);
1995 setlocale(LC_NUMERIC,
"C");
1997 $this->parser = $this->makeParser($name);
1998 $root = $this->parser->parse($string);
2001 $this->scope = null;
2003 $this->formatter = $this->newFormatter();
2005 if (!empty($this->registeredVars)) {
2006 $this->injectVariables($this->registeredVars);
2009 $this->sourceParser = $this->parser;
2013 $this->formatter->block($this->scope);
2014 $out = ob_get_clean();
2015 setlocale(LC_NUMERIC, $locale);
2019 public function compileFile($fname, $outFname = null) {
2020 if (!is_readable($fname)) {
2021 throw new Exception(
'load error: failed to find '.$fname);
2024 $pi = pathinfo($fname);
2026 $oldImport = $this->importDir;
2028 $this->importDir = (array) $this->importDir;
2029 $this->importDir[] = $pi[
'dirname'].
'/';
2031 $this->addParsedFile($fname);
2033 $out = $this->compile(file_get_contents($fname), $fname);
2035 $this->importDir = $oldImport;
2037 if ($outFname !== null) {
2038 return file_put_contents($outFname, $out);
2045 public function checkedCompile($in, $out) {
2046 if (!is_file($out) || filemtime($in) > filemtime($out)) {
2047 $this->compileFile($in, $out);
2077 if (is_string($in)) {
2079 } elseif (is_array($in) && isset($in[
'root'])) {
2080 if ($force || !isset($in[
'files'])) {
2084 $root = $in[
'root'];
2085 } elseif (isset($in[
'files']) && is_array($in[
'files'])) {
2086 foreach ($in[
'files'] as $fname => $ftime) {
2087 if (!file_exists($fname) || filemtime($fname) > $ftime) {
2090 $root = $in[
'root'];
2101 if ($root !== null) {
2104 $out[
'root'] = $root;
2105 $out[
'compiled'] = $this->compileFile($root);
2106 $out[
'files'] = $this->allParsedFiles();
2107 $out[
'updated'] = time();
2119 public function parse($str = null, $initialVariables = null) {
2120 if (is_array($str)) {
2121 $initialVariables = $str;
2125 $oldVars = $this->registeredVars;
2126 if ($initialVariables !== null) {
2127 $this->setVariables($initialVariables);
2131 if (empty($this->_parseFile)) {
2132 throw new exception(
"nothing to parse");
2135 $out = $this->compileFile($this->_parseFile);
2137 $out = $this->compile($str);
2140 $this->registeredVars = $oldVars;
2144 protected function makeParser($name) {
2146 $parser->writeComments = $this->preserveComments;
2151 public function setFormatter($name) {
2152 $this->formatterName = $name;
2155 protected function newFormatter() {
2156 $className =
"lessc_formatter_lessjs";
2157 if (!empty($this->formatterName)) {
2158 if (!is_string($this->formatterName))
2159 return $this->formatterName;
2160 $className =
"lessc_formatter_$this->formatterName";
2163 return new $className;
2166 public function setPreserveComments($preserve) {
2167 $this->preserveComments = $preserve;
2170 public function registerFunction($name, $func) {
2171 $this->libFunctions[$name] = $func;
2174 public function unregisterFunction($name) {
2175 unset($this->libFunctions[$name]);
2178 public function setVariables($variables) {
2179 $this->registeredVars = array_merge($this->registeredVars, $variables);
2182 public function unsetVariable($name) {
2183 unset($this->registeredVars[$name]);
2186 public function setImportDir($dirs) {
2187 $this->importDir = (array) $dirs;
2190 public function addImportDir($dir) {
2191 $this->importDir = (array) $this->importDir;
2192 $this->importDir[] = $dir;
2195 public function allParsedFiles() {
2196 return $this->allParsedFiles;
2199 public function addParsedFile($file) {
2200 $this->allParsedFiles[realpath($file)] = filemtime($file);
2207 if ($this->sourceLoc >= 0) {
2208 $this->sourceParser->throwError($msg, $this->sourceLoc);
2210 throw new exception($msg);
2215 public static function ccompile($in, $out, $less = null) {
2216 if ($less === null) {
2219 return $less->checkedCompile($in, $out);
2222 public static function cexecute($in, $force =
false, $less = null) {
2223 if ($less === null) {
2226 return $less->cachedCompile($in, $force);
2229 protected static $cssColors = array(
2230 'aliceblue' =>
'240,248,255',
2231 'antiquewhite' =>
'250,235,215',
2232 'aqua' =>
'0,255,255',
2233 'aquamarine' =>
'127,255,212',
2234 'azure' =>
'240,255,255',
2235 'beige' =>
'245,245,220',
2236 'bisque' =>
'255,228,196',
2238 'blanchedalmond' =>
'255,235,205',
2239 'blue' =>
'0,0,255',
2240 'blueviolet' =>
'138,43,226',
2241 'brown' =>
'165,42,42',
2242 'burlywood' =>
'222,184,135',
2243 'cadetblue' =>
'95,158,160',
2244 'chartreuse' =>
'127,255,0',
2245 'chocolate' =>
'210,105,30',
2246 'coral' =>
'255,127,80',
2247 'cornflowerblue' =>
'100,149,237',
2248 'cornsilk' =>
'255,248,220',
2249 'crimson' =>
'220,20,60',
2250 'cyan' =>
'0,255,255',
2251 'darkblue' =>
'0,0,139',
2252 'darkcyan' =>
'0,139,139',
2253 'darkgoldenrod' =>
'184,134,11',
2254 'darkgray' =>
'169,169,169',
2255 'darkgreen' =>
'0,100,0',
2256 'darkgrey' =>
'169,169,169',
2257 'darkkhaki' =>
'189,183,107',
2258 'darkmagenta' =>
'139,0,139',
2259 'darkolivegreen' =>
'85,107,47',
2260 'darkorange' =>
'255,140,0',
2261 'darkorchid' =>
'153,50,204',
2262 'darkred' =>
'139,0,0',
2263 'darksalmon' =>
'233,150,122',
2264 'darkseagreen' =>
'143,188,143',
2265 'darkslateblue' =>
'72,61,139',
2266 'darkslategray' =>
'47,79,79',
2267 'darkslategrey' =>
'47,79,79',
2268 'darkturquoise' =>
'0,206,209',
2269 'darkviolet' =>
'148,0,211',
2270 'deeppink' =>
'255,20,147',
2271 'deepskyblue' =>
'0,191,255',
2272 'dimgray' =>
'105,105,105',
2273 'dimgrey' =>
'105,105,105',
2274 'dodgerblue' =>
'30,144,255',
2275 'firebrick' =>
'178,34,34',
2276 'floralwhite' =>
'255,250,240',
2277 'forestgreen' =>
'34,139,34',
2278 'fuchsia' =>
'255,0,255',
2279 'gainsboro' =>
'220,220,220',
2280 'ghostwhite' =>
'248,248,255',
2281 'gold' =>
'255,215,0',
2282 'goldenrod' =>
'218,165,32',
2283 'gray' =>
'128,128,128',
2284 'green' =>
'0,128,0',
2285 'greenyellow' =>
'173,255,47',
2286 'grey' =>
'128,128,128',
2287 'honeydew' =>
'240,255,240',
2288 'hotpink' =>
'255,105,180',
2289 'indianred' =>
'205,92,92',
2290 'indigo' =>
'75,0,130',
2291 'ivory' =>
'255,255,240',
2292 'khaki' =>
'240,230,140',
2293 'lavender' =>
'230,230,250',
2294 'lavenderblush' =>
'255,240,245',
2295 'lawngreen' =>
'124,252,0',
2296 'lemonchiffon' =>
'255,250,205',
2297 'lightblue' =>
'173,216,230',
2298 'lightcoral' =>
'240,128,128',
2299 'lightcyan' =>
'224,255,255',
2300 'lightgoldenrodyellow' =>
'250,250,210',
2301 'lightgray' =>
'211,211,211',
2302 'lightgreen' =>
'144,238,144',
2303 'lightgrey' =>
'211,211,211',
2304 'lightpink' =>
'255,182,193',
2305 'lightsalmon' =>
'255,160,122',
2306 'lightseagreen' =>
'32,178,170',
2307 'lightskyblue' =>
'135,206,250',
2308 'lightslategray' =>
'119,136,153',
2309 'lightslategrey' =>
'119,136,153',
2310 'lightsteelblue' =>
'176,196,222',
2311 'lightyellow' =>
'255,255,224',
2312 'lime' =>
'0,255,0',
2313 'limegreen' =>
'50,205,50',
2314 'linen' =>
'250,240,230',
2315 'magenta' =>
'255,0,255',
2316 'maroon' =>
'128,0,0',
2317 'mediumaquamarine' =>
'102,205,170',
2318 'mediumblue' =>
'0,0,205',
2319 'mediumorchid' =>
'186,85,211',
2320 'mediumpurple' =>
'147,112,219',
2321 'mediumseagreen' =>
'60,179,113',
2322 'mediumslateblue' =>
'123,104,238',
2323 'mediumspringgreen' =>
'0,250,154',
2324 'mediumturquoise' =>
'72,209,204',
2325 'mediumvioletred' =>
'199,21,133',
2326 'midnightblue' =>
'25,25,112',
2327 'mintcream' =>
'245,255,250',
2328 'mistyrose' =>
'255,228,225',
2329 'moccasin' =>
'255,228,181',
2330 'navajowhite' =>
'255,222,173',
2331 'navy' =>
'0,0,128',
2332 'oldlace' =>
'253,245,230',
2333 'olive' =>
'128,128,0',
2334 'olivedrab' =>
'107,142,35',
2335 'orange' =>
'255,165,0',
2336 'orangered' =>
'255,69,0',
2337 'orchid' =>
'218,112,214',
2338 'palegoldenrod' =>
'238,232,170',
2339 'palegreen' =>
'152,251,152',
2340 'paleturquoise' =>
'175,238,238',
2341 'palevioletred' =>
'219,112,147',
2342 'papayawhip' =>
'255,239,213',
2343 'peachpuff' =>
'255,218,185',
2344 'peru' =>
'205,133,63',
2345 'pink' =>
'255,192,203',
2346 'plum' =>
'221,160,221',
2347 'powderblue' =>
'176,224,230',
2348 'purple' =>
'128,0,128',
2350 'rosybrown' =>
'188,143,143',
2351 'royalblue' =>
'65,105,225',
2352 'saddlebrown' =>
'139,69,19',
2353 'salmon' =>
'250,128,114',
2354 'sandybrown' =>
'244,164,96',
2355 'seagreen' =>
'46,139,87',
2356 'seashell' =>
'255,245,238',
2357 'sienna' =>
'160,82,45',
2358 'silver' =>
'192,192,192',
2359 'skyblue' =>
'135,206,235',
2360 'slateblue' =>
'106,90,205',
2361 'slategray' =>
'112,128,144',
2362 'slategrey' =>
'112,128,144',
2363 'snow' =>
'255,250,250',
2364 'springgreen' =>
'0,255,127',
2365 'steelblue' =>
'70,130,180',
2366 'tan' =>
'210,180,140',
2367 'teal' =>
'0,128,128',
2368 'thistle' =>
'216,191,216',
2369 'tomato' =>
'255,99,71',
2370 'transparent' =>
'0,0,0,0',
2371 'turquoise' =>
'64,224,208',
2372 'violet' =>
'238,130,238',
2373 'wheat' =>
'245,222,179',
2374 'white' =>
'255,255,255',
2375 'whitesmoke' =>
'245,245,245',
2376 'yellow' =>
'255,255,0',
2377 'yellowgreen' =>
'154,205,50'
2384 protected static $nextBlockId = 0;
2386 protected static $precedence = array(
2400 protected static $whitePattern;
2401 protected static $commentMulti;
2403 protected static $commentSingle =
"//";
2404 protected static $commentMultiLeft =
"/*";
2405 protected static $commentMultiRight =
"*/";
2408 protected static $operatorString;
2411 protected static $supressDivisionProps =
2412 array(
'/border-radius$/i',
'/^font$/i');
2414 protected $blockDirectives = array(
"font-face",
"keyframes",
"page",
"-moz-document",
"viewport",
"-moz-viewport",
"-o-viewport",
"-ms-viewport");
2415 protected $lineDirectives = array(
"charset");
2429 protected static $literalCache = array();
2431 public function __construct($lessc, $sourceName = null) {
2432 $this->eatWhiteDefault =
true;
2434 $this->lessc = $lessc;
2436 $this->sourceName = $sourceName;
2438 $this->writeComments =
false;
2440 if (!self::$operatorString) {
2441 self::$operatorString =
2442 '('.implode(
'|', array_map(array(
'lessc',
'preg_quote'),
2443 array_keys(self::$precedence))).
')';
2445 $commentSingle = lessc::preg_quote(self::$commentSingle);
2446 $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
2447 $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
2449 self::$commentMulti = $commentMultiLeft.
'.*?'.$commentMultiRight;
2450 self::$whitePattern =
'/'.$commentSingle.
'[^\n]*\s*|('.self::$commentMulti.
')\s*|\s+/Ais';
2466 $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
2467 $this->pushSpecialBlock(
"root");
2468 $this->eatWhiteDefault =
true;
2469 $this->seenComments = array();
2476 $this->whitespace();
2481 if ($this->count != strlen($this->buffer))
2483 $this->throwError(
'parse error count '.$this->count.
' != len buffer '.strlen($this->buffer));
2487 if (!property_exists($this->env,
'parent') || !is_null($this->env->parent))
2489 throw new exception(
'parse error: unclosed block');
2532 if (empty($this->buffer))
return false;
2535 if ($this->whitespace()) {
2540 if ($this->keyword($key) && $this->
assign() &&
2541 $this->propertyValue($value, $key) && $this->end()
2543 $this->append(array(
'assign', $key, $value), $s);
2551 if ($this->literal(
'@',
false)) {
2555 if ($this->literal(
'@media')) {
2556 if (($this->mediaQueryList($mediaQueries) ||
true)
2557 && $this->literal(
'{')
2559 $media = $this->pushSpecialBlock(
"media");
2560 $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
2568 if ($this->literal(
"@",
false) && $this->keyword($dirName)) {
2569 if ($this->isDirective($dirName, $this->blockDirectives)) {
2570 if (($this->openString(
"{", $dirValue, null, array(
";")) ||
true) &&
2573 $dir = $this->pushSpecialBlock(
"directive");
2574 $dir->name = $dirName;
2575 if (isset($dirValue)) $dir->value = $dirValue;
2578 } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
2579 if ($this->propertyValue($dirValue) && $this->end()) {
2580 $this->append(array(
"directive", $dirName, $dirValue));
2590 if ($this->variable($var) && $this->
assign() &&
2591 $this->propertyValue($value) && $this->end()
2593 $this->append(array(
'assign', $var, $value), $s);
2599 if ($this->
import($importValue)) {
2600 $this->append($importValue, $s);
2605 if ($this->tag($tag,
true) && $this->argumentDef($args, $isVararg) &&
2606 ($this->guards($guards) ||
true) &&
2609 $block = $this->pushBlock($this->fixTags(array($tag)));
2610 $block->args = $args;
2611 $block->isVararg = $isVararg;
2612 if (!empty($guards)) $block->guards = $guards;
2619 if ($this->tags($tags) && $this->literal(
'{',
false)) {
2620 $tags = $this->fixTags($tags);
2621 $this->pushBlock($tags);
2628 if ($this->literal(
'}',
false)) {
2630 $block = $this->pop();
2631 }
catch (exception $e) {
2633 $this->throwError($e->getMessage());
2637 if (is_null($block->type)) {
2639 if (!isset($block->args)) {
2640 foreach ($block->tags as $tag) {
2641 if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) {
2648 foreach ($block->tags as $tag) {
2649 if (is_string($tag)) {
2650 $this->env->children[$tag][] = $block;
2656 $this->append(array(
'block', $block), $s);
2661 $this->whitespace();
2666 if ($this->mixinTags($tags) &&
2667 ($this->argumentDef($argv, $isVararg) ||
true) &&
2668 ($this->keyword($suffix) ||
true) && $this->end()
2670 $tags = $this->fixTags($tags);
2671 $this->append(array(
'mixin', $tags, $argv, $suffix), $s);
2678 if ($this->literal(
';'))
return true;
2683 protected function isDirective($dirname, $directives) {
2685 $pattern = implode(
"|",
2686 array_map(array(
"lessc",
"preg_quote"), $directives));
2687 $pattern =
'/^(-[a-z-]+-)?('.$pattern.
')$/i';
2689 return preg_match($pattern, $dirname);
2692 protected function fixTags($tags) {
2694 foreach ($tags as &$tag) {
2695 if ($tag[0] == $this->lessc->vPrefix)
2696 $tag[0] = $this->lessc->mPrefix;
2702 protected function expressionList(&$exps) {
2709 if (count($values) == 0)
return false;
2711 $exps = lessc::compressList($values,
' ');
2720 if ($this->value($lhs)) {
2724 if (!empty($this->env->supressedDivision)) {
2725 unset($this->env->supressedDivision);
2727 if ($this->literal(
"/") && $this->value($rhs)) {
2728 $out = array(
"list",
"",
2729 array($out, array(
"keyword",
"/"), $rhs));
2744 $this->inExp =
true;
2745 $ss = $this->seek();
2748 $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2749 ctype_space($this->buffer[$this->count - 1]);
2755 if ($this->match(self::$operatorString.($needWhite ?
'\s' :
''), $m) && self::$precedence[$m[1]] >= $minP) {
2756 if (!$this->inParens && isset($this->env->currentProperty) && $m[1] ==
"/" && empty($this->env->supressedDivision)) {
2757 foreach (self::$supressDivisionProps as $pattern) {
2758 if (preg_match($pattern, $this->env->currentProperty)) {
2759 $this->env->supressedDivision =
true;
2766 $whiteAfter = isset($this->buffer[$this->count - 1]) &&
2767 ctype_space($this->buffer[$this->count - 1]);
2769 if (!$this->value($rhs))
break;
2772 if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
2773 $rhs = $this->
expHelper($rhs, self::$precedence[$next[1]]);
2776 $lhs = array(
'expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
2777 $ss = $this->seek();
2791 public function propertyValue(&$value, $keyName = null) {
2794 if ($keyName !== null) $this->env->currentProperty = $keyName;
2797 while ($this->expressionList($v)) {
2800 if (!$this->literal(
','))
break;
2803 if ($s) $this->seek($s);
2805 if ($keyName !== null) unset($this->env->currentProperty);
2807 if (count($values) == 0)
return false;
2809 $value = lessc::compressList($values,
', ');
2813 protected function parenValue(&$out) {
2817 if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] !=
"(") {
2822 if ($this->literal(
"(") &&
2823 ($this->inParens =
true) && $this->
expression($exp) &&
2838 protected function value(&$value) {
2842 if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] ==
"-") {
2844 if ($this->literal(
"-",
false) &&
2845 (($this->variable($inner) && $inner = array(
"variable", $inner)) ||
2846 $this->unit($inner) ||
2847 $this->parenValue($inner))
2849 $value = array(
"unary",
"-", $inner);
2856 if ($this->parenValue($value))
return true;
2857 if ($this->unit($value))
return true;
2858 if ($this->color($value))
return true;
2859 if ($this->func($value))
return true;
2860 if ($this->
string($value))
return true;
2862 if ($this->keyword($word)) {
2863 $value = array(
'keyword', $word);
2868 if ($this->variable($var)) {
2869 $value = array(
'variable', $var);
2874 if ($this->literal(
"~") && $this->
string($str)) {
2875 $value = array(
"escape", $str);
2882 if ($this->literal(
'\\') && $this->match(
'([0-9]+)', $m)) {
2883 $value = array(
'keyword',
'\\'.$m[1]);
2893 protected function import(&$out) {
2894 if (!$this->literal(
'@import'))
return false;
2900 if ($this->propertyValue($value)) {
2901 $out = array(
"import", $value);
2906 protected function mediaQueryList(&$out) {
2907 if ($this->genericList($list,
"mediaQuery",
",",
false)) {
2914 protected function mediaQuery(&$out) {
2917 $expressions = null;
2920 if (($this->literal(
"only") && ($only =
true) || $this->literal(
"not") && ($not =
true) ||
true) && $this->keyword($mediaType)) {
2921 $prop = array(
"mediaType");
2922 if (isset($only)) $prop[] =
"only";
2923 if (isset($not)) $prop[] =
"not";
2924 $prop[] = $mediaType;
2931 if (!empty($mediaType) && !$this->literal(
"and")) {
2934 $this->genericList($expressions,
"mediaExpression",
"and",
false);
2935 if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
2938 if (count($parts) == 0) {
2947 protected function mediaExpression(&$out) {
2950 if ($this->literal(
"(") &&
2951 $this->keyword($feature) &&
2952 ($this->literal(
":") && $this->
expression($value) ||
true) &&
2955 $out = array(
"mediaExp", $feature);
2956 if ($value) $out[] = $value;
2958 } elseif ($this->variable($variable)) {
2959 $out = array(
'variable', $variable);
2968 protected function openString($end, &$out, $nestingOpen = null, $rejectStrs = null) {
2969 $oldWhite = $this->eatWhiteDefault;
2970 $this->eatWhiteDefault =
false;
2972 $stop = array(
"'",
'"',
"@{", $end);
2973 $stop = array_map(array(
"lessc",
"preg_quote"), $stop);
2976 if (!is_null($rejectStrs)) {
2977 $stop = array_merge($stop, $rejectStrs);
2980 $patt =
'(.*?)('.implode(
"|", $stop).
')';
2985 while ($this->match($patt, $m,
false)) {
2986 if (!empty($m[1])) {
2989 $nestingLevel += substr_count($m[1], $nestingOpen);
2995 $this->count -= strlen($tok);
2997 if ($nestingLevel == 0) {
3004 if (($tok ==
"'" || $tok ==
'"') && $this->
string($str)) {
3009 if ($tok ==
"@{" && $this->interpolation($inter)) {
3010 $content[] = $inter;
3014 if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
3019 $this->count += strlen($tok);
3022 $this->eatWhiteDefault = $oldWhite;
3024 if (count($content) == 0)
return false;
3027 if (is_string(end($content))) {
3028 $content[count($content) - 1] = rtrim(end($content));
3031 $out = array(
"string",
"", $content);
3035 protected function string(&$out) {
3037 if ($this->literal(
'"',
false)) {
3039 } elseif ($this->literal(
"'",
false)) {
3048 $patt =
'([^\n]*?)(@\{|\\\\|'.
3049 lessc::preg_quote($delim).
')';
3051 $oldWhite = $this->eatWhiteDefault;
3052 $this->eatWhiteDefault =
false;
3054 while ($this->match($patt, $m,
false)) {
3056 if ($m[2] ==
"@{") {
3057 $this->count -= strlen($m[2]);
3058 if ($this->interpolation($inter)) {
3059 $content[] = $inter;
3061 $this->count += strlen($m[2]);
3064 } elseif ($m[2] ==
'\\') {
3066 if ($this->literal($delim,
false)) {
3067 $content[] = $delim;
3070 $this->count -= strlen($delim);
3075 $this->eatWhiteDefault = $oldWhite;
3077 if ($this->literal($delim)) {
3078 $out = array(
"string", $delim, $content);
3086 protected function interpolation(&$out) {
3087 $oldWhite = $this->eatWhiteDefault;
3088 $this->eatWhiteDefault =
true;
3091 if ($this->literal(
"@{") &&
3092 $this->openString(
"}", $interp, null, array(
"'",
'"',
";")) &&
3093 $this->literal(
"}",
false)
3095 $out = array(
"interpolate", $interp);
3096 $this->eatWhiteDefault = $oldWhite;
3097 if ($this->eatWhiteDefault) $this->whitespace();
3101 $this->eatWhiteDefault = $oldWhite;
3106 protected function unit(&$unit) {
3108 if (isset($this->buffer[$this->count])) {
3109 $char = $this->buffer[$this->count];
3110 if (!ctype_digit($char) && $char !=
".")
return false;
3113 if ($this->match(
'([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
3114 $unit = array(
"number", $m[1], empty($m[2]) ?
"" : $m[2]);
3121 protected function color(&$out) {
3122 if ($this->match(
'(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
3123 if (strlen($m[1]) > 7) {
3124 $out = array(
"string",
"", array($m[1]));
3126 $out = array(
"raw_color", $m[1]);
3139 protected function argumentDef(&$args, &$isVararg) {
3141 if (!$this->literal(
'(')) {
3147 $method =
"expressionList";
3151 if ($this->literal(
"...")) {
3156 if ($this->$method($value)) {
3157 if ($value[0] ==
"variable") {
3158 $arg = array(
"arg", $value[1]);
3159 $ss = $this->seek();
3161 if ($this->
assign() && $this->$method($rhs)) {
3165 if ($this->literal(
"...")) {
3177 $values[] = array(
"lit", $value);
3182 if (!$this->literal($delim)) {
3183 if ($delim ==
"," && $this->literal(
";")) {
3186 $method =
"propertyValue";
3189 if (isset($values[1])) {
3191 foreach ($values as $i => $arg) {
3195 $this->throwError(
"Cannot mix ; and , as delimiter types");
3197 $newList[] = $arg[2];
3200 $newList[] = $arg[1];
3203 $this->throwError(
"Unexpected rest before semicolon");
3207 $newList = array(
"list",
", ", $newList);
3209 switch ($values[0][0]) {
3211 $newArg = array(
"arg", $values[0][1], $newList);
3214 $newArg = array(
"lit", $newList);
3218 } elseif ($values) {
3219 $newArg = $values[0];
3223 $values = array($newArg);
3231 if (!$this->literal(
')')) {
3243 protected function tags(&$tags, $simple =
false, $delim =
',') {
3245 while ($this->tag($tt, $simple)) {
3247 if (!$this->literal($delim))
break;
3249 if (count($tags) == 0)
return false;
3256 protected function mixinTags(&$tags) {
3258 while ($this->tag($tt,
true)) {
3260 $this->literal(
">");
3271 protected function tagBracket(&$parts, &$hasExpression) {
3273 if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] !=
"[") {
3279 $hasInterpolation =
false;
3281 if ($this->literal(
"[",
false)) {
3282 $attrParts = array(
"[");
3285 if ($this->literal(
"]",
false)) {
3290 if ($this->match(
'\s+', $m)) {
3294 if ($this->
string($str)) {
3296 foreach ($str[2] as &$chunk) {
3297 $chunk = str_replace($this->lessc->parentSelector,
"$&$", $chunk);
3300 $attrParts[] = $str;
3301 $hasInterpolation =
true;
3305 if ($this->keyword($word)) {
3306 $attrParts[] = $word;
3310 if ($this->interpolation($inter)) {
3311 $attrParts[] = $inter;
3312 $hasInterpolation =
true;
3317 if ($this->match(
'[|-~\$\*\^=]+', $m)) {
3318 $attrParts[] = $m[0];
3325 if ($this->literal(
"]",
false)) {
3327 foreach ($attrParts as $part) {
3330 $hasExpression = $hasExpression || $hasInterpolation;
3341 protected function tag(&$tag, $simple =
false) {
3343 $chars =
'^@,:;{}\][>\(\) "\'';
3345 $chars =
'^@,;{}["\'';
3349 $hasExpression =
false;
3351 while ($this->tagBracket($parts, $hasExpression));
3353 $oldWhite = $this->eatWhiteDefault;
3354 $this->eatWhiteDefault =
false;
3357 if ($this->match(
'(['.$chars.
'0-9]['.$chars.
']*)', $m)) {
3361 while ($this->tagBracket($parts, $hasExpression));
3365 if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] ==
"@") {
3366 if ($this->interpolation($interp)) {
3367 $hasExpression =
true;
3373 if ($this->literal(
"@")) {
3379 if ($this->unit($unit)) {
3380 $parts[] = $unit[1];
3381 $parts[] = $unit[2];
3388 $this->eatWhiteDefault = $oldWhite;
3394 if ($hasExpression) {
3395 $tag = array(
"exp", array(
"string",
"", $parts));
3397 $tag = trim(implode($parts));
3400 $this->whitespace();
3405 protected function func(&$func) {
3408 if ($this->match(
'(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal(
'(')) {
3411 $sPreArgs = $this->seek();
3415 $ss = $this->seek();
3417 if ($this->keyword($name) && $this->literal(
'=') && $this->expressionList($value)) {
3418 $args[] = array(
"string",
"", array($name,
"=", $value));
3421 if ($this->expressionList($value)) {
3426 if (!$this->literal(
','))
break;
3428 $args = array(
'list',
',', $args);
3430 if ($this->literal(
')')) {
3431 $func = array(
'function', $fname, $args);
3433 } elseif ($fname ==
'url') {
3435 $this->seek($sPreArgs);
3436 if ($this->openString(
")", $string) && $this->literal(
")")) {
3437 $func = array(
'function', $fname, $string);
3448 protected function variable(&$name) {
3450 if ($this->literal($this->lessc->vPrefix,
false) &&
3451 ($this->variable($sub) || $this->keyword($name))
3454 $name = array(
'variable', $sub);
3456 $name = $this->lessc->vPrefix.$name;
3471 if ($name) $this->currentProperty = $name;
3472 return $this->literal(
':') || $this->literal(
'=');
3476 protected function keyword(&$word) {
3477 if ($this->match(
'([\w_\-\*!"][\w\-_"]*)', $m)) {
3485 protected function end() {
3486 if ($this->literal(
';',
false)) {
3488 } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] ==
'}') {
3495 protected function guards(&$guards) {
3498 if (!$this->literal(
"when")) {
3505 while ($this->guardGroup($g)) {
3507 if (!$this->literal(
","))
break;
3510 if (count($guards) == 0) {
3521 protected function guardGroup(&$guardGroup) {
3523 $guardGroup = array();
3524 while ($this->guard($guard)) {
3525 $guardGroup[] = $guard;
3526 if (!$this->literal(
"and"))
break;
3529 if (count($guardGroup) == 0) {
3538 protected function guard(&$guard) {
3540 $negate = $this->literal(
"not");
3542 if ($this->literal(
"(") && $this->
expression($exp) && $this->literal(
")")) {
3544 if ($negate) $guard = array(
"negate", $guard);
3554 protected function literal($what, $eatWhitespace = null) {
3555 if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3558 if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3559 if ($this->buffer[$this->count] == $what) {
3560 if (!$eatWhitespace) {
3570 if (!isset(self::$literalCache[$what])) {
3571 self::$literalCache[$what] = lessc::preg_quote($what);
3574 return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
3577 protected function genericList(&$out, $parseItem, $delim =
"", $flatten =
true) {
3580 while ($this->$parseItem($value)) {
3583 if (!$this->literal($delim))
break;
3587 if (count($items) == 0) {
3592 if ($flatten && count($items) == 1) {
3595 $out = array(
"list", $delim, $items);
3605 protected function to($what, &$out, $until =
false, $allowNewline =
false) {
3606 if (is_string($allowNewline)) {
3607 $validChars = $allowNewline;
3609 $validChars = $allowNewline ?
"." :
"[^\n]";
3611 if (!$this->match(
'('.$validChars.
'*?)'.lessc::preg_quote($what), $m, !$until))
return false;
3612 if ($until) $this->count -= strlen($what);
3618 protected function match($regex, &$out, $eatWhitespace = null) {
3619 if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3621 $r =
'/'.$regex.($eatWhitespace && !$this->writeComments ?
'\s*' :
'').
'/Ais';
3622 if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3623 $this->count += strlen($out[0]);
3624 if ($eatWhitespace && $this->writeComments) $this->whitespace();
3631 protected function whitespace() {
3632 if ($this->writeComments) {
3634 while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3635 if (isset($m[1]) && empty($this->seenComments[$this->count])) {
3636 $this->append(array(
"comment", $m[1]));
3637 $this->seenComments[$this->count] =
true;
3639 $this->count += strlen($m[0]);
3644 $this->match(
"", $m);
3645 return strlen($m[0]) > 0;
3650 protected function peek($regex, &$out = null, $from = null) {
3651 if (is_null($from)) $from = $this->count;
3652 $r =
'/'.$regex.
'/Ais';
3653 $result = preg_match($r, $this->buffer, $out, null, $from);
3659 protected function seek($where = null) {
3660 if ($where === null)
return $this->count;
3661 else $this->count = $where;
3667 public function throwError($msg =
"parse error", $count = null) {
3668 $count = is_null($count) ? $this->count : $count;
3670 $line = $this->line +
3671 substr_count(substr($this->buffer, 0, $count),
"\n");
3673 if (!empty($this->sourceName)) {
3674 $loc =
"$this->sourceName on line $line";
3676 $loc =
"line: $line";
3680 if ($this->peek(
"(.*?)(\n|$)", $m, $count)) {
3681 throw new exception(
"$msg: failed at `$m[1]` $loc");
3683 throw new exception(
"$msg: $loc");
3687 protected function pushBlock($selectors = null, $type = null) {
3689 $b->parent = $this->env;
3692 $b->id = self::$nextBlockId++;
3694 $b->isVararg =
false;
3695 $b->tags = $selectors;
3697 $b->props = array();
3698 $b->children = array();
3705 protected function pushSpecialBlock($type) {
3706 return $this->pushBlock(null, $type);
3710 protected function append($prop, $pos = null) {
3711 if ($pos !== null) $prop[-1] = $pos;
3712 $this->env->props[] = $prop;
3716 protected function pop() {
3718 $this->env = $this->env->parent;
3724 protected function removeComments($text) {
3726 'url(',
'//',
'/*',
'"',
"'"
3733 foreach ($look as $token) {
3734 $pos = strpos($text, $token);
3735 if ($pos !==
false) {
3736 if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
3740 if (is_null($min))
break;
3747 if (preg_match(
'/url\(.*?\)/', $text, $m, 0, $count))
3748 $count += strlen($m[0]) - strlen($min[0]);
3752 if (preg_match(
'/'.$min[0].
'.*?(?<!\\\\)'.$min[0].
'/', $text, $m, 0, $count))
3753 $count += strlen($m[0]) - 1;
3756 $skip = strpos($text,
"\n", $count);
3757 if ($skip ===
false) $skip = strlen($text) - $count;
3758 else $skip -= $count;
3761 if (preg_match(
'/\/\*.*?\*\//s', $text, $m, 0, $count)) {
3762 $skip = strlen($m[0]);
3763 $newlines = substr_count($m[0],
"\n");
3768 if ($skip == 0) $count += strlen($min[0]);
3770 $out .= substr($text, 0, $count).str_repeat(
"\n", $newlines);
3771 $text = substr($text, $count + $skip);
3782 public $indentChar =
" ";
3784 public $break =
"\n";
3785 public $open =
" {";
3786 public $close =
"}";
3787 public $selectorSeparator =
", ";
3788 public $assignSeparator =
":";
3790 public $openSingle =
" { ";
3791 public $closeSingle =
" }";
3793 public $disableSingle =
false;
3794 public $breakSelectors =
false;
3796 public $compressColors =
false;
3798 public function __construct() {
3799 $this->indentLevel = 0;
3802 public function indentStr($n = 0) {
3803 return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
3806 public function property($name, $value) {
3807 return $name.$this->assignSeparator.$value.
";";
3810 protected function isEmpty($block) {
3811 if (empty($block->lines)) {
3812 foreach ($block->children as $child) {
3813 if (!$this->isEmpty($child))
return false;
3821 public function block($block) {
3822 if ($this->isEmpty($block))
return;
3824 $inner = $pre = $this->indentStr();
3826 $isSingle = !$this->disableSingle &&
3827 is_null($block->type) && count($block->lines) == 1;
3829 if (!empty($block->selectors)) {
3830 $this->indentLevel++;
3832 if ($this->breakSelectors) {
3833 $selectorSeparator = $this->selectorSeparator.$this->break.$pre;
3835 $selectorSeparator = $this->selectorSeparator;
3839 implode($selectorSeparator, $block->selectors);
3841 echo $this->openSingle;
3844 echo $this->open.$this->break;
3845 $inner = $this->indentStr();
3850 if (!empty($block->lines)) {
3851 $glue = $this->
break.$inner;
3852 echo $inner.implode($glue, $block->lines);
3853 if (!$isSingle && !empty($block->children)) {
3858 foreach ($block->children as $child) {
3859 $this->block($child);
3862 if (!empty($block->selectors)) {
3863 if (!$isSingle && empty($block->children)) echo $this->break;
3866 echo $this->closeSingle.$this->break;
3868 echo $pre.$this->close.$this->break;
3871 $this->indentLevel--;
3880 public $disableSingle =
true;
3882 public $selectorSeparator =
",";
3883 public $assignSeparator =
":";
3885 public $compressColors =
true;
3887 public function indentStr($n = 0) {
3896 public $disableSingle =
true;
3897 public $breakSelectors =
true;
3898 public $assignSeparator =
": ";
3899 public $selectorSeparator =
",";
throwError($msg=null)
Uses the current value of $this->count to show line and line number.
deduplicate($lines)
Deduplicate lines in a block.
$inParens
if we are in parens we can be more liberal with whitespace around operators because it must evaluate ...
funcToColor($func)
Convert the rgb, rgba, hsl color literals of function type as returned by the parser into values of c...
toRGB($color)
Converts a hsl array into a color value in rgb.
expHelper($lhs, $minP)
recursively parse infix equation with $lhs at precedence $minP
cachedCompile($in, $force=false)
Execute lessphp on a .less file or a lessphp cache structure.
__construct($fname=null)
Initialize any static state, can initialize parser for a file $opts isn't used yet.
colorArgs($args)
Helper function to get arguments for color manipulation functions.
expression(&$out)
Attempt to consume an expression.
assign($name=null)
Consume an assignment operator Can optionally take a name that will be set to the current property na...
compileValue($value)
Compiles a primitive value into a CSS property value.
parse($buffer)
Parse a string.
lessphp v0.5.0 http://leafo.net/lessphp
lib_tint($args)
Mix color with white in variable proportion.
compileBlock($block)
Recursively compiles a block.
fileExists($name)
fileExists
lib_data_uri($value)
Given an url, decide whether to output a regular link or the base64-encoded contents of the file...
parseChunk()
Parse a single chunk off the head of the buffer and append it to the current parse environment...
lib_shade($args)
Mix color with black in variable proportion.