Blame | Last modification | View Log | RSS feed
<?php/*** lessphp v0.4.0* http://leafo.net/lessphp** LESS css compiler, adapted from http://lesscss.org** Copyright 2012, Leaf Corcoran <leafot@gmail.com>* Licensed under MIT or GPLv3, see LICENSE*//*** The less compiler and parser.** Converting LESS to CSS is a three stage process. The incoming file is parsed* by `lessc_parser` into a syntax tree, then it is compiled into another tree* representing the CSS structure by `lessc`. The CSS tree is fed into a* formatter, like `lessc_formatter` which then outputs CSS as a string.** During the first compile, all values are *reduced*, which means that their* types are brought to the lowest form before being dump as strings. This* handles math equations, variable dereferences, and the like.** The `parse` function of `lessc` is the entry point.** In summary:** The `lessc` class creates an intstance of the parser, feeds it LESS code,* then transforms the resulting tree to a CSS tree. This class also holds the* evaluation context, such as all available mixins and variables at any given* time.** The `lessc_parser` class is only concerned with parsing its input.** The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,* handling things like indentation.*/class lessc {static public $VERSION = "v0.4.0";static protected $TRUE = array("keyword", "true");static protected $FALSE = array("keyword", "false");protected $libFunctions = array();protected $registeredVars = array();protected $preserveComments = false;public $vPrefix = '@'; // prefix of abstract propertiespublic $mPrefix = '$'; // prefix of abstract blockspublic $parentSelector = '&';public $importDisabled = false;public $importDir = '';protected $numberPrecision = null;protected $allParsedFiles = array();// set to the parser that generated the current line when compiling// so we know how to create error messagesprotected $sourceParser = null;protected $sourceLoc = null;static public $defaultValue = array("keyword", "");static protected $nextImportId = 0; // uniquely identify imports// attempts to find the path of an import url, returns null for css filesprotected function findImport($url) {foreach ((array)$this->importDir as $dir) {$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {return $file;}}return null;}protected function fileExists($name) {return is_file($name);}static public function compressList($items, $delim) {if (!isset($items[1]) && isset($items[0])) return $items[0];else return array('list', $delim, $items);}static public function preg_quote($what) {return preg_quote($what, '/');}protected function tryImport($importPath, $parentBlock, $out) {if ($importPath[0] == "function" && $importPath[1] == "url") {$importPath = $this->flattenList($importPath[2]);}$str = $this->coerceString($importPath);if ($str === null) return false;$url = $this->compileValue($this->lib_e($str));// don't import if it ends in cssif (substr_compare($url, '.css', -4, 4) === 0) return false;$realPath = $this->findImport($url);if ($realPath === null) return false;if ($this->importDisabled) {return array(false, "/* import disabled */");}if (isset($this->allParsedFiles[realpath($realPath)])) {return array(false, null);}$this->addParsedFile($realPath);$parser = $this->makeParser($realPath);$root = $parser->parse(file_get_contents($realPath));// set the parents of all the block propsforeach ($root->props as $prop) {if ($prop[0] == "block") {$prop[1]->parent = $parentBlock;}}// copy mixins into scope, set their parents// bring blocks from import into current block// TODO: need to mark the source parser these came from this fileforeach ($root->children as $childName => $child) {if (isset($parentBlock->children[$childName])) {$parentBlock->children[$childName] = array_merge($parentBlock->children[$childName],$child);} else {$parentBlock->children[$childName] = $child;}}$pi = pathinfo($realPath);$dir = $pi["dirname"];list($top, $bottom) = $this->sortProps($root->props, true);$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);return array(true, $bottom, $parser, $dir);}protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {$oldSourceParser = $this->sourceParser;$oldImport = $this->importDir;// TODO: this is because the importDir api is stupid$this->importDir = (array)$this->importDir;array_unshift($this->importDir, $importDir);foreach ($props as $prop) {$this->compileProp($prop, $block, $out);}$this->importDir = $oldImport;$this->sourceParser = $oldSourceParser;}/*** Recursively compiles a block.** A block is analogous to a CSS block in most cases. A single LESS document* is encapsulated in a block when parsed, but it does not have parent tags* so all of it's children appear on the root level when compiled.** Blocks are made up of props and children.** Props are property instructions, array tuples which describe an action* to be taken, eg. write a property, set a variable, mixin a block.** The children of a block are just all the blocks that are defined within.* This is used to look up mixins when performing a mixin.** Compiling the block involves pushing a fresh environment on the stack,* and iterating through the props, compiling each one.** See lessc::compileProp()**/protected function compileBlock($block) {switch ($block->type) {case "root":$this->compileRoot($block);break;case null:$this->compileCSSBlock($block);break;case "media":$this->compileMedia($block);break;case "directive":$name = "@" . $block->name;if (!empty($block->value)) {$name .= " " . $this->compileValue($this->reduce($block->value));}$this->compileNestedBlock($block, array($name));break;default:$this->throwError("unknown block type: $block->type\n");}}protected function compileCSSBlock($block) {$env = $this->pushEnv();$selectors = $this->compileSelectors($block->tags);$env->selectors = $this->multiplySelectors($selectors);$out = $this->makeOutputBlock(null, $env->selectors);$this->scope->children[] = $out;$this->compileProps($block, $out);$block->scope = $env; // mixins carry scope with them!$this->popEnv();}protected function compileMedia($media) {$env = $this->pushEnv($media);$parentScope = $this->mediaParent($this->scope);$query = $this->compileMediaQuery($this->multiplyMedia($env));$this->scope = $this->makeOutputBlock($media->type, array($query));$parentScope->children[] = $this->scope;$this->compileProps($media, $this->scope);if (count($this->scope->lines) > 0) {$orphanSelelectors = $this->findClosestSelectors();if (!is_null($orphanSelelectors)) {$orphan = $this->makeOutputBlock(null, $orphanSelelectors);$orphan->lines = $this->scope->lines;array_unshift($this->scope->children, $orphan);$this->scope->lines = array();}}$this->scope = $this->scope->parent;$this->popEnv();}protected function mediaParent($scope) {while (!empty($scope->parent)) {if (!empty($scope->type) && $scope->type != "media") {break;}$scope = $scope->parent;}return $scope;}protected function compileNestedBlock($block, $selectors) {$this->pushEnv($block);$this->scope = $this->makeOutputBlock($block->type, $selectors);$this->scope->parent->children[] = $this->scope;$this->compileProps($block, $this->scope);$this->scope = $this->scope->parent;$this->popEnv();}protected function compileRoot($root) {$this->pushEnv();$this->scope = $this->makeOutputBlock($root->type);$this->compileProps($root, $this->scope);$this->popEnv();}protected function compileProps($block, $out) {foreach ($this->sortProps($block->props) as $prop) {$this->compileProp($prop, $block, $out);}$out->lines = array_values(array_unique($out->lines));}protected function sortProps($props, $split = false) {$vars = array();$imports = array();$other = array();foreach ($props as $prop) {switch ($prop[0]) {case "assign":if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {$vars[] = $prop;} else {$other[] = $prop;}break;case "import":$id = self::$nextImportId++;$prop[] = $id;$imports[] = $prop;$other[] = array("import_mixin", $id);break;default:$other[] = $prop;}}if ($split) {return array(array_merge($vars, $imports), $other);} else {return array_merge($vars, $imports, $other);}}protected function compileMediaQuery($queries) {$compiledQueries = array();foreach ($queries as $query) {$parts = array();foreach ($query as $q) {switch ($q[0]) {case "mediaType":$parts[] = implode(" ", array_slice($q, 1));break;case "mediaExp":if (isset($q[2])) {$parts[] = "($q[1]: " .$this->compileValue($this->reduce($q[2])) . ")";} else {$parts[] = "($q[1])";}break;case "variable":$parts[] = $this->compileValue($this->reduce($q));break;}}if (count($parts) > 0) {$compiledQueries[] = implode(" and ", $parts);}}$out = "@media";if (!empty($parts)) {$out .= " " .implode($this->formatter->selectorSeparator, $compiledQueries);}return $out;}protected function multiplyMedia($env, $childQueries = null) {if (is_null($env) ||!empty($env->block->type) && $env->block->type != "media"){return $childQueries;}// plain old block, skipif (empty($env->block->type)) {return $this->multiplyMedia($env->parent, $childQueries);}$out = array();$queries = $env->block->queries;if (is_null($childQueries)) {$out = $queries;} else {foreach ($queries as $parent) {foreach ($childQueries as $child) {$out[] = array_merge($parent, $child);}}}return $this->multiplyMedia($env->parent, $out);}protected function expandParentSelectors(&$tag, $replace) {$parts = explode("$&$", $tag);$count = 0;foreach ($parts as &$part) {$part = str_replace($this->parentSelector, $replace, $part, $c);$count += $c;}$tag = implode($this->parentSelector, $parts);return $count;}protected function findClosestSelectors() {$env = $this->env;$selectors = null;while ($env !== null) {if (isset($env->selectors)) {$selectors = $env->selectors;break;}$env = $env->parent;}return $selectors;}// multiply $selectors against the nearest selectors in envprotected function multiplySelectors($selectors) {// find parent selectors$parentSelectors = $this->findClosestSelectors();if (is_null($parentSelectors)) {// kill parent reference in top level selectorforeach ($selectors as &$s) {$this->expandParentSelectors($s, "");}return $selectors;}$out = array();foreach ($parentSelectors as $parent) {foreach ($selectors as $child) {$count = $this->expandParentSelectors($child, $parent);// don't prepend the parent tag if & was usedif ($count > 0) {$out[] = trim($child);} else {$out[] = trim($parent . ' ' . $child);}}}return $out;}// reduces selector expressionsprotected function compileSelectors($selectors) {$out = array();foreach ($selectors as $s) {if (is_array($s)) {list(, $value) = $s;$out[] = trim($this->compileValue($this->reduce($value)));} else {$out[] = $s;}}return $out;}protected function eq($left, $right) {return $left == $right;}protected function patternMatch($block, $orderedArgs, $keywordArgs) {// match the guards if it has them// any one of the groups must have all its guards pass for a matchif (!empty($block->guards)) {$groupPassed = false;foreach ($block->guards as $guardGroup) {foreach ($guardGroup as $guard) {$this->pushEnv();$this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);$negate = false;if ($guard[0] == "negate") {$guard = $guard[1];$negate = true;}$passed = $this->reduce($guard) == self::$TRUE;if ($negate) $passed = !$passed;$this->popEnv();if ($passed) {$groupPassed = true;} else {$groupPassed = false;break;}}if ($groupPassed) break;}if (!$groupPassed) {return false;}}if (empty($block->args)) {return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);}$remainingArgs = $block->args;if ($keywordArgs) {$remainingArgs = array();foreach ($block->args as $arg) {if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {continue;}$remainingArgs[] = $arg;}}$i = -1; // no args// try to match by arity or by argument literalforeach ($remainingArgs as $i => $arg) {switch ($arg[0]) {case "lit":if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {return false;}break;case "arg":// no arg and no default valueif (!isset($orderedArgs[$i]) && !isset($arg[2])) {return false;}break;case "rest":$i--; // rest can be emptybreak 2;}}if ($block->isVararg) {return true; // not having enough is handled above} else {$numMatched = $i + 1;// greater than becuase default values always matchreturn $numMatched >= count($orderedArgs);}}protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {$matches = null;foreach ($blocks as $block) {// skip seen blocks that don't have argumentsif (isset($skip[$block->id]) && !isset($block->args)) {continue;}if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {$matches[] = $block;}}return $matches;}// attempt to find blocks matched by path and argsprotected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {if ($searchIn == null) return null;if (isset($seen[$searchIn->id])) return null;$seen[$searchIn->id] = true;$name = $path[0];if (isset($searchIn->children[$name])) {$blocks = $searchIn->children[$name];if (count($path) == 1) {$matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);if (!empty($matches)) {// This will return all blocks that match in the closest// scope that has any matching block, like lessjsreturn $matches;}} else {$matches = array();foreach ($blocks as $subBlock) {$subMatches = $this->findBlocks($subBlock,array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);if (!is_null($subMatches)) {foreach ($subMatches as $sm) {$matches[] = $sm;}}}return count($matches) > 0 ? $matches : null;}}if ($searchIn->parent === $searchIn) return null;return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);}// sets all argument names in $args to either the default value// or the one passed in through $valuesprotected function zipSetArgs($args, $orderedValues, $keywordValues) {$assignedValues = array();$i = 0;foreach ($args as $a) {if ($a[0] == "arg") {if (isset($keywordValues[$a[1]])) {// has keyword arg$value = $keywordValues[$a[1]];} elseif (isset($orderedValues[$i])) {// has ordered arg$value = $orderedValues[$i];$i++;} elseif (isset($a[2])) {// has default value$value = $a[2];} else {$this->throwError("Failed to assign arg " . $a[1]);$value = null; // :(}$value = $this->reduce($value);$this->set($a[1], $value);$assignedValues[] = $value;} else {// a lit$i++;}}// check for a rest$last = end($args);if ($last[0] == "rest") {$rest = array_slice($orderedValues, count($args) - 1);$this->set($last[1], $this->reduce(array("list", " ", $rest)));}// wow is this the only true use of PHP's + operator for arrays?$this->env->arguments = $assignedValues + $orderedValues;}// compile a prop and update $lines or $blocks appropriatelyprotected function compileProp($prop, $block, $out) {// set error position context$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;switch ($prop[0]) {case 'assign':list(, $name, $value) = $prop;if ($name[0] == $this->vPrefix) {$this->set($name, $value);} else {$out->lines[] = $this->formatter->property($name,$this->compileValue($this->reduce($value)));}break;case 'block':list(, $child) = $prop;$this->compileBlock($child);break;case 'mixin':list(, $path, $args, $suffix) = $prop;$orderedArgs = array();$keywordArgs = array();foreach ((array)$args as $arg) {$argval = null;switch ($arg[0]) {case "arg":if (!isset($arg[2])) {$orderedArgs[] = $this->reduce(array("variable", $arg[1]));} else {$keywordArgs[$arg[1]] = $this->reduce($arg[2]);}break;case "lit":$orderedArgs[] = $this->reduce($arg[1]);break;default:$this->throwError("Unknown arg type: " . $arg[0]);}}$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);if ($mixins === null) {// fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n");break; // throw error here??}foreach ($mixins as $mixin) {if ($mixin === $block && !$orderedArgs) {continue;}$haveScope = false;if (isset($mixin->parent->scope)) {$haveScope = true;$mixinParentEnv = $this->pushEnv();$mixinParentEnv->storeParent = $mixin->parent->scope;}$haveArgs = false;if (isset($mixin->args)) {$haveArgs = true;$this->pushEnv();$this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);}$oldParent = $mixin->parent;if ($mixin != $block) $mixin->parent = $block;foreach ($this->sortProps($mixin->props) as $subProp) {if ($suffix !== null &&$subProp[0] == "assign" &&is_string($subProp[1]) &&$subProp[1]{0} != $this->vPrefix){$subProp[2] = array('list', ' ',array($subProp[2], array('keyword', $suffix)));}$this->compileProp($subProp, $mixin, $out);}$mixin->parent = $oldParent;if ($haveArgs) $this->popEnv();if ($haveScope) $this->popEnv();}break;case 'raw':$out->lines[] = $prop[1];break;case "directive":list(, $name, $value) = $prop;$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';break;case "comment":$out->lines[] = $prop[1];break;case "import";list(, $importPath, $importId) = $prop;$importPath = $this->reduce($importPath);if (!isset($this->env->imports)) {$this->env->imports = array();}$result = $this->tryImport($importPath, $block, $out);$this->env->imports[$importId] = $result === false ?array(false, "@import " . $this->compileValue($importPath).";") :$result;break;case "import_mixin":list(,$importId) = $prop;$import = $this->env->imports[$importId];if ($import[0] === false) {if (isset($import[1])) {$out->lines[] = $import[1];}} else {list(, $bottom, $parser, $importDir) = $import;$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);}break;default:$this->throwError("unknown op: {$prop[0]}\n");}}/*** Compiles a primitive value into a CSS property value.** Values in lessphp are typed by being wrapped in arrays, their format is* typically:** array(type, contents [, additional_contents]*)** The input is expected to be reduced. This function will not work on* things like expressions and variables.*/protected function compileValue($value) {switch ($value[0]) {case 'list':// [1] - delimiter// [2] - array of valuesreturn implode($value[1], array_map(array($this, 'compileValue'), $value[2]));case 'raw_color':if (!empty($this->formatter->compressColors)) {return $this->compileValue($this->coerceColor($value));}return $value[1];case 'keyword':// [1] - the keywordreturn $value[1];case 'number':list(, $num, $unit) = $value;// [1] - the number// [2] - the unitif ($this->numberPrecision !== null) {$num = round($num, $this->numberPrecision);}return $num . $unit;case 'string':// [1] - contents of string (includes quotes)list(, $delim, $content) = $value;foreach ($content as &$part) {if (is_array($part)) {$part = $this->compileValue($part);}}return $delim . implode($content) . $delim;case 'color':// [1] - red component (either number or a %)// [2] - green component// [3] - blue component// [4] - optional alpha componentlist(, $r, $g, $b) = $value;$r = round($r);$g = round($g);$b = round($b);if (count($value) == 5 && $value[4] != 1) { // rgbareturn 'rgba('.$r.','.$g.','.$b.','.$value[4].')';}$h = sprintf("#%02x%02x%02x", $r, $g, $b);if (!empty($this->formatter->compressColors)) {// Converting hex color to short notation (e.g. #003399 to #039)if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {$h = '#' . $h[1] . $h[3] . $h[5];}}return $h;case 'function':list(, $name, $args) = $value;return $name.'('.$this->compileValue($args).')';default: // assumed to be unit$this->throwError("unknown value type: $value[0]");}}protected function lib_pow($args) {list($base, $exp) = $this->assertArgs($args, 2, "pow");return pow($this->assertNumber($base), $this->assertNumber($exp));}protected function lib_pi() {return pi();}protected function lib_mod($args) {list($a, $b) = $this->assertArgs($args, 2, "mod");return $this->assertNumber($a) % $this->assertNumber($b);}protected function lib_tan($num) {return tan($this->assertNumber($num));}protected function lib_sin($num) {return sin($this->assertNumber($num));}protected function lib_cos($num) {return cos($this->assertNumber($num));}protected function lib_atan($num) {$num = atan($this->assertNumber($num));return array("number", $num, "rad");}protected function lib_asin($num) {$num = asin($this->assertNumber($num));return array("number", $num, "rad");}protected function lib_acos($num) {$num = acos($this->assertNumber($num));return array("number", $num, "rad");}protected function lib_sqrt($num) {return sqrt($this->assertNumber($num));}protected function lib_extract($value) {list($list, $idx) = $this->assertArgs($value, 2, "extract");$idx = $this->assertNumber($idx);// 1 indexedif ($list[0] == "list" && isset($list[2][$idx - 1])) {return $list[2][$idx - 1];}}protected function lib_isnumber($value) {return $this->toBool($value[0] == "number");}protected function lib_isstring($value) {return $this->toBool($value[0] == "string");}protected function lib_iscolor($value) {return $this->toBool($this->coerceColor($value));}protected function lib_iskeyword($value) {return $this->toBool($value[0] == "keyword");}protected function lib_ispixel($value) {return $this->toBool($value[0] == "number" && $value[2] == "px");}protected function lib_ispercentage($value) {return $this->toBool($value[0] == "number" && $value[2] == "%");}protected function lib_isem($value) {return $this->toBool($value[0] == "number" && $value[2] == "em");}protected function lib_isrem($value) {return $this->toBool($value[0] == "number" && $value[2] == "rem");}protected function lib_rgbahex($color) {$color = $this->coerceColor($color);if (is_null($color))$this->throwError("color expected for rgbahex");return sprintf("#%02x%02x%02x%02x",isset($color[4]) ? $color[4]*255 : 255,$color[1],$color[2], $color[3]);}protected function lib_argb($color){return $this->lib_rgbahex($color);}// utility func to unquote a stringprotected function lib_e($arg) {switch ($arg[0]) {case "list":$items = $arg[2];if (isset($items[0])) {return $this->lib_e($items[0]);}return self::$defaultValue;case "string":$arg[1] = "";return $arg;case "keyword":return $arg;default:return array("keyword", $this->compileValue($arg));}}protected function lib__sprintf($args) {if ($args[0] != "list") return $args;$values = $args[2];$string = array_shift($values);$template = $this->compileValue($this->lib_e($string));$i = 0;if (preg_match_all('/%[dsa]/', $template, $m)) {foreach ($m[0] as $match) {$val = isset($values[$i]) ?$this->reduce($values[$i]) : array('keyword', '');// lessjs compat, renders fully expanded color, not raw colorif ($color = $this->coerceColor($val)) {$val = $color;}$i++;$rep = $this->compileValue($this->lib_e($val));$template = preg_replace('/'.self::preg_quote($match).'/',$rep, $template, 1);}}$d = $string[0] == "string" ? $string[1] : '"';return array("string", $d, array($template));}protected function lib_floor($arg) {$value = $this->assertNumber($arg);return array("number", floor($value), $arg[2]);}protected function lib_ceil($arg) {$value = $this->assertNumber($arg);return array("number", ceil($value), $arg[2]);}protected function lib_round($arg) {$value = $this->assertNumber($arg);return array("number", round($value), $arg[2]);}protected function lib_unit($arg) {if ($arg[0] == "list") {list($number, $newUnit) = $arg[2];return array("number", $this->assertNumber($number),$this->compileValue($this->lib_e($newUnit)));} else {return array("number", $this->assertNumber($arg), "");}}/*** Helper function to get arguments for color manipulation functions.* takes a list that contains a color like thing and a percentage*/protected function colorArgs($args) {if ($args[0] != 'list' || count($args[2]) < 2) {return array(array('color', 0, 0, 0), 0);}list($color, $delta) = $args[2];$color = $this->assertColor($color);$delta = floatval($delta[1]);return array($color, $delta);}protected function lib_darken($args) {list($color, $delta) = $this->colorArgs($args);$hsl = $this->toHSL($color);$hsl[3] = $this->clamp($hsl[3] - $delta, 100);return $this->toRGB($hsl);}protected function lib_lighten($args) {list($color, $delta) = $this->colorArgs($args);$hsl = $this->toHSL($color);$hsl[3] = $this->clamp($hsl[3] + $delta, 100);return $this->toRGB($hsl);}protected function lib_saturate($args) {list($color, $delta) = $this->colorArgs($args);$hsl = $this->toHSL($color);$hsl[2] = $this->clamp($hsl[2] + $delta, 100);return $this->toRGB($hsl);}protected function lib_desaturate($args) {list($color, $delta) = $this->colorArgs($args);$hsl = $this->toHSL($color);$hsl[2] = $this->clamp($hsl[2] - $delta, 100);return $this->toRGB($hsl);}protected function lib_spin($args) {list($color, $delta) = $this->colorArgs($args);$hsl = $this->toHSL($color);$hsl[1] = $hsl[1] + $delta % 360;if ($hsl[1] < 0) $hsl[1] += 360;return $this->toRGB($hsl);}protected function lib_fadeout($args) {list($color, $delta) = $this->colorArgs($args);$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);return $color;}protected function lib_fadein($args) {list($color, $delta) = $this->colorArgs($args);$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);return $color;}protected function lib_hue($color) {$hsl = $this->toHSL($this->assertColor($color));return round($hsl[1]);}protected function lib_saturation($color) {$hsl = $this->toHSL($this->assertColor($color));return round($hsl[2]);}protected function lib_lightness($color) {$hsl = $this->toHSL($this->assertColor($color));return round($hsl[3]);}// get the alpha of a color// defaults to 1 for non-colors or colors without an alphaprotected function lib_alpha($value) {if (!is_null($color = $this->coerceColor($value))) {return isset($color[4]) ? $color[4] : 1;}}// set the alpha of the colorprotected function lib_fade($args) {list($color, $alpha) = $this->colorArgs($args);$color[4] = $this->clamp($alpha / 100.0);return $color;}protected function lib_percentage($arg) {$num = $this->assertNumber($arg);return array("number", $num*100, "%");}// mixes two colors by weight// mix(@color1, @color2, [@weight: 50%]);// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_methodprotected function lib_mix($args) {if ($args[0] != "list" || count($args[2]) < 2)$this->throwError("mix expects (color1, color2, weight)");list($first, $second) = $args[2];$first = $this->assertColor($first);$second = $this->assertColor($second);$first_a = $this->lib_alpha($first);$second_a = $this->lib_alpha($second);if (isset($args[2][2])) {$weight = $args[2][2][1] / 100.0;} else {$weight = 0.5;}$w = $weight * 2 - 1;$a = $first_a - $second_a;$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;$w2 = 1.0 - $w1;$new = array('color',$w1 * $first[1] + $w2 * $second[1],$w1 * $first[2] + $w2 * $second[2],$w1 * $first[3] + $w2 * $second[3],);if ($first_a != 1.0 || $second_a != 1.0) {$new[] = $first_a * $weight + $second_a * ($weight - 1);}return $this->fixColor($new);}protected function lib_contrast($args) {if ($args[0] != 'list' || count($args[2]) < 3) {return array(array('color', 0, 0, 0), 0);}list($inputColor, $darkColor, $lightColor) = $args[2];$inputColor = $this->assertColor($inputColor);$darkColor = $this->assertColor($darkColor);$lightColor = $this->assertColor($lightColor);$hsl = $this->toHSL($inputColor);if ($hsl[3] > 50) {return $darkColor;}return $lightColor;}protected function assertColor($value, $error = "expected color value") {$color = $this->coerceColor($value);if (is_null($color)) $this->throwError($error);return $color;}protected function assertNumber($value, $error = "expecting number") {if ($value[0] == "number") return $value[1];$this->throwError($error);}protected function assertArgs($value, $expectedArgs, $name="") {if ($expectedArgs == 1) {return $value;} else {if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");$values = $value[2];$numValues = count($values);if ($expectedArgs != $numValues) {if ($name) {$name = $name . ": ";}$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");}return $values;}}protected function toHSL($color) {if ($color[0] == 'hsl') return $color;$r = $color[1] / 255;$g = $color[2] / 255;$b = $color[3] / 255;$min = min($r, $g, $b);$max = max($r, $g, $b);$L = ($min + $max) / 2;if ($min == $max) {$S = $H = 0;} else {if ($L < 0.5)$S = ($max - $min)/($max + $min);else$S = ($max - $min)/(2.0 - $max - $min);if ($r == $max) $H = ($g - $b)/($max - $min);elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);}$out = array('hsl',($H < 0 ? $H + 6 : $H)*60,$S*100,$L*100,);if (count($color) > 4) $out[] = $color[4]; // copy alphareturn $out;}protected function toRGB_helper($comp, $temp1, $temp2) {if ($comp < 0) $comp += 1.0;elseif ($comp > 1) $comp -= 1.0;if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;if (2 * $comp < 1) return $temp2;if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;return $temp1;}/*** Converts a hsl array into a color value in rgb.* Expects H to be in range of 0 to 360, S and L in 0 to 100*/protected function toRGB($color) {if ($color[0] == 'color') return $color;$H = $color[1] / 360;$S = $color[2] / 100;$L = $color[3] / 100;if ($S == 0) {$r = $g = $b = $L;} else {$temp2 = $L < 0.5 ?$L*(1.0 + $S) :$L + $S - $L * $S;$temp1 = 2.0 * $L - $temp2;$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);$g = $this->toRGB_helper($H, $temp1, $temp2);$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);}// $out = array('color', round($r*255), round($g*255), round($b*255));$out = array('color', $r*255, $g*255, $b*255);if (count($color) > 4) $out[] = $color[4]; // copy alphareturn $out;}protected function clamp($v, $max = 1, $min = 0) {return min($max, max($min, $v));}/*** Convert the rgb, rgba, hsl color literals of function type* as returned by the parser into values of color type.*/protected function funcToColor($func) {$fname = $func[1];if ($func[2][0] != 'list') return false; // need a list of arguments$rawComponents = $func[2][2];if ($fname == 'hsl' || $fname == 'hsla') {$hsl = array('hsl');$i = 0;foreach ($rawComponents as $c) {$val = $this->reduce($c);$val = isset($val[1]) ? floatval($val[1]) : 0;if ($i == 0) $clamp = 360;elseif ($i < 3) $clamp = 100;else $clamp = 1;$hsl[] = $this->clamp($val, $clamp);$i++;}while (count($hsl) < 4) $hsl[] = 0;return $this->toRGB($hsl);} elseif ($fname == 'rgb' || $fname == 'rgba') {$components = array();$i = 1;foreach ($rawComponents as $c) {$c = $this->reduce($c);if ($i < 4) {if ($c[0] == "number" && $c[2] == "%") {$components[] = 255 * ($c[1] / 100);} else {$components[] = floatval($c[1]);}} elseif ($i == 4) {if ($c[0] == "number" && $c[2] == "%") {$components[] = 1.0 * ($c[1] / 100);} else {$components[] = floatval($c[1]);}} else break;$i++;}while (count($components) < 3) $components[] = 0;array_unshift($components, 'color');return $this->fixColor($components);}return false;}protected function reduce($value, $forExpression = false) {switch ($value[0]) {case "interpolate":$reduced = $this->reduce($value[1]);$var = $this->compileValue($reduced);$res = $this->reduce(array("variable", $this->vPrefix . $var));if ($res[0] == "raw_color") {$res = $this->coerceColor($res);}if (empty($value[2])) $res = $this->lib_e($res);return $res;case "variable":$key = $value[1];if (is_array($key)) {$key = $this->reduce($key);$key = $this->vPrefix . $this->compileValue($this->lib_e($key));}$seen =& $this->env->seenNames;if (!empty($seen[$key])) {$this->throwError("infinite loop detected: $key");}$seen[$key] = true;$out = $this->reduce($this->get($key, self::$defaultValue));$seen[$key] = false;return $out;case "list":foreach ($value[2] as &$item) {$item = $this->reduce($item, $forExpression);}return $value;case "expression":return $this->evaluate($value);case "string":foreach ($value[2] as &$part) {if (is_array($part)) {$strip = $part[0] == "variable";$part = $this->reduce($part);if ($strip) $part = $this->lib_e($part);}}return $value;case "escape":list(,$inner) = $value;return $this->lib_e($this->reduce($inner));case "function":$color = $this->funcToColor($value);if ($color) return $color;list(, $name, $args) = $value;if ($name == "%") $name = "_sprintf";$f = isset($this->libFunctions[$name]) ?$this->libFunctions[$name] : array($this, 'lib_'.$name);if (is_callable($f)) {if ($args[0] == 'list')$args = self::compressList($args[2], $args[1]);$ret = call_user_func($f, $this->reduce($args, true), $this);if (is_null($ret)) {return array("string", "", array($name, "(", $args, ")"));}// convert to a typed value if the result is a php primitiveif (is_numeric($ret)) $ret = array('number', $ret, "");elseif (!is_array($ret)) $ret = array('keyword', $ret);return $ret;}// plain function, reduce args$value[2] = $this->reduce($value[2]);return $value;case "unary":list(, $op, $exp) = $value;$exp = $this->reduce($exp);if ($exp[0] == "number") {switch ($op) {case "+":return $exp;case "-":$exp[1] *= -1;return $exp;}}return array("string", "", array($op, $exp));}if ($forExpression) {switch ($value[0]) {case "keyword":if ($color = $this->coerceColor($value)) {return $color;}break;case "raw_color":return $this->coerceColor($value);}}return $value;}// coerce a value for use in color operationprotected function coerceColor($value) {switch($value[0]) {case 'color': return $value;case 'raw_color':$c = array("color", 0, 0, 0);$colorStr = substr($value[1], 1);$num = hexdec($colorStr);$width = strlen($colorStr) == 3 ? 16 : 256;for ($i = 3; $i > 0; $i--) { // 3 2 1$t = $num % $width;$num /= $width;$c[$i] = $t * (256/$width) + $t * floor(16/$width);}return $c;case 'keyword':$name = $value[1];if (isset(self::$cssColors[$name])) {$rgba = explode(',', self::$cssColors[$name]);if(isset($rgba[3]))return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);return array('color', $rgba[0], $rgba[1], $rgba[2]);}return null;}}// make something string like into a stringprotected function coerceString($value) {switch ($value[0]) {case "string":return $value;case "keyword":return array("string", "", array($value[1]));}return null;}// turn list of length 1 into value typeprotected function flattenList($value) {if ($value[0] == "list" && count($value[2]) == 1) {return $this->flattenList($value[2][0]);}return $value;}protected function toBool($a) {if ($a) return self::$TRUE;else return self::$FALSE;}// evaluate an expressionprotected function evaluate($exp) {list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;$left = $this->reduce($left, true);$right = $this->reduce($right, true);if ($leftColor = $this->coerceColor($left)) {$left = $leftColor;}if ($rightColor = $this->coerceColor($right)) {$right = $rightColor;}$ltype = $left[0];$rtype = $right[0];// operators that work on all typesif ($op == "and") {return $this->toBool($left == self::$TRUE && $right == self::$TRUE);}if ($op == "=") {return $this->toBool($this->eq($left, $right) );}if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {return $str;}// type based operators$fname = "op_${ltype}_${rtype}";if (is_callable(array($this, $fname))) {$out = $this->$fname($op, $left, $right);if (!is_null($out)) return $out;}// make the expression look it did before being parsed$paddedOp = $op;if ($whiteBefore) $paddedOp = " " . $paddedOp;if ($whiteAfter) $paddedOp .= " ";return array("string", "", array($left, $paddedOp, $right));}protected function stringConcatenate($left, $right) {if ($strLeft = $this->coerceString($left)) {if ($right[0] == "string") {$right[1] = "";}$strLeft[2][] = $right;return $strLeft;}if ($strRight = $this->coerceString($right)) {array_unshift($strRight[2], $left);return $strRight;}}// make sure a color's components don't go out of boundsprotected function fixColor($c) {foreach (range(1, 3) as $i) {if ($c[$i] < 0) $c[$i] = 0;if ($c[$i] > 255) $c[$i] = 255;}return $c;}protected function op_number_color($op, $lft, $rgt) {if ($op == '+' || $op == '*') {return $this->op_color_number($op, $rgt, $lft);}}protected function op_color_number($op, $lft, $rgt) {if ($rgt[0] == '%') $rgt[1] /= 100;return $this->op_color_color($op, $lft,array_fill(1, count($lft) - 1, $rgt[1]));}protected function op_color_color($op, $left, $right) {$out = array('color');$max = count($left) > count($right) ? count($left) : count($right);foreach (range(1, $max - 1) as $i) {$lval = isset($left[$i]) ? $left[$i] : 0;$rval = isset($right[$i]) ? $right[$i] : 0;switch ($op) {case '+':$out[] = $lval + $rval;break;case '-':$out[] = $lval - $rval;break;case '*':$out[] = $lval * $rval;break;case '%':$out[] = $lval % $rval;break;case '/':if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");$out[] = $lval / $rval;break;default:$this->throwError('evaluate error: color op number failed on op '.$op);}}return $this->fixColor($out);}function lib_red($color){$color = $this->coerceColor($color);if (is_null($color)) {$this->throwError('color expected for red()');}return $color[1];}function lib_green($color){$color = $this->coerceColor($color);if (is_null($color)) {$this->throwError('color expected for green()');}return $color[2];}function lib_blue($color){$color = $this->coerceColor($color);if (is_null($color)) {$this->throwError('color expected for blue()');}return $color[3];}// operator on two numbersprotected function op_number_number($op, $left, $right) {$unit = empty($left[2]) ? $right[2] : $left[2];$value = 0;switch ($op) {case '+':$value = $left[1] + $right[1];break;case '*':$value = $left[1] * $right[1];break;case '-':$value = $left[1] - $right[1];break;case '%':$value = $left[1] % $right[1];break;case '/':if ($right[1] == 0) $this->throwError('parse error: divide by zero');$value = $left[1] / $right[1];break;case '<':return $this->toBool($left[1] < $right[1]);case '>':return $this->toBool($left[1] > $right[1]);case '>=':return $this->toBool($left[1] >= $right[1]);case '=<':return $this->toBool($left[1] <= $right[1]);default:$this->throwError('parse error: unknown number operator: '.$op);}return array("number", $value, $unit);}/* environment functions */protected function makeOutputBlock($type, $selectors = null) {$b = new stdclass;$b->lines = array();$b->children = array();$b->selectors = $selectors;$b->type = $type;$b->parent = $this->scope;return $b;}// the state of executionprotected function pushEnv($block = null) {$e = new stdclass;$e->parent = $this->env;$e->store = array();$e->block = $block;$this->env = $e;return $e;}// pop something off the stackprotected function popEnv() {$old = $this->env;$this->env = $this->env->parent;return $old;}// set something in the current envprotected function set($name, $value) {$this->env->store[$name] = $value;}// get the highest occurrence entry for a nameprotected function get($name, $default=null) {$current = $this->env;$isArguments = $name == $this->vPrefix . 'arguments';while ($current) {if ($isArguments && isset($current->arguments)) {return array('list', ' ', $current->arguments);}if (isset($current->store[$name]))return $current->store[$name];else {$current = isset($current->storeParent) ?$current->storeParent : $current->parent;}}return $default;}// inject array of unparsed strings into environment as variablesprotected function injectVariables($args) {$this->pushEnv();$parser = new lessc_parser($this, __METHOD__);foreach ($args as $name => $strValue) {if ($name{0} != '@') $name = '@'.$name;$parser->count = 0;$parser->buffer = (string)$strValue;if (!$parser->propertyValue($value)) {throw new Exception("failed to parse passed in variable $name: $strValue");}$this->set($name, $value);}}/*** Initialize any static state, can initialize parser for a file* $opts isn't used yet*/public function __construct($fname = null) {if ($fname !== null) {// used for deprecated parse method$this->_parseFile = $fname;}}public function compile($string, $name = null) {$locale = setlocale(LC_NUMERIC, 0);setlocale(LC_NUMERIC, "C");$this->parser = $this->makeParser($name);$root = $this->parser->parse($string);$this->env = null;$this->scope = null;$this->formatter = $this->newFormatter();if (!empty($this->registeredVars)) {$this->injectVariables($this->registeredVars);}$this->sourceParser = $this->parser; // used for error messages$this->compileBlock($root);ob_start();$this->formatter->block($this->scope);$out = ob_get_clean();setlocale(LC_NUMERIC, $locale);return $out;}public function compileFile($fname, $outFname = null) {if (!is_readable($fname)) {throw new Exception('load error: failed to find '.$fname);}$pi = pathinfo($fname);$oldImport = $this->importDir;$this->importDir = (array)$this->importDir;$this->importDir[] = $pi['dirname'].'/';$this->addParsedFile($fname);$out = $this->compile(file_get_contents($fname), $fname);$this->importDir = $oldImport;if ($outFname !== null) {return file_put_contents($outFname, $out);}return $out;}// compile only if changed input has changed or output doesn't existpublic function checkedCompile($in, $out) {if (!is_file($out) || filemtime($in) > filemtime($out)) {$this->compileFile($in, $out);return true;}return false;}/*** Execute lessphp on a .less file or a lessphp cache structure** The lessphp cache structure contains information about a specific* less file having been parsed. It can be used as a hint for future* calls to determine whether or not a rebuild is required.** The cache structure contains two important keys that may be used* externally:** compiled: The final compiled CSS* updated: The time (in seconds) the CSS was last compiled** The cache structure is a plain-ol' PHP associative array and can* be serialized and unserialized without a hitch.** @param mixed $in Input* @param bool $force Force rebuild?* @return array lessphp cache structure*/public function cachedCompile($in, $force = false) {// assume no root$root = null;if (is_string($in)) {$root = $in;} elseif (is_array($in) and isset($in['root'])) {if ($force or ! isset($in['files'])) {// If we are forcing a recompile or if for some reason the// structure does not contain any file information we should// specify the root to trigger a rebuild.$root = $in['root'];} elseif (isset($in['files']) and is_array($in['files'])) {foreach ($in['files'] as $fname => $ftime ) {if (!file_exists($fname) or filemtime($fname) > $ftime) {// One of the files we knew about previously has changed// so we should look at our incoming root again.$root = $in['root'];break;}}}} else {// TODO: Throw an exception? We got neither a string nor something// that looks like a compatible lessphp cache structure.return null;}if ($root !== null) {// If we have a root value which means we should rebuild.$out = array();$out['root'] = $root;$out['compiled'] = $this->compileFile($root);$out['files'] = $this->allParsedFiles();$out['updated'] = time();return $out;} else {// No changes, pass back the structure// we were given initially.return $in;}}// parse and compile buffer// This is deprecatedpublic function parse($str = null, $initialVariables = null) {if (is_array($str)) {$initialVariables = $str;$str = null;}$oldVars = $this->registeredVars;if ($initialVariables !== null) {$this->setVariables($initialVariables);}if ($str == null) {if (empty($this->_parseFile)) {throw new exception("nothing to parse");}$out = $this->compileFile($this->_parseFile);} else {$out = $this->compile($str);}$this->registeredVars = $oldVars;return $out;}protected function makeParser($name) {$parser = new lessc_parser($this, $name);$parser->writeComments = $this->preserveComments;return $parser;}public function setFormatter($name) {$this->formatterName = $name;}protected function newFormatter() {$className = "lessc_formatter_lessjs";if (!empty($this->formatterName)) {if (!is_string($this->formatterName))return $this->formatterName;$className = "lessc_formatter_$this->formatterName";}return new $className;}public function setPreserveComments($preserve) {$this->preserveComments = $preserve;}public function registerFunction($name, $func) {$this->libFunctions[$name] = $func;}public function unregisterFunction($name) {unset($this->libFunctions[$name]);}public function setVariables($variables) {$this->registeredVars = array_merge($this->registeredVars, $variables);}public function unsetVariable($name) {unset($this->registeredVars[$name]);}public function setImportDir($dirs) {$this->importDir = (array)$dirs;}public function addImportDir($dir) {$this->importDir = (array)$this->importDir;$this->importDir[] = $dir;}public function allParsedFiles() {return $this->allParsedFiles;}protected function addParsedFile($file) {$this->allParsedFiles[realpath($file)] = filemtime($file);}/*** Uses the current value of $this->count to show line and line number*/protected function throwError($msg = null) {if ($this->sourceLoc >= 0) {$this->sourceParser->throwError($msg, $this->sourceLoc);}throw new exception($msg);}// compile file $in to file $out if $in is newer than $out// returns true when it compiles, false otherwisepublic static function ccompile($in, $out, $less = null) {if ($less === null) {$less = new self;}return $less->checkedCompile($in, $out);}public static function cexecute($in, $force = false, $less = null) {if ($less === null) {$less = new self;}return $less->cachedCompile($in, $force);}static protected $cssColors = array('aliceblue' => '240,248,255','antiquewhite' => '250,235,215','aqua' => '0,255,255','aquamarine' => '127,255,212','azure' => '240,255,255','beige' => '245,245,220','bisque' => '255,228,196','black' => '0,0,0','blanchedalmond' => '255,235,205','blue' => '0,0,255','blueviolet' => '138,43,226','brown' => '165,42,42','burlywood' => '222,184,135','cadetblue' => '95,158,160','chartreuse' => '127,255,0','chocolate' => '210,105,30','coral' => '255,127,80','cornflowerblue' => '100,149,237','cornsilk' => '255,248,220','crimson' => '220,20,60','cyan' => '0,255,255','darkblue' => '0,0,139','darkcyan' => '0,139,139','darkgoldenrod' => '184,134,11','darkgray' => '169,169,169','darkgreen' => '0,100,0','darkgrey' => '169,169,169','darkkhaki' => '189,183,107','darkmagenta' => '139,0,139','darkolivegreen' => '85,107,47','darkorange' => '255,140,0','darkorchid' => '153,50,204','darkred' => '139,0,0','darksalmon' => '233,150,122','darkseagreen' => '143,188,143','darkslateblue' => '72,61,139','darkslategray' => '47,79,79','darkslategrey' => '47,79,79','darkturquoise' => '0,206,209','darkviolet' => '148,0,211','deeppink' => '255,20,147','deepskyblue' => '0,191,255','dimgray' => '105,105,105','dimgrey' => '105,105,105','dodgerblue' => '30,144,255','firebrick' => '178,34,34','floralwhite' => '255,250,240','forestgreen' => '34,139,34','fuchsia' => '255,0,255','gainsboro' => '220,220,220','ghostwhite' => '248,248,255','gold' => '255,215,0','goldenrod' => '218,165,32','gray' => '128,128,128','green' => '0,128,0','greenyellow' => '173,255,47','grey' => '128,128,128','honeydew' => '240,255,240','hotpink' => '255,105,180','indianred' => '205,92,92','indigo' => '75,0,130','ivory' => '255,255,240','khaki' => '240,230,140','lavender' => '230,230,250','lavenderblush' => '255,240,245','lawngreen' => '124,252,0','lemonchiffon' => '255,250,205','lightblue' => '173,216,230','lightcoral' => '240,128,128','lightcyan' => '224,255,255','lightgoldenrodyellow' => '250,250,210','lightgray' => '211,211,211','lightgreen' => '144,238,144','lightgrey' => '211,211,211','lightpink' => '255,182,193','lightsalmon' => '255,160,122','lightseagreen' => '32,178,170','lightskyblue' => '135,206,250','lightslategray' => '119,136,153','lightslategrey' => '119,136,153','lightsteelblue' => '176,196,222','lightyellow' => '255,255,224','lime' => '0,255,0','limegreen' => '50,205,50','linen' => '250,240,230','magenta' => '255,0,255','maroon' => '128,0,0','mediumaquamarine' => '102,205,170','mediumblue' => '0,0,205','mediumorchid' => '186,85,211','mediumpurple' => '147,112,219','mediumseagreen' => '60,179,113','mediumslateblue' => '123,104,238','mediumspringgreen' => '0,250,154','mediumturquoise' => '72,209,204','mediumvioletred' => '199,21,133','midnightblue' => '25,25,112','mintcream' => '245,255,250','mistyrose' => '255,228,225','moccasin' => '255,228,181','navajowhite' => '255,222,173','navy' => '0,0,128','oldlace' => '253,245,230','olive' => '128,128,0','olivedrab' => '107,142,35','orange' => '255,165,0','orangered' => '255,69,0','orchid' => '218,112,214','palegoldenrod' => '238,232,170','palegreen' => '152,251,152','paleturquoise' => '175,238,238','palevioletred' => '219,112,147','papayawhip' => '255,239,213','peachpuff' => '255,218,185','peru' => '205,133,63','pink' => '255,192,203','plum' => '221,160,221','powderblue' => '176,224,230','purple' => '128,0,128','red' => '255,0,0','rosybrown' => '188,143,143','royalblue' => '65,105,225','saddlebrown' => '139,69,19','salmon' => '250,128,114','sandybrown' => '244,164,96','seagreen' => '46,139,87','seashell' => '255,245,238','sienna' => '160,82,45','silver' => '192,192,192','skyblue' => '135,206,235','slateblue' => '106,90,205','slategray' => '112,128,144','slategrey' => '112,128,144','snow' => '255,250,250','springgreen' => '0,255,127','steelblue' => '70,130,180','tan' => '210,180,140','teal' => '0,128,128','thistle' => '216,191,216','tomato' => '255,99,71','transparent' => '0,0,0,0','turquoise' => '64,224,208','violet' => '238,130,238','wheat' => '245,222,179','white' => '255,255,255','whitesmoke' => '245,245,245','yellow' => '255,255,0','yellowgreen' => '154,205,50');}// responsible for taking a string of LESS code and converting it into a// syntax treeclass lessc_parser {static protected $nextBlockId = 0; // used to uniquely identify blocksstatic protected $precedence = array('=<' => 0,'>=' => 0,'=' => 0,'<' => 0,'>' => 0,'+' => 1,'-' => 1,'*' => 2,'/' => 2,'%' => 2,);static protected $whitePattern;static protected $commentMulti;static protected $commentSingle = "//";static protected $commentMultiLeft = "/*";static protected $commentMultiRight = "*/";// regex string to match any of the operatorsstatic protected $operatorString;// these properties will supress division unless it's inside parenthasesstatic protected $supressDivisionProps =array('/border-radius$/i', '/^font$/i');protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");protected $lineDirectives = array("charset");/*** if we are in parens we can be more liberal with whitespace around* operators because it must evaluate to a single value and thus is less* ambiguous.** Consider:* property1: 10 -5; // is two numbers, 10 and -5* property2: (10 -5); // should evaluate to 5*/protected $inParens = false;// caches preg escaped literalsstatic protected $literalCache = array();public function __construct($lessc, $sourceName = null) {$this->eatWhiteDefault = true;// reference to less needed for vPrefix, mPrefix, and parentSelector$this->lessc = $lessc;$this->sourceName = $sourceName; // name used for error messages$this->writeComments = false;if (!self::$operatorString) {self::$operatorString ='('.implode('|', array_map(array('lessc', 'preg_quote'),array_keys(self::$precedence))).')';$commentSingle = lessc::preg_quote(self::$commentSingle);$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';}}public function parse($buffer) {$this->count = 0;$this->line = 1;$this->env = null; // block stack$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);$this->pushSpecialBlock("root");$this->eatWhiteDefault = true;$this->seenComments = array();// trim whitespace on head// if (preg_match('/^\s+/', $this->buffer, $m)) {// $this->line += substr_count($m[0], "\n");// $this->buffer = ltrim($this->buffer);// }$this->whitespace();// parse the entire file$lastCount = $this->count;while (false !== $this->parseChunk());if ($this->count != strlen($this->buffer))$this->throwError();// TODO report where the block was openedif (!is_null($this->env->parent))throw new exception('parse error: unclosed block');return $this->env;}/*** Parse a single chunk off the head of the buffer and append it to the* current parse environment.* Returns false when the buffer is empty, or when there is an error.** This function is called repeatedly until the entire document is* parsed.** This parser is most similar to a recursive descent parser. Single* functions represent discrete grammatical rules for the language, and* they are able to capture the text that represents those rules.** Consider the function lessc::keyword(). (all parse functions are* structured the same)** The function takes a single reference argument. When calling the* function it will attempt to match a keyword on the head of the buffer.* If it is successful, it will place the keyword in the referenced* argument, advance the position in the buffer, and return true. If it* fails then it won't advance the buffer and it will return false.** All of these parse functions are powered by lessc::match(), which behaves* the same way, but takes a literal regular expression. Sometimes it is* more convenient to use match instead of creating a new function.** Because of the format of the functions, to parse an entire string of* grammatical rules, you can chain them together using &&.** But, if some of the rules in the chain succeed before one fails, then* the buffer position will be left at an invalid state. In order to* avoid this, lessc::seek() is used to remember and set buffer positions.** Before parsing a chain, use $s = $this->seek() to remember the current* position into $s. Then if a chain fails, use $this->seek($s) to* go back where we started.*/protected function parseChunk() {if (empty($this->buffer)) return false;$s = $this->seek();// setting a propertyif ($this->keyword($key) && $this->assign() &&$this->propertyValue($value, $key) && $this->end()){$this->append(array('assign', $key, $value), $s);return true;} else {$this->seek($s);}// look for special css blocksif ($this->literal('@', false)) {$this->count--;// mediaif ($this->literal('@media')) {if (($this->mediaQueryList($mediaQueries) || true)&& $this->literal('{')){$media = $this->pushSpecialBlock("media");$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;return true;} else {$this->seek($s);return false;}}if ($this->literal("@", false) && $this->keyword($dirName)) {if ($this->isDirective($dirName, $this->blockDirectives)) {if (($this->openString("{", $dirValue, null, array(";")) || true) &&$this->literal("{")){$dir = $this->pushSpecialBlock("directive");$dir->name = $dirName;if (isset($dirValue)) $dir->value = $dirValue;return true;}} elseif ($this->isDirective($dirName, $this->lineDirectives)) {if ($this->propertyValue($dirValue) && $this->end()) {$this->append(array("directive", $dirName, $dirValue));return true;}}}$this->seek($s);}// setting a variableif ($this->variable($var) && $this->assign() &&$this->propertyValue($value) && $this->end()){$this->append(array('assign', $var, $value), $s);return true;} else {$this->seek($s);}if ($this->import($importValue)) {$this->append($importValue, $s);return true;}// opening parametric mixinif ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&($this->guards($guards) || true) &&$this->literal('{')){$block = $this->pushBlock($this->fixTags(array($tag)));$block->args = $args;$block->isVararg = $isVararg;if (!empty($guards)) $block->guards = $guards;return true;} else {$this->seek($s);}// opening a simple blockif ($this->tags($tags) && $this->literal('{')) {$tags = $this->fixTags($tags);$this->pushBlock($tags);return true;} else {$this->seek($s);}// closing a blockif ($this->literal('}', false)) {try {$block = $this->pop();} catch (exception $e) {$this->seek($s);$this->throwError($e->getMessage());}$hidden = false;if (is_null($block->type)) {$hidden = true;if (!isset($block->args)) {foreach ($block->tags as $tag) {if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {$hidden = false;break;}}}foreach ($block->tags as $tag) {if (is_string($tag)) {$this->env->children[$tag][] = $block;}}}if (!$hidden) {$this->append(array('block', $block), $s);}// this is done here so comments aren't bundled into he block that// was just closed$this->whitespace();return true;}// mixinif ($this->mixinTags($tags) &&($this->argumentDef($argv, $isVararg) || true) &&($this->keyword($suffix) || true) && $this->end()){$tags = $this->fixTags($tags);$this->append(array('mixin', $tags, $argv, $suffix), $s);return true;} else {$this->seek($s);}// spare ;if ($this->literal(';')) return true;return false; // got nothing, throw error}protected function isDirective($dirname, $directives) {// TODO: cache pattern in parser$pattern = implode("|",array_map(array("lessc", "preg_quote"), $directives));$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';return preg_match($pattern, $dirname);}protected function fixTags($tags) {// move @ tags out of variable namespaceforeach ($tags as &$tag) {if ($tag{0} == $this->lessc->vPrefix)$tag[0] = $this->lessc->mPrefix;}return $tags;}// a list of expressionsprotected function expressionList(&$exps) {$values = array();while ($this->expression($exp)) {$values[] = $exp;}if (count($values) == 0) return false;$exps = lessc::compressList($values, ' ');return true;}/*** Attempt to consume an expression.* @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code*/protected function expression(&$out) {if ($this->value($lhs)) {$out = $this->expHelper($lhs, 0);// look for / shorthandif (!empty($this->env->supressedDivision)) {unset($this->env->supressedDivision);$s = $this->seek();if ($this->literal("/") && $this->value($rhs)) {$out = array("list", "",array($out, array("keyword", "/"), $rhs));} else {$this->seek($s);}}return true;}return false;}/*** recursively parse infix equation with $lhs at precedence $minP*/protected function expHelper($lhs, $minP) {$this->inExp = true;$ss = $this->seek();while (true) {$whiteBefore = isset($this->buffer[$this->count - 1]) &&ctype_space($this->buffer[$this->count - 1]);// If there is whitespace before the operator, then we require// whitespace after the operator for it to be an expression$needWhite = $whiteBefore && !$this->inParens;if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {foreach (self::$supressDivisionProps as $pattern) {if (preg_match($pattern, $this->env->currentProperty)) {$this->env->supressedDivision = true;break 2;}}}$whiteAfter = isset($this->buffer[$this->count - 1]) &&ctype_space($this->buffer[$this->count - 1]);if (!$this->value($rhs)) break;// peek for next operator to see what to do with rhsif ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);}$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);$ss = $this->seek();continue;}break;}$this->seek($ss);return $lhs;}// consume a list of values for a propertypublic function propertyValue(&$value, $keyName = null) {$values = array();if ($keyName !== null) $this->env->currentProperty = $keyName;$s = null;while ($this->expressionList($v)) {$values[] = $v;$s = $this->seek();if (!$this->literal(',')) break;}if ($s) $this->seek($s);if ($keyName !== null) unset($this->env->currentProperty);if (count($values) == 0) return false;$value = lessc::compressList($values, ', ');return true;}protected function parenValue(&$out) {$s = $this->seek();// speed shortcutif (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {return false;}$inParens = $this->inParens;if ($this->literal("(") &&($this->inParens = true) && $this->expression($exp) &&$this->literal(")")){$out = $exp;$this->inParens = $inParens;return true;} else {$this->inParens = $inParens;$this->seek($s);}return false;}// a single valueprotected function value(&$value) {$s = $this->seek();// speed shortcutif (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {// negationif ($this->literal("-", false) &&(($this->variable($inner) && $inner = array("variable", $inner)) ||$this->unit($inner) ||$this->parenValue($inner))){$value = array("unary", "-", $inner);return true;} else {$this->seek($s);}}if ($this->parenValue($value)) return true;if ($this->unit($value)) return true;if ($this->color($value)) return true;if ($this->func($value)) return true;if ($this->string($value)) return true;if ($this->keyword($word)) {$value = array('keyword', $word);return true;}// try a variableif ($this->variable($var)) {$value = array('variable', $var);return true;}// unquote string (should this work on any type?if ($this->literal("~") && $this->string($str)) {$value = array("escape", $str);return true;} else {$this->seek($s);}// css hack: \0if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {$value = array('keyword', '\\'.$m[1]);return true;} else {$this->seek($s);}return false;}// an import statementprotected function import(&$out) {$s = $this->seek();if (!$this->literal('@import')) return false;// @import "something.css" media;// @import url("something.css") media;// @import url(something.css) media;if ($this->propertyValue($value)) {$out = array("import", $value);return true;}}protected function mediaQueryList(&$out) {if ($this->genericList($list, "mediaQuery", ",", false)) {$out = $list[2];return true;}return false;}protected function mediaQuery(&$out) {$s = $this->seek();$expressions = null;$parts = array();if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {$prop = array("mediaType");if (isset($only)) $prop[] = "only";if (isset($not)) $prop[] = "not";$prop[] = $mediaType;$parts[] = $prop;} else {$this->seek($s);}if (!empty($mediaType) && !$this->literal("and")) {// ~} else {$this->genericList($expressions, "mediaExpression", "and", false);if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);}if (count($parts) == 0) {$this->seek($s);return false;}$out = $parts;return true;}protected function mediaExpression(&$out) {$s = $this->seek();$value = null;if ($this->literal("(") &&$this->keyword($feature) &&($this->literal(":") && $this->expression($value) || true) &&$this->literal(")")){$out = array("mediaExp", $feature);if ($value) $out[] = $value;return true;} elseif ($this->variable($variable)) {$out = array('variable', $variable);return true;}$this->seek($s);return false;}// an unbounded string stopped by $endprotected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {$oldWhite = $this->eatWhiteDefault;$this->eatWhiteDefault = false;$stop = array("'", '"', "@{", $end);$stop = array_map(array("lessc", "preg_quote"), $stop);// $stop[] = self::$commentMulti;if (!is_null($rejectStrs)) {$stop = array_merge($stop, $rejectStrs);}$patt = '(.*?)('.implode("|", $stop).')';$nestingLevel = 0;$content = array();while ($this->match($patt, $m, false)) {if (!empty($m[1])) {$content[] = $m[1];if ($nestingOpen) {$nestingLevel += substr_count($m[1], $nestingOpen);}}$tok = $m[2];$this->count-= strlen($tok);if ($tok == $end) {if ($nestingLevel == 0) {break;} else {$nestingLevel--;}}if (($tok == "'" || $tok == '"') && $this->string($str)) {$content[] = $str;continue;}if ($tok == "@{" && $this->interpolation($inter)) {$content[] = $inter;continue;}if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {break;}$content[] = $tok;$this->count+= strlen($tok);}$this->eatWhiteDefault = $oldWhite;if (count($content) == 0) return false;// trim the endif (is_string(end($content))) {$content[count($content) - 1] = rtrim(end($content));}$out = array("string", "", $content);return true;}protected function string(&$out) {$s = $this->seek();if ($this->literal('"', false)) {$delim = '"';} elseif ($this->literal("'", false)) {$delim = "'";} else {return false;}$content = array();// look for either ending delim , escape, or string interpolation$patt = '([^\n]*?)(@\{|\\\\|' .lessc::preg_quote($delim).')';$oldWhite = $this->eatWhiteDefault;$this->eatWhiteDefault = false;while ($this->match($patt, $m, false)) {$content[] = $m[1];if ($m[2] == "@{") {$this->count -= strlen($m[2]);if ($this->interpolation($inter, false)) {$content[] = $inter;} else {$this->count += strlen($m[2]);$content[] = "@{"; // ignore it}} elseif ($m[2] == '\\') {$content[] = $m[2];if ($this->literal($delim, false)) {$content[] = $delim;}} else {$this->count -= strlen($delim);break; // delim}}$this->eatWhiteDefault = $oldWhite;if ($this->literal($delim)) {$out = array("string", $delim, $content);return true;}$this->seek($s);return false;}protected function interpolation(&$out) {$oldWhite = $this->eatWhiteDefault;$this->eatWhiteDefault = true;$s = $this->seek();if ($this->literal("@{") &&$this->openString("}", $interp, null, array("'", '"', ";")) &&$this->literal("}", false)){$out = array("interpolate", $interp);$this->eatWhiteDefault = $oldWhite;if ($this->eatWhiteDefault) $this->whitespace();return true;}$this->eatWhiteDefault = $oldWhite;$this->seek($s);return false;}protected function unit(&$unit) {// speed shortcutif (isset($this->buffer[$this->count])) {$char = $this->buffer[$this->count];if (!ctype_digit($char) && $char != ".") return false;}if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);return true;}return false;}// a # colorprotected function color(&$out) {if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {if (strlen($m[1]) > 7) {$out = array("string", "", array($m[1]));} else {$out = array("raw_color", $m[1]);}return true;}return false;}// consume an argument definition list surrounded by ()// each argument is a variable name with optional value// or at the end a ... or a variable named followed by ...// arguments are separated by , unless a ; is in the list, then ; is the// delimiter.protected function argumentDef(&$args, &$isVararg) {$s = $this->seek();if (!$this->literal('(')) return false;$values = array();$delim = ",";$method = "expressionList";$isVararg = false;while (true) {if ($this->literal("...")) {$isVararg = true;break;}if ($this->$method($value)) {if ($value[0] == "variable") {$arg = array("arg", $value[1]);$ss = $this->seek();if ($this->assign() && $this->$method($rhs)) {$arg[] = $rhs;} else {$this->seek($ss);if ($this->literal("...")) {$arg[0] = "rest";$isVararg = true;}}$values[] = $arg;if ($isVararg) break;continue;} else {$values[] = array("lit", $value);}}if (!$this->literal($delim)) {if ($delim == "," && $this->literal(";")) {// found new delim, convert existing args$delim = ";";$method = "propertyValue";// transform arg listif (isset($values[1])) { // 2 items$newList = array();foreach ($values as $i => $arg) {switch($arg[0]) {case "arg":if ($i) {$this->throwError("Cannot mix ; and , as delimiter types");}$newList[] = $arg[2];break;case "lit":$newList[] = $arg[1];break;case "rest":$this->throwError("Unexpected rest before semicolon");}}$newList = array("list", ", ", $newList);switch ($values[0][0]) {case "arg":$newArg = array("arg", $values[0][1], $newList);break;case "lit":$newArg = array("lit", $newList);break;}} elseif ($values) { // 1 item$newArg = $values[0];}if ($newArg) {$values = array($newArg);}} else {break;}}}if (!$this->literal(')')) {$this->seek($s);return false;}$args = $values;return true;}// consume a list of tags// this accepts a hanging delimiterprotected function tags(&$tags, $simple = false, $delim = ',') {$tags = array();while ($this->tag($tt, $simple)) {$tags[] = $tt;if (!$this->literal($delim)) break;}if (count($tags) == 0) return false;return true;}// list of tags of specifying mixin path// optionally separated by > (lazy, accepts extra >)protected function mixinTags(&$tags) {$s = $this->seek();$tags = array();while ($this->tag($tt, true)) {$tags[] = $tt;$this->literal(">");}if (count($tags) == 0) return false;return true;}// a bracketed value (contained within in a tag definition)protected function tagBracket(&$parts, &$hasExpression) {// speed shortcutif (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {return false;}$s = $this->seek();$hasInterpolation = false;if ($this->literal("[", false)) {$attrParts = array("[");// keyword, string, operatorwhile (true) {if ($this->literal("]", false)) {$this->count--;break; // get out early}if ($this->match('\s+', $m)) {$attrParts[] = " ";continue;}if ($this->string($str)) {// escape parent selector, (yuck)foreach ($str[2] as &$chunk) {$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);}$attrParts[] = $str;$hasInterpolation = true;continue;}if ($this->keyword($word)) {$attrParts[] = $word;continue;}if ($this->interpolation($inter, false)) {$attrParts[] = $inter;$hasInterpolation = true;continue;}// operator, handles attr namespace tooif ($this->match('[|-~\$\*\^=]+', $m)) {$attrParts[] = $m[0];continue;}break;}if ($this->literal("]", false)) {$attrParts[] = "]";foreach ($attrParts as $part) {$parts[] = $part;}$hasExpression = $hasExpression || $hasInterpolation;return true;}$this->seek($s);}$this->seek($s);return false;}// a space separated list of selectorsprotected function tag(&$tag, $simple = false) {if ($simple)$chars = '^@,:;{}\][>\(\) "\'';else$chars = '^@,;{}["\'';$s = $this->seek();$hasExpression = false;$parts = array();while ($this->tagBracket($parts, $hasExpression));$oldWhite = $this->eatWhiteDefault;$this->eatWhiteDefault = false;while (true) {if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {$parts[] = $m[1];if ($simple) break;while ($this->tagBracket($parts, $hasExpression));continue;}if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {if ($this->interpolation($interp)) {$hasExpression = true;$interp[2] = true; // don't unescape$parts[] = $interp;continue;}if ($this->literal("@")) {$parts[] = "@";continue;}}if ($this->unit($unit)) { // for keyframes$parts[] = $unit[1];$parts[] = $unit[2];continue;}break;}$this->eatWhiteDefault = $oldWhite;if (!$parts) {$this->seek($s);return false;}if ($hasExpression) {$tag = array("exp", array("string", "", $parts));} else {$tag = trim(implode($parts));}$this->whitespace();return true;}// a css functionprotected function func(&$func) {$s = $this->seek();if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {$fname = $m[1];$sPreArgs = $this->seek();$args = array();while (true) {$ss = $this->seek();// this ugly nonsense is for ie filter propertiesif ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {$args[] = array("string", "", array($name, "=", $value));} else {$this->seek($ss);if ($this->expressionList($value)) {$args[] = $value;}}if (!$this->literal(',')) break;}$args = array('list', ',', $args);if ($this->literal(')')) {$func = array('function', $fname, $args);return true;} elseif ($fname == 'url') {// couldn't parse and in url? treat as string$this->seek($sPreArgs);if ($this->openString(")", $string) && $this->literal(")")) {$func = array('function', $fname, $string);return true;}}}$this->seek($s);return false;}// consume a less variableprotected function variable(&$name) {$s = $this->seek();if ($this->literal($this->lessc->vPrefix, false) &&($this->variable($sub) || $this->keyword($name))){if (!empty($sub)) {$name = array('variable', $sub);} else {$name = $this->lessc->vPrefix.$name;}return true;}$name = null;$this->seek($s);return false;}/*** Consume an assignment operator* Can optionally take a name that will be set to the current property name*/protected function assign($name = null) {if ($name) $this->currentProperty = $name;return $this->literal(':') || $this->literal('=');}// consume a keywordprotected function keyword(&$word) {if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {$word = $m[1];return true;}return false;}// consume an end of statement delimiterprotected function end() {if ($this->literal(';')) {return true;} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {// if there is end of file or a closing block next then we don't need a ;return true;}return false;}protected function guards(&$guards) {$s = $this->seek();if (!$this->literal("when")) {$this->seek($s);return false;}$guards = array();while ($this->guardGroup($g)) {$guards[] = $g;if (!$this->literal(",")) break;}if (count($guards) == 0) {$guards = null;$this->seek($s);return false;}return true;}// a bunch of guards that are and'd together// TODO rename to guardGroupprotected function guardGroup(&$guardGroup) {$s = $this->seek();$guardGroup = array();while ($this->guard($guard)) {$guardGroup[] = $guard;if (!$this->literal("and")) break;}if (count($guardGroup) == 0) {$guardGroup = null;$this->seek($s);return false;}return true;}protected function guard(&$guard) {$s = $this->seek();$negate = $this->literal("not");if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {$guard = $exp;if ($negate) $guard = array("negate", $guard);return true;}$this->seek($s);return false;}/* raw parsing functions */protected function literal($what, $eatWhitespace = null) {if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;// shortcut on single letterif (!isset($what[1]) && isset($this->buffer[$this->count])) {if ($this->buffer[$this->count] == $what) {if (!$eatWhitespace) {$this->count++;return true;}// goes below...} else {return false;}}if (!isset(self::$literalCache[$what])) {self::$literalCache[$what] = lessc::preg_quote($what);}return $this->match(self::$literalCache[$what], $m, $eatWhitespace);}protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {$s = $this->seek();$items = array();while ($this->$parseItem($value)) {$items[] = $value;if ($delim) {if (!$this->literal($delim)) break;}}if (count($items) == 0) {$this->seek($s);return false;}if ($flatten && count($items) == 1) {$out = $items[0];} else {$out = array("list", $delim, $items);}return true;}// advance counter to next occurrence of $what// $until - don't include $what in advance// $allowNewline, if string, will be used as valid char setprotected function to($what, &$out, $until = false, $allowNewline = false) {if (is_string($allowNewline)) {$validChars = $allowNewline;} else {$validChars = $allowNewline ? "." : "[^\n]";}if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;if ($until) $this->count -= strlen($what); // give back $what$out = $m[1];return true;}// try to match something on head of bufferprotected function match($regex, &$out, $eatWhitespace = null) {if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';if (preg_match($r, $this->buffer, $out, null, $this->count)) {$this->count += strlen($out[0]);if ($eatWhitespace && $this->writeComments) $this->whitespace();return true;}return false;}// match some whitespaceprotected function whitespace() {if ($this->writeComments) {$gotWhite = false;while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {$this->append(array("comment", $m[1]));$this->commentsSeen[$this->count] = true;}$this->count += strlen($m[0]);$gotWhite = true;}return $gotWhite;} else {$this->match("", $m);return strlen($m[0]) > 0;}}// match something without consuming itprotected function peek($regex, &$out = null, $from=null) {if (is_null($from)) $from = $this->count;$r = '/'.$regex.'/Ais';$result = preg_match($r, $this->buffer, $out, null, $from);return $result;}// seek to a spot in the buffer or return where we are on no argumentprotected function seek($where = null) {if ($where === null) return $this->count;else $this->count = $where;return true;}/* misc functions */public function throwError($msg = "parse error", $count = null) {$count = is_null($count) ? $this->count : $count;$line = $this->line +substr_count(substr($this->buffer, 0, $count), "\n");if (!empty($this->sourceName)) {$loc = "$this->sourceName on line $line";} else {$loc = "line: $line";}// TODO this depends on $this->countif ($this->peek("(.*?)(\n|$)", $m, $count)) {throw new exception("$msg: failed at `$m[1]` $loc");} else {throw new exception("$msg: $loc");}}protected function pushBlock($selectors=null, $type=null) {$b = new stdclass;$b->parent = $this->env;$b->type = $type;$b->id = self::$nextBlockId++;$b->isVararg = false; // TODO: kill me from here$b->tags = $selectors;$b->props = array();$b->children = array();$this->env = $b;return $b;}// push a block that doesn't multiply tagsprotected function pushSpecialBlock($type) {return $this->pushBlock(null, $type);}// append a property to the current blockprotected function append($prop, $pos = null) {if ($pos !== null) $prop[-1] = $pos;$this->env->props[] = $prop;}// pop something off the stackprotected function pop() {$old = $this->env;$this->env = $this->env->parent;return $old;}// remove comments from $text// todo: make it work for all functions, not just urlprotected function removeComments($text) {$look = array('url(', '//', '/*', '"', "'");$out = '';$min = null;while (true) {// find the next itemforeach ($look as $token) {$pos = strpos($text, $token);if ($pos !== false) {if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);}}if (is_null($min)) break;$count = $min[1];$skip = 0;$newlines = 0;switch ($min[0]) {case 'url(':if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))$count += strlen($m[0]) - strlen($min[0]);break;case '"':case "'":if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))$count += strlen($m[0]) - 1;break;case '//':$skip = strpos($text, "\n", $count);if ($skip === false) $skip = strlen($text) - $count;else $skip -= $count;break;case '/*':if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {$skip = strlen($m[0]);$newlines = substr_count($m[0], "\n");}break;}if ($skip == 0) $count += strlen($min[0]);$out .= substr($text, 0, $count).str_repeat("\n", $newlines);$text = substr($text, $count + $skip);$min = null;}return $out.$text;}}class lessc_formatter_classic {public $indentChar = " ";public $break = "\n";public $open = " {";public $close = "}";public $selectorSeparator = ", ";public $assignSeparator = ":";public $openSingle = " { ";public $closeSingle = " }";public $disableSingle = false;public $breakSelectors = false;public $compressColors = false;public function __construct() {$this->indentLevel = 0;}public function indentStr($n = 0) {return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));}public function property($name, $value) {return $name . $this->assignSeparator . $value . ";";}protected function isEmpty($block) {if (empty($block->lines)) {foreach ($block->children as $child) {if (!$this->isEmpty($child)) return false;}return true;}return false;}public function block($block) {if ($this->isEmpty($block)) return;$inner = $pre = $this->indentStr();$isSingle = !$this->disableSingle &&is_null($block->type) && count($block->lines) == 1;if (!empty($block->selectors)) {$this->indentLevel++;if ($this->breakSelectors) {$selectorSeparator = $this->selectorSeparator . $this->break . $pre;} else {$selectorSeparator = $this->selectorSeparator;}echo $pre .implode($selectorSeparator, $block->selectors);if ($isSingle) {echo $this->openSingle;$inner = "";} else {echo $this->open . $this->break;$inner = $this->indentStr();}}if (!empty($block->lines)) {$glue = $this->break.$inner;echo $inner . implode($glue, $block->lines);if (!$isSingle && !empty($block->children)) {echo $this->break;}}foreach ($block->children as $child) {$this->block($child);}if (!empty($block->selectors)) {if (!$isSingle && empty($block->children)) echo $this->break;if ($isSingle) {echo $this->closeSingle . $this->break;} else {echo $pre . $this->close . $this->break;}$this->indentLevel--;}}}class lessc_formatter_compressed extends lessc_formatter_classic {public $disableSingle = true;public $open = "{";public $selectorSeparator = ",";public $assignSeparator = ":";public $break = "";public $compressColors = true;public function indentStr($n = 0) {return "";}}class lessc_formatter_lessjs extends lessc_formatter_classic {public $disableSingle = true;public $breakSelectors = true;public $assignSeparator = ": ";public $selectorSeparator = ",";}