1<?php
2
3/**
4 * lessphp v0.5.2
5 * http://leafo.net/lessphp
6 *
7 * LESS CSS compiler, adapted from http://lesscss.org
8 *
9 * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
10 * Copyright 2016, Marcus Schwarz <github@maswaba.de>
11 * Licensed under MIT or GPLv3, see LICENSE
12 */
13
14
15/**
16 * The LESS compiler and parser.
17 *
18 * Converting LESS to CSS is a three stage process. The incoming file is parsed
19 * by `lessc_parser` into a syntax tree, then it is compiled into another tree
20 * representing the CSS structure by `lessc`. The CSS tree is fed into a
21 * formatter, like `lessc_formatter` which then outputs CSS as a string.
22 *
23 * During the first compile, all values are *reduced*, which means that their
24 * types are brought to the lowest form before being dump as strings. This
25 * handles math equations, variable dereferences, and the like.
26 *
27 * The `parse` function of `lessc` is the entry point.
28 *
29 * In summary:
30 *
31 * The `lessc` class creates an instance of the parser, feeds it LESS code,
32 * then transforms the resulting tree to a CSS tree. This class also holds the
33 * evaluation context, such as all available mixins and variables at any given
34 * time.
35 *
36 * The `lessc_parser` class is only concerned with parsing its input.
37 *
38 * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
39 * handling things like indentation.
40 */
41class lessc {
42	static public $VERSION = "v0.5.2";
43
44	static public $TRUE = array("keyword", "true");
45	static public $FALSE = array("keyword", "false");
46
47	protected $libFunctions = array();
48	protected $registeredVars = array();
49	protected $preserveComments = false;
50
51	public $vPrefix = '@'; // prefix of abstract properties
52	public $mPrefix = '$'; // prefix of abstract blocks
53	public $parentSelector = '&';
54
55	static public $lengths = array( "px", "m", "cm", "mm", "in", "pt", "pc" );
56	static public $times = array( "s", "ms" );
57	static public $angles = array( "rad", "deg", "grad", "turn" );
58
59	static public $lengths_to_base = array( 1, 3779.52755906, 37.79527559, 3.77952756, 96, 1.33333333, 16  );
60	public $importDisabled = false;
61	public $importDir = array();
62
63	protected $numberPrecision = null;
64
65	protected $allParsedFiles = array();
66
67	// set to the parser that generated the current line when compiling
68	// so we know how to create error messages
69	protected $sourceParser = null;
70	protected $sourceLoc = null;
71
72	static protected $nextImportId = 0; // uniquely identify imports
73
74	// attempts to find the path of an import url, returns null for css files
75	protected function findImport($url) {
76		foreach ((array)$this->importDir as $dir) {
77			$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
78			if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
79				return $file;
80			}
81		}
82
83		return null;
84	}
85
86	protected function fileExists($name) {
87		return is_file($name);
88	}
89
90	static public function compressList($items, $delim) {
91		if (!isset($items[1]) && isset($items[0])) return $items[0];
92		else return array('list', $delim, $items);
93	}
94
95	static public function preg_quote($what) {
96		return preg_quote($what, '/');
97	}
98
99	protected function tryImport($importPath, $parentBlock, $out) {
100		if ($importPath[0] == "function" && $importPath[1] == "url") {
101			$importPath = $this->flattenList($importPath[2]);
102		}
103
104		$str = $this->coerceString($importPath);
105		if ($str === null) return false;
106
107		$url = $this->compileValue($this->lib_e($str));
108
109		// don't import if it ends in css
110		if (substr_compare($url, '.css', -4, 4) === 0) return false;
111
112		$realPath = $this->findImport($url);
113
114		if ($realPath === null) return false;
115
116		if ($this->importDisabled) {
117			return array(false, "/* import disabled */");
118		}
119
120		if (isset($this->allParsedFiles[realpath($realPath)])) {
121			return array(false, null);
122		}
123
124		$this->addParsedFile($realPath);
125		$parser = $this->makeParser($realPath);
126		$root = $parser->parse(file_get_contents($realPath));
127
128		// set the parents of all the block props
129		foreach ($root->props as $prop) {
130			if ($prop[0] == "block") {
131				$prop[1]->parent = $parentBlock;
132			}
133		}
134
135		// copy mixins into scope, set their parents
136		// bring blocks from import into current block
137		// TODO: need to mark the source parser	these came from this file
138		foreach ($root->children as $childName => $child) {
139			if (isset($parentBlock->children[$childName])) {
140				$parentBlock->children[$childName] = array_merge(
141					$parentBlock->children[$childName],
142					$child);
143			} else {
144				$parentBlock->children[$childName] = $child;
145			}
146		}
147
148		$pi = pathinfo($realPath);
149		$dir = $pi["dirname"];
150
151		list($top, $bottom) = $this->sortProps($root->props, true);
152		$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
153
154		return array(true, $bottom, $parser, $dir);
155	}
156
157	protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
158		$oldSourceParser = $this->sourceParser;
159
160		$oldImport = $this->importDir;
161
162		// TODO: this is because the importDir api is stupid
163		$this->importDir = (array)$this->importDir;
164		array_unshift($this->importDir, $importDir);
165
166		foreach ($props as $prop) {
167			$this->compileProp($prop, $block, $out);
168		}
169
170		$this->importDir = $oldImport;
171		$this->sourceParser = $oldSourceParser;
172	}
173
174	/**
175	 * Recursively compiles a block.
176	 *
177	 * A block is analogous to a CSS block in most cases. A single LESS document
178	 * is encapsulated in a block when parsed, but it does not have parent tags
179	 * so all of it's children appear on the root level when compiled.
180	 *
181	 * Blocks are made up of props and children.
182	 *
183	 * Props are property instructions, array tuples which describe an action
184	 * to be taken, eg. write a property, set a variable, mixin a block.
185	 *
186	 * The children of a block are just all the blocks that are defined within.
187	 * This is used to look up mixins when performing a mixin.
188	 *
189	 * Compiling the block involves pushing a fresh environment on the stack,
190	 * and iterating through the props, compiling each one.
191	 *
192	 * See lessc::compileProp()
193	 *
194	 */
195	protected function compileBlock($block) {
196		switch ($block->type) {
197		case "root":
198			$this->compileRoot($block);
199			break;
200		case null:
201			$this->compileCSSBlock($block);
202			break;
203		case "media":
204			$this->compileMedia($block);
205			break;
206		case "directive":
207			$name = "@" . $block->name;
208			if (!empty($block->value)) {
209				$name .= " " . $this->compileValue($this->reduce($block->value));
210			}
211
212			$this->compileNestedBlock($block, array($name));
213			break;
214		default:
215            $block->parser->throwError("unknown block type: $block->type\n", $block->count);
216		}
217	}
218
219	protected function compileCSSBlock($block) {
220		$env = $this->pushEnv();
221
222		$selectors = $this->compileSelectors($block->tags);
223		$env->selectors = $this->multiplySelectors($selectors);
224		$out = $this->makeOutputBlock(null, $env->selectors);
225
226		$this->scope->children[] = $out;
227		$this->compileProps($block, $out);
228
229		$block->scope = $env; // mixins carry scope with them!
230		$this->popEnv();
231	}
232
233	protected function compileMedia($media) {
234		$env = $this->pushEnv($media);
235		$parentScope = $this->mediaParent($this->scope);
236
237		$query = $this->compileMediaQuery($this->multiplyMedia($env));
238
239		$this->scope = $this->makeOutputBlock($media->type, array($query));
240		$parentScope->children[] = $this->scope;
241
242		$this->compileProps($media, $this->scope);
243
244		if (count($this->scope->lines) > 0) {
245			$orphanSelelectors = $this->findClosestSelectors();
246			if (!is_null($orphanSelelectors)) {
247				$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
248				$orphan->lines = $this->scope->lines;
249				array_unshift($this->scope->children, $orphan);
250				$this->scope->lines = array();
251			}
252		}
253
254		$this->scope = $this->scope->parent;
255		$this->popEnv();
256	}
257
258	protected function mediaParent($scope) {
259		while (!empty($scope->parent)) {
260			if (!empty($scope->type) && $scope->type != "media") {
261				break;
262			}
263			$scope = $scope->parent;
264		}
265
266		return $scope;
267	}
268
269	protected function compileNestedBlock($block, $selectors) {
270		$this->pushEnv($block);
271		$this->scope = $this->makeOutputBlock($block->type, $selectors);
272		$this->scope->parent->children[] = $this->scope;
273
274		$this->compileProps($block, $this->scope);
275
276		$this->scope = $this->scope->parent;
277		$this->popEnv();
278	}
279
280	protected function compileRoot($root) {
281		$this->pushEnv();
282		$this->scope = $this->makeOutputBlock($root->type);
283		$this->compileProps($root, $this->scope);
284		$this->popEnv();
285	}
286
287	protected function compileProps($block, $out) {
288		foreach ($this->sortProps($block->props) as $prop) {
289			$this->compileProp($prop, $block, $out);
290		}
291		$out->lines = $this->deduplicate($out->lines);
292	}
293
294	/**
295	 * Deduplicate lines in a block. Comments are not deduplicated. If a
296	 * duplicate rule is detected, the comments immediately preceding each
297	 * occurence are consolidated.
298	 */
299	protected function deduplicate($lines) {
300		$unique = array();
301		$comments = array();
302
303		foreach($lines as $line) {
304			if (strpos($line, '/*') === 0) {
305				$comments[] = $line;
306				continue;
307			}
308			if (!in_array($line, $unique)) {
309				$unique[] = $line;
310			}
311			array_splice($unique, array_search($line, $unique), 0, $comments);
312			$comments = array();
313		}
314		return array_merge($unique, $comments);
315	}
316
317	protected function sortProps($props, $split = false) {
318		$vars = array();
319		$imports = array();
320		$other = array();
321		$stack = array();
322
323		foreach ($props as $prop) {
324			switch ($prop[0]) {
325			case "comment":
326				$stack[] = $prop;
327				break;
328			case "assign":
329				$stack[] = $prop;
330				if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
331					$vars = array_merge($vars, $stack);
332				} else {
333					$other = array_merge($other, $stack);
334				}
335				$stack = array();
336				break;
337			case "import":
338				$id = self::$nextImportId++;
339				$prop[] = $id;
340				$stack[] = $prop;
341				$imports = array_merge($imports, $stack);
342				$other[] = array("import_mixin", $id);
343				$stack = array();
344				break;
345			default:
346				$stack[] = $prop;
347				$other = array_merge($other, $stack);
348				$stack = array();
349				break;
350			}
351		}
352		$other = array_merge($other, $stack);
353
354		if ($split) {
355			return array(array_merge($vars, $imports, $vars), $other);
356		} else {
357			return array_merge($vars, $imports, $vars, $other);
358		}
359	}
360
361	protected function compileMediaQuery($queries) {
362		$compiledQueries = array();
363		foreach ($queries as $query) {
364			$parts = array();
365			foreach ($query as $q) {
366				switch ($q[0]) {
367				case "mediaType":
368					$parts[] = implode(" ", array_slice($q, 1));
369					break;
370				case "mediaExp":
371					if (isset($q[2])) {
372						$parts[] = "($q[1]: " .
373							$this->compileValue($this->reduce($q[2])) . ")";
374					} else {
375						$parts[] = "($q[1])";
376					}
377					break;
378				case "variable":
379					$parts[] = $this->compileValue($this->reduce($q));
380				break;
381				}
382			}
383
384			if (count($parts) > 0) {
385				$compiledQueries[] =  implode(" and ", $parts);
386			}
387		}
388
389		$out = "@media";
390		if (!empty($parts)) {
391			$out .= " " .
392				implode($this->formatter->selectorSeparator, $compiledQueries);
393		}
394		return $out;
395	}
396
397	protected function multiplyMedia($env, $childQueries = null) {
398		if (is_null($env) ||
399			!empty($env->block->type) && $env->block->type != "media")
400		{
401			return $childQueries;
402		}
403
404		// plain old block, skip
405		if (empty($env->block->type)) {
406			return $this->multiplyMedia($env->parent, $childQueries);
407		}
408
409		$out = array();
410		$queries = $env->block->queries;
411		if (is_null($childQueries)) {
412			$out = $queries;
413		} else {
414			foreach ($queries as $parent) {
415				foreach ($childQueries as $child) {
416					$out[] = array_merge($parent, $child);
417				}
418			}
419		}
420
421		return $this->multiplyMedia($env->parent, $out);
422	}
423
424	protected function expandParentSelectors(&$tag, $replace) {
425		$parts = explode("$&$", $tag);
426		$count = 0;
427		foreach ($parts as &$part) {
428			$part = str_replace($this->parentSelector, $replace, $part, $c);
429			$count += $c;
430		}
431		$tag = implode($this->parentSelector, $parts);
432		return $count;
433	}
434
435	protected function findClosestSelectors() {
436		$env = $this->env;
437		$selectors = null;
438		while ($env !== null) {
439			if (isset($env->selectors)) {
440				$selectors = $env->selectors;
441				break;
442			}
443			$env = $env->parent;
444		}
445
446		return $selectors;
447	}
448
449
450	// multiply $selectors against the nearest selectors in env
451	protected function multiplySelectors($selectors) {
452		// find parent selectors
453
454		$parentSelectors = $this->findClosestSelectors();
455		if (is_null($parentSelectors)) {
456			// kill parent reference in top level selector
457			foreach ($selectors as &$s) {
458				$this->expandParentSelectors($s, "");
459			}
460
461			return $selectors;
462		}
463
464		$out = array();
465		foreach ($parentSelectors as $parent) {
466			foreach ($selectors as $child) {
467				$count = $this->expandParentSelectors($child, $parent);
468
469				// don't prepend the parent tag if & was used
470				if ($count > 0) {
471					$out[] = trim($child);
472				} else {
473					$out[] = trim($parent . ' ' . $child);
474				}
475			}
476		}
477
478		return $out;
479	}
480
481	// reduces selector expressions
482	protected function compileSelectors($selectors) {
483		$out = array();
484
485		foreach ($selectors as $s) {
486			if (is_array($s)) {
487				list(, $value) = $s;
488				$out[] = trim($this->compileValue($this->reduce($value)));
489			} else {
490				$out[] = $s;
491			}
492		}
493
494		return $out;
495	}
496
497	protected function eq($left, $right) {
498		return $left == $right;
499	}
500
501	protected function patternMatch($block, $orderedArgs, $keywordArgs) {
502		// match the guards if it has them
503		// any one of the groups must have all its guards pass for a match
504		if (!empty($block->guards)) {
505			$groupPassed = false;
506			foreach ($block->guards as $guardGroup) {
507				foreach ($guardGroup as $guard) {
508					$this->pushEnv();
509					$this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
510
511					$negate = false;
512					if ($guard[0] == "negate") {
513						$guard = $guard[1];
514						$negate = true;
515					}
516
517					$passed = $this->reduce($guard) == self::$TRUE;
518					if ($negate) $passed = !$passed;
519
520					$this->popEnv();
521
522					if ($passed) {
523						$groupPassed = true;
524					} else {
525						$groupPassed = false;
526						break;
527					}
528				}
529
530				if ($groupPassed) break;
531			}
532
533			if (!$groupPassed) {
534				return false;
535			}
536		}
537
538		if (empty($block->args)) {
539			return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
540		}
541
542		$remainingArgs = $block->args;
543		if ($keywordArgs) {
544			$remainingArgs = array();
545			foreach ($block->args as $arg) {
546				if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
547					continue;
548				}
549
550				$remainingArgs[] = $arg;
551			}
552		}
553
554		$i = -1; // no args
555		// try to match by arity or by argument literal
556		foreach ($remainingArgs as $i => $arg) {
557			switch ($arg[0]) {
558			case "lit":
559				if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
560					return false;
561				}
562				break;
563			case "arg":
564				// no arg and no default value
565				if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
566					return false;
567				}
568				break;
569			case "rest":
570				$i--; // rest can be empty
571				break 2;
572			}
573		}
574
575		if ($block->isVararg) {
576			return true; // not having enough is handled above
577		} else {
578			$numMatched = $i + 1;
579			// greater than because default values always match
580			return $numMatched >= count($orderedArgs);
581		}
582	}
583
584	protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
585		$matches = null;
586		foreach ($blocks as $block) {
587			// skip seen blocks that don't have arguments
588			if (isset($skip[$block->id]) && !isset($block->args)) {
589				continue;
590			}
591
592			if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
593				$matches[] = $block;
594			}
595		}
596
597		return $matches;
598	}
599
600	// attempt to find blocks matched by path and args
601	protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
602		if ($searchIn == null) return null;
603		if (isset($seen[$searchIn->id])) return null;
604		$seen[$searchIn->id] = true;
605
606		$name = $path[0];
607
608		if (isset($searchIn->children[$name])) {
609			$blocks = $searchIn->children[$name];
610			if (count($path) == 1) {
611				$matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
612				if (!empty($matches)) {
613					// This will return all blocks that match in the closest
614					// scope that has any matching block, like lessjs
615					return $matches;
616				}
617			} else {
618				$matches = array();
619				foreach ($blocks as $subBlock) {
620					$subMatches = $this->findBlocks($subBlock,
621						array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
622
623					if (!is_null($subMatches)) {
624						foreach ($subMatches as $sm) {
625							$matches[] = $sm;
626						}
627					}
628				}
629
630				return count($matches) > 0 ? $matches : null;
631			}
632		}
633		if ($searchIn->parent === $searchIn) return null;
634		return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
635	}
636
637	// sets all argument names in $args to either the default value
638	// or the one passed in through $values
639	protected function zipSetArgs($args, $orderedValues, $keywordValues) {
640		$assignedValues = array();
641
642		$i = 0;
643		foreach ($args as  $a) {
644			if ($a[0] == "arg") {
645				if (isset($keywordValues[$a[1]])) {
646					// has keyword arg
647					$value = $keywordValues[$a[1]];
648				} elseif (isset($orderedValues[$i])) {
649					// has ordered arg
650					$value = $orderedValues[$i];
651					$i++;
652				} elseif (isset($a[2])) {
653					// has default value
654					$value = $a[2];
655				} else {
656					$this->throwError("Failed to assign arg " . $a[1]);
657					$value = null; // :(
658				}
659
660				$value = $this->reduce($value);
661				$this->set($a[1], $value);
662				$assignedValues[] = $value;
663			} else {
664				// a lit
665				$i++;
666			}
667		}
668
669		// check for a rest
670		$last = end($args);
671        if ($last !== false && $last[0] === "rest") {
672			$rest = array_slice($orderedValues, count($args) - 1);
673			$this->set($last[1], $this->reduce(array("list", " ", $rest)));
674		}
675
676		// wow is this the only true use of PHP's + operator for arrays?
677		$this->env->arguments = $assignedValues + $orderedValues;
678	}
679
680	// compile a prop and update $lines or $blocks appropriately
681	protected function compileProp($prop, $block, $out) {
682		// set error position context
683		$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
684
685		switch ($prop[0]) {
686		case 'assign':
687			list(, $name, $value) = $prop;
688			if ($name[0] == $this->vPrefix) {
689				$this->set($name, $value);
690			} else {
691				$out->lines[] = $this->formatter->property($name,
692						$this->compileValue($this->reduce($value)));
693			}
694			break;
695		case 'block':
696			list(, $child) = $prop;
697			$this->compileBlock($child);
698			break;
699		case 'ruleset':
700		case 'mixin':
701			list(, $path, $args, $suffix) = $prop;
702
703			$orderedArgs = array();
704			$keywordArgs = array();
705			foreach ((array)$args as $arg) {
706				$argval = null;
707				switch ($arg[0]) {
708				case "arg":
709					if (!isset($arg[2])) {
710						$orderedArgs[] = $this->reduce(array("variable", $arg[1]));
711					} else {
712						$keywordArgs[$arg[1]] = $this->reduce($arg[2]);
713					}
714					break;
715
716				case "lit":
717					$orderedArgs[] = $this->reduce($arg[1]);
718					break;
719				default:
720					$this->throwError("Unknown arg type: " . $arg[0]);
721				}
722			}
723
724			$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
725
726			if ($mixins === null) {
727                $block->parser->throwError("{$prop[1][0]} is undefined", $block->count);
728			}
729
730			if(strpos($prop[1][0], "$") === 0) {
731				//Use Ruleset Logic - Only last element
732				$mixins = array(array_pop($mixins));
733			}
734
735			foreach ($mixins as $mixin) {
736				if ($mixin === $block && !$orderedArgs) {
737					continue;
738				}
739
740				$haveScope = false;
741				if (isset($mixin->parent->scope)) {
742					$haveScope = true;
743					$mixinParentEnv = $this->pushEnv();
744					$mixinParentEnv->storeParent = $mixin->parent->scope;
745				}
746
747				$haveArgs = false;
748				if (isset($mixin->args)) {
749					$haveArgs = true;
750					$this->pushEnv();
751					$this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
752				}
753
754				$oldParent = $mixin->parent;
755				if ($mixin != $block) $mixin->parent = $block;
756
757				foreach ($this->sortProps($mixin->props) as $subProp) {
758					if ($suffix !== null &&
759						$subProp[0] == "assign" &&
760						is_string($subProp[1]) &&
761						$subProp[1][0] != $this->vPrefix)
762					{
763						$subProp[2] = array(
764							'list', ' ',
765							array($subProp[2], array('keyword', $suffix))
766						);
767					}
768
769					$this->compileProp($subProp, $mixin, $out);
770				}
771
772				$mixin->parent = $oldParent;
773
774				if ($haveArgs) $this->popEnv();
775				if ($haveScope) $this->popEnv();
776			}
777
778			break;
779		case 'raw':
780			$out->lines[] = $prop[1];
781			break;
782		case "directive":
783			list(, $name, $value) = $prop;
784			$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
785			break;
786		case "comment":
787			$out->lines[] = $prop[1];
788			break;
789		case "import";
790			list(, $importPath, $importId) = $prop;
791			$importPath = $this->reduce($importPath);
792
793			if (!isset($this->env->imports)) {
794				$this->env->imports = array();
795			}
796
797			$result = $this->tryImport($importPath, $block, $out);
798
799			$this->env->imports[$importId] = $result === false ?
800				array(false, "@import " . $this->compileValue($importPath).";") :
801				$result;
802
803			break;
804		case "import_mixin":
805			list(,$importId) = $prop;
806			$import = $this->env->imports[$importId];
807			if ($import[0] === false) {
808				if (isset($import[1])) {
809					$out->lines[] = $import[1];
810				}
811			} else {
812				list(, $bottom, $parser, $importDir) = $import;
813				$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
814			}
815
816			break;
817		default:
818            $block->parser->throwError("unknown op: {$prop[0]}\n", $block->count);
819		}
820	}
821
822
823	/**
824	 * Compiles a primitive value into a CSS property value.
825	 *
826	 * Values in lessphp are typed by being wrapped in arrays, their format is
827	 * typically:
828	 *
829	 *     array(type, contents [, additional_contents]*)
830	 *
831	 * The input is expected to be reduced. This function will not work on
832	 * things like expressions and variables.
833	 */
834	public function compileValue($value) {
835		switch ($value[0]) {
836		case 'list':
837			// [1] - delimiter
838			// [2] - array of values
839			return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
840		case 'raw_color':
841			if (!empty($this->formatter->compressColors)) {
842				return $this->compileValue($this->coerceColor($value));
843			}
844			return $value[1];
845		case 'keyword':
846			// [1] - the keyword
847			return $value[1];
848		case 'number':
849			list(, $num, $unit) = $value;
850			// [1] - the number
851			// [2] - the unit
852			if ($this->numberPrecision !== null) {
853				$num = round($num, $this->numberPrecision);
854			}
855			return $num . $unit;
856		case 'string':
857			// [1] - contents of string (includes quotes)
858			list(, $delim, $content) = $value;
859			foreach ($content as &$part) {
860				if (is_array($part)) {
861					$part = $this->compileValue($part);
862				}
863			}
864			return $delim . implode($content) . $delim;
865		case 'color':
866			// [1] - red component (either number or a %)
867			// [2] - green component
868			// [3] - blue component
869			// [4] - optional alpha component
870			list(, $r, $g, $b) = $value;
871			$r = round($r);
872			$g = round($g);
873			$b = round($b);
874
875			if (count($value) == 5 && $value[4] != 1) { // rgba
876				return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
877			}
878
879			$h = sprintf("#%02x%02x%02x", $r, $g, $b);
880
881			if (!empty($this->formatter->compressColors)) {
882				// Converting hex color to short notation (e.g. #003399 to #039)
883				if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
884					$h = '#' . $h[1] . $h[3] . $h[5];
885				}
886			}
887
888			return $h;
889
890		case 'function':
891			list(, $name, $args) = $value;
892			return $name.'('.$this->compileValue($args).')';
893		default: // assumed to be unit
894			$this->throwError("unknown value type: $value[0]");
895		}
896	}
897
898	protected function lib_pow($args) {
899		list($base, $exp) = $this->assertArgs($args, 2, "pow");
900        return array( "number", pow($this->assertNumber($base), $this->assertNumber($exp)), $args[2][0][2] );
901	}
902
903	protected function lib_pi() {
904		return pi();
905	}
906
907	protected function lib_mod($args) {
908		list($a, $b) = $this->assertArgs($args, 2, "mod");
909        return array( "number", $this->assertNumber($a) % $this->assertNumber($b), $args[2][0][2] );
910 	}
911
912	protected function lib_convert($args) {
913        list($value, $to) = $this->assertArgs($args, 2, "convert");
914
915        // If it's a keyword, grab the string version instead
916        if( is_array( $to ) && $to[0] == "keyword" )
917                $to = $to[1];
918
919 		return $this->convert( $value, $to );
920 	}
921
922	protected function lib_abs($num) {
923        return array( "number", abs($this->assertNumber($num)), $num[2] );
924 	}
925
926	protected function lib_min($args) {
927        $values = $this->assertMinArgs($args, 1, "min");
928
929        $first_format = $values[0][2];
930
931        $min_index = 0;
932        $min_value = $values[0][1];
933
934        for( $a = 0; $a < sizeof( $values ); $a++ )
935        {
936            $converted = $this->convert( $values[$a], $first_format );
937
938            if( $converted[1] < $min_value )
939            {
940                $min_index = $a;
941                $min_value = $values[$a][1];
942            }
943 		}
944
945 		return $values[ $min_index ];
946 	}
947
948	protected function lib_max($args) {
949        $values = $this->assertMinArgs($args, 1, "max");
950
951        $first_format = $values[0][2];
952
953        $max_index = 0;
954        $max_value = $values[0][1];
955
956        for( $a = 0; $a < sizeof( $values ); $a++ )
957        {
958            $converted = $this->convert( $values[$a], $first_format );
959
960            if( $converted[1] > $max_value )
961            {
962                $max_index = $a;
963                $max_value = $values[$a][1];
964            }
965 		}
966
967 		return $values[ $max_index ];
968    }
969
970	protected function lib_tan($num) {
971		return tan($this->assertNumber($num));
972	}
973
974	protected function lib_sin($num) {
975		return sin($this->assertNumber($num));
976	}
977
978	protected function lib_cos($num) {
979		return cos($this->assertNumber($num));
980	}
981
982	protected function lib_atan($num) {
983		$num = atan($this->assertNumber($num));
984		return array("number", $num, "rad");
985	}
986
987	protected function lib_asin($num) {
988		$num = asin($this->assertNumber($num));
989		return array("number", $num, "rad");
990	}
991
992	protected function lib_acos($num) {
993		$num = acos($this->assertNumber($num));
994		return array("number", $num, "rad");
995	}
996
997	protected function lib_sqrt($num) {
998		return sqrt($this->assertNumber($num));
999	}
1000
1001	protected function lib_extract($value) {
1002		list($list, $idx) = $this->assertArgs($value, 2, "extract");
1003		$idx = $this->assertNumber($idx);
1004		// 1 indexed
1005		if ($list[0] == "list" && isset($list[2][$idx - 1])) {
1006			return $list[2][$idx - 1];
1007		}
1008	}
1009
1010	protected function lib_isnumber($value) {
1011		return $this->toBool($value[0] == "number");
1012	}
1013
1014	protected function lib_isstring($value) {
1015		return $this->toBool($value[0] == "string");
1016	}
1017
1018	protected function lib_iscolor($value) {
1019		return $this->toBool($this->coerceColor($value));
1020	}
1021
1022	protected function lib_iskeyword($value) {
1023		return $this->toBool($value[0] == "keyword");
1024	}
1025
1026	protected function lib_ispixel($value) {
1027		return $this->toBool($value[0] == "number" && $value[2] == "px");
1028	}
1029
1030	protected function lib_ispercentage($value) {
1031		return $this->toBool($value[0] == "number" && $value[2] == "%");
1032	}
1033
1034	protected function lib_isem($value) {
1035		return $this->toBool($value[0] == "number" && $value[2] == "em");
1036	}
1037
1038	protected function lib_isrem($value) {
1039		return $this->toBool($value[0] == "number" && $value[2] == "rem");
1040	}
1041
1042	protected function lib_rgbahex($color) {
1043		$color = $this->coerceColor($color);
1044		if (is_null($color))
1045			$this->throwError("color expected for rgbahex");
1046
1047		return sprintf("#%02x%02x%02x%02x",
1048			isset($color[4]) ? $color[4]*255 : 255,
1049			$color[1],$color[2], $color[3]);
1050	}
1051
1052	protected function lib_argb($color){
1053		return $this->lib_rgbahex($color);
1054	}
1055
1056	/**
1057	 * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
1058	 *
1059	 * @param  array  $value either an argument list (two strings) or a single string
1060	 * @return string        formatted url(), either as a link or base64-encoded
1061	 */
1062	protected function lib_data_uri($value) {
1063		$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
1064		$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
1065
1066		$fullpath = $this->findImport($url);
1067
1068		if($fullpath && ($fsize = filesize($fullpath)) !== false) {
1069			// IE8 can't handle data uris larger than 32KB
1070			if($fsize/1024 < 32) {
1071				if(is_null($mime)) {
1072					if(class_exists('finfo')) { // php 5.3+
1073						$finfo = new finfo(FILEINFO_MIME);
1074						$mime = explode('; ', $finfo->file($fullpath));
1075						$mime = $mime[0];
1076					} elseif(function_exists('mime_content_type')) { // PHP 5.2
1077						$mime = mime_content_type($fullpath);
1078					}
1079				}
1080
1081				if(!is_null($mime)) // fallback if the mime type is still unknown
1082					$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
1083			}
1084		}
1085
1086		return 'url("'.$url.'")';
1087	}
1088
1089	// utility func to unquote a string
1090	protected function lib_e($arg) {
1091		switch ($arg[0]) {
1092			case "list":
1093				$items = $arg[2];
1094				if (isset($items[0])) {
1095					return $this->lib_e($items[0]);
1096				}
1097				$this->throwError("unrecognised input");
1098			case "string":
1099				$arg[1] = "";
1100				return $arg;
1101			case "keyword":
1102				return $arg;
1103			default:
1104				return array("keyword", $this->compileValue($arg));
1105		}
1106	}
1107
1108	protected function lib__sprintf($args) {
1109		if ($args[0] != "list") return $args;
1110		$values = $args[2];
1111		$string = array_shift($values);
1112		$template = $this->compileValue($this->lib_e($string));
1113
1114		$i = 0;
1115		if (preg_match_all('/%[dsa]/', $template, $m)) {
1116			foreach ($m[0] as $match) {
1117				$val = isset($values[$i]) ?
1118					$this->reduce($values[$i]) : array('keyword', '');
1119
1120				// lessjs compat, renders fully expanded color, not raw color
1121				if ($color = $this->coerceColor($val)) {
1122					$val = $color;
1123				}
1124
1125				$i++;
1126				$rep = $this->compileValue($this->lib_e($val));
1127				$template = preg_replace('/'.self::preg_quote($match).'/',
1128					$rep, $template, 1);
1129			}
1130		}
1131
1132		$d = $string[0] == "string" ? $string[1] : '"';
1133		return array("string", $d, array($template));
1134	}
1135
1136	protected function lib_floor($arg) {
1137		$value = $this->assertNumber($arg);
1138		return array("number", floor($value), $arg[2]);
1139	}
1140
1141	protected function lib_ceil($arg) {
1142		$value = $this->assertNumber($arg);
1143		return array("number", ceil($value), $arg[2]);
1144	}
1145
1146	protected function lib_round($arg) {
1147		if($arg[0] != "list") {
1148			$value = $this->assertNumber($arg);
1149			return array("number", round($value), $arg[2]);
1150		} else {
1151			$value = $this->assertNumber($arg[2][0]);
1152			$precision = $this->assertNumber($arg[2][1]);
1153			return array("number", round($value, $precision), $arg[2][0][2]);
1154		}
1155	}
1156
1157	protected function lib_unit($arg) {
1158		if ($arg[0] == "list") {
1159			list($number, $newUnit) = $arg[2];
1160			return array("number", $this->assertNumber($number),
1161				$this->compileValue($this->lib_e($newUnit)));
1162		} else {
1163			return array("number", $this->assertNumber($arg), "");
1164		}
1165	}
1166
1167	/**
1168	 * Helper function to get arguments for color manipulation functions.
1169	 * takes a list that contains a color like thing and a percentage
1170	 */
1171	public function colorArgs($args) {
1172		if ($args[0] != 'list' || count($args[2]) < 2) {
1173			return array(array('color', 0, 0, 0), 0);
1174		}
1175		list($color, $delta) = $args[2];
1176		$color = $this->assertColor($color);
1177		$delta = floatval($delta[1]);
1178
1179		return array($color, $delta);
1180	}
1181
1182	protected function lib_darken($args) {
1183		list($color, $delta) = $this->colorArgs($args);
1184
1185		$hsl = $this->toHSL($color);
1186		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1187		return $this->toRGB($hsl);
1188	}
1189
1190	protected function lib_lighten($args) {
1191		list($color, $delta) = $this->colorArgs($args);
1192
1193		$hsl = $this->toHSL($color);
1194		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1195		return $this->toRGB($hsl);
1196	}
1197
1198	protected function lib_saturate($args) {
1199		list($color, $delta) = $this->colorArgs($args);
1200
1201		$hsl = $this->toHSL($color);
1202		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1203		return $this->toRGB($hsl);
1204	}
1205
1206	protected function lib_desaturate($args) {
1207		list($color, $delta) = $this->colorArgs($args);
1208
1209		$hsl = $this->toHSL($color);
1210		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1211		return $this->toRGB($hsl);
1212	}
1213
1214	protected function lib_spin($args) {
1215		list($color, $delta) = $this->colorArgs($args);
1216
1217		$hsl = $this->toHSL($color);
1218
1219		$hsl[1] = $hsl[1] + $delta % 360;
1220		if ($hsl[1] < 0) $hsl[1] += 360;
1221
1222		return $this->toRGB($hsl);
1223	}
1224
1225	protected function lib_fadeout($args) {
1226		list($color, $delta) = $this->colorArgs($args);
1227		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
1228		return $color;
1229	}
1230
1231	protected function lib_fadein($args) {
1232		list($color, $delta) = $this->colorArgs($args);
1233		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
1234		return $color;
1235	}
1236
1237	protected function lib_hue($color) {
1238		$hsl = $this->toHSL($this->assertColor($color));
1239		return round($hsl[1]);
1240	}
1241
1242	protected function lib_saturation($color) {
1243		$hsl = $this->toHSL($this->assertColor($color));
1244		return round($hsl[2]);
1245	}
1246
1247	protected function lib_lightness($color) {
1248		$hsl = $this->toHSL($this->assertColor($color));
1249		return round($hsl[3]);
1250	}
1251
1252	// get the alpha of a color
1253	// defaults to 1 for non-colors or colors without an alpha
1254	protected function lib_alpha($value) {
1255		if (!is_null($color = $this->coerceColor($value))) {
1256			return isset($color[4]) ? $color[4] : 1;
1257		}
1258	}
1259
1260	// set the alpha of the color
1261	protected function lib_fade($args) {
1262		list($color, $alpha) = $this->colorArgs($args);
1263		$color[4] = $this->clamp($alpha / 100.0);
1264		return $color;
1265	}
1266
1267	protected function lib_percentage($arg) {
1268		$num = $this->assertNumber($arg);
1269		return array("number", $num*100, "%");
1270	}
1271
1272	// mixes two colors by weight
1273	// mix(@color1, @color2, [@weight: 50%]);
1274	// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
1275	protected function lib_mix($args) {
1276		if ($args[0] != "list" || count($args[2]) < 2)
1277			$this->throwError("mix expects (color1, color2, weight)");
1278
1279		list($first, $second) = $args[2];
1280		$first = $this->assertColor($first);
1281		$second = $this->assertColor($second);
1282
1283		$first_a = $this->lib_alpha($first);
1284		$second_a = $this->lib_alpha($second);
1285
1286		if (isset($args[2][2])) {
1287			$weight = $args[2][2][1] / 100.0;
1288		} else {
1289			$weight = 0.5;
1290		}
1291
1292		$w = $weight * 2 - 1;
1293		$a = $first_a - $second_a;
1294
1295		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1296		$w2 = 1.0 - $w1;
1297
1298		$new = array('color',
1299			$w1 * $first[1] + $w2 * $second[1],
1300			$w1 * $first[2] + $w2 * $second[2],
1301			$w1 * $first[3] + $w2 * $second[3],
1302		);
1303
1304		if ($first_a != 1.0 || $second_a != 1.0) {
1305			$new[] = $first_a * $weight + $second_a * ($weight - 1);
1306		}
1307
1308		return $this->fixColor($new);
1309	}
1310
1311	protected function lib_contrast($args) {
1312	    $darkColor  = array('color', 0, 0, 0);
1313	    $lightColor = array('color', 255, 255, 255);
1314	    $threshold  = 0.43;
1315
1316	    if ( $args[0] == 'list' ) {
1317	        $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
1318	        $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
1319	        $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
1320            if( isset($args[2][3]) ) {
1321                if( isset($args[2][3][2]) && $args[2][3][2] == '%' ) {
1322                    $args[2][3][1] /= 100;
1323                    unset($args[2][3][2]);
1324 	        	}
1325 		        $threshold = $this->assertNumber($args[2][3]);
1326 	        }
1327	    }
1328	    else {
1329	        $inputColor  = $this->assertColor($args);
1330	    }
1331
1332	    $inputColor = $this->coerceColor($inputColor);
1333	    $darkColor  = $this->coerceColor($darkColor);
1334	    $lightColor = $this->coerceColor($lightColor);
1335
1336	    //Figure out which is actually light and dark!
1337	    if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
1338	        $t  = $lightColor;
1339	        $lightColor = $darkColor;
1340	        $darkColor  = $t;
1341	    }
1342
1343	    $inputColor_alpha = $this->lib_alpha($inputColor);
1344	    if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
1345	        return $lightColor;
1346	    }
1347	    return $darkColor;
1348	}
1349
1350	protected function lib_luma($color) {
1351	    $color = $this->coerceColor($color);
1352	    return (0.2126 * $color[1] / 255) + (0.7152 * $color[2] / 255) + (0.0722 * $color[3] / 255);
1353	}
1354
1355
1356	public function assertColor($value, $error = "expected color value") {
1357		$color = $this->coerceColor($value);
1358		if (is_null($color)) $this->throwError($error);
1359		return $color;
1360	}
1361
1362	public function assertNumber($value, $error = "expecting number") {
1363		if ($value[0] == "number") return $value[1];
1364		$this->throwError($error);
1365	}
1366
1367	public function assertArgs($value, $expectedArgs, $name="") {
1368		if ($expectedArgs == 1) {
1369			return $value;
1370		} else {
1371			if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
1372			$values = $value[2];
1373			$numValues = count($values);
1374			if ($expectedArgs != $numValues) {
1375				if ($name) {
1376					$name = $name . ": ";
1377				}
1378
1379				$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
1380			}
1381
1382			return $values;
1383		}
1384	}
1385
1386	public function assertMinArgs($value, $expectedMinArgs, $name="") {
1387    	if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
1388 		$values = $value[2];
1389 		$numValues = count($values);
1390 		if ($expectedMinArgs > $numValues) {
1391        	if ($name) {
1392            	$name = $name . ": ";
1393        	}
1394
1395 			$this->throwError("${name}expecting at least $expectedMinArgs arguments, got $numValues");
1396 		}
1397
1398 		return $values;
1399}
1400
1401	protected function toHSL($color) {
1402		if ($color[0] == 'hsl') return $color;
1403
1404		$r = $color[1] / 255;
1405		$g = $color[2] / 255;
1406		$b = $color[3] / 255;
1407
1408		$min = min($r, $g, $b);
1409		$max = max($r, $g, $b);
1410
1411		$L = ($min + $max) / 2;
1412		if ($min == $max) {
1413			$S = $H = 0;
1414		} else {
1415			if ($L < 0.5)
1416				$S = ($max - $min)/($max + $min);
1417			else
1418				$S = ($max - $min)/(2.0 - $max - $min);
1419
1420			if ($r == $max) $H = ($g - $b)/($max - $min);
1421			elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1422			elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1423
1424		}
1425
1426		$out = array('hsl',
1427			($H < 0 ? $H + 6 : $H)*60,
1428			$S*100,
1429			$L*100,
1430		);
1431
1432		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1433		return $out;
1434	}
1435
1436	protected function toRGB_helper($comp, $temp1, $temp2) {
1437		if ($comp < 0) $comp += 1.0;
1438		elseif ($comp > 1) $comp -= 1.0;
1439
1440		if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1441		if (2 * $comp < 1) return $temp2;
1442		if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
1443
1444		return $temp1;
1445	}
1446
1447	/**
1448	 * Converts a hsl array into a color value in rgb.
1449	 * Expects H to be in range of 0 to 360, S and L in 0 to 100
1450	 */
1451	protected function toRGB($color) {
1452		if ($color[0] == 'color') return $color;
1453
1454		$H = $color[1] / 360;
1455		$S = $color[2] / 100;
1456		$L = $color[3] / 100;
1457
1458		if ($S == 0) {
1459			$r = $g = $b = $L;
1460		} else {
1461			$temp2 = $L < 0.5 ?
1462				$L*(1.0 + $S) :
1463				$L + $S - $L * $S;
1464
1465			$temp1 = 2.0 * $L - $temp2;
1466
1467			$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1468			$g = $this->toRGB_helper($H, $temp1, $temp2);
1469			$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1470		}
1471
1472		// $out = array('color', round($r*255), round($g*255), round($b*255));
1473		$out = array('color', $r*255, $g*255, $b*255);
1474		if (count($color) > 4) $out[] = $color[4]; // copy alpha
1475		return $out;
1476	}
1477
1478	protected function clamp($v, $max = 1, $min = 0) {
1479		return min($max, max($min, $v));
1480	}
1481
1482	/**
1483	 * Convert the rgb, rgba, hsl color literals of function type
1484	 * as returned by the parser into values of color type.
1485	 */
1486	protected function funcToColor($func) {
1487		$fname = $func[1];
1488		if ($func[2][0] != 'list') return false; // need a list of arguments
1489		$rawComponents = $func[2][2];
1490
1491		if ($fname == 'hsl' || $fname == 'hsla') {
1492			$hsl = array('hsl');
1493			$i = 0;
1494			foreach ($rawComponents as $c) {
1495				$val = $this->reduce($c);
1496				$val = isset($val[1]) ? floatval($val[1]) : 0;
1497
1498				if ($i == 0) $clamp = 360;
1499				elseif ($i < 3) $clamp = 100;
1500				else $clamp = 1;
1501
1502				$hsl[] = $this->clamp($val, $clamp);
1503				$i++;
1504			}
1505
1506			while (count($hsl) < 4) $hsl[] = 0;
1507			return $this->toRGB($hsl);
1508
1509		} elseif ($fname == 'rgb' || $fname == 'rgba') {
1510			$components = array();
1511			$i = 1;
1512			foreach	($rawComponents as $c) {
1513				$c = $this->reduce($c);
1514				if ($i < 4) {
1515					if ($c[0] == "number" && $c[2] == "%") {
1516						$components[] = 255 * ($c[1] / 100);
1517					} else {
1518						$components[] = floatval($c[1]);
1519					}
1520				} elseif ($i == 4) {
1521					if ($c[0] == "number" && $c[2] == "%") {
1522						$components[] = 1.0 * ($c[1] / 100);
1523					} else {
1524						$components[] = floatval($c[1]);
1525					}
1526				} else break;
1527
1528				$i++;
1529			}
1530			while (count($components) < 3) $components[] = 0;
1531			array_unshift($components, 'color');
1532			return $this->fixColor($components);
1533		}
1534
1535		return false;
1536	}
1537
1538	protected function reduce($value, $forExpression = false) {
1539		switch ($value[0]) {
1540		case "interpolate":
1541			$reduced = $this->reduce($value[1]);
1542			$var = $this->compileValue($reduced);
1543			$res = $this->reduce(array("variable", $this->vPrefix . $var));
1544
1545			if ($res[0] == "raw_color") {
1546				$res = $this->coerceColor($res);
1547			}
1548
1549			if (empty($value[2])) $res = $this->lib_e($res);
1550
1551			return $res;
1552		case "variable":
1553			$key = $value[1];
1554			if (is_array($key)) {
1555				$key = $this->reduce($key);
1556				$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
1557			}
1558
1559			$seen =& $this->env->seenNames;
1560
1561			if (!empty($seen[$key])) {
1562				$this->throwError("infinite loop detected: $key");
1563			}
1564
1565			$seen[$key] = true;
1566			$out = $this->reduce($this->get($key));
1567			$seen[$key] = false;
1568			return $out;
1569		case "list":
1570			foreach ($value[2] as &$item) {
1571				$item = $this->reduce($item, $forExpression);
1572			}
1573			return $value;
1574		case "expression":
1575			return $this->evaluate($value);
1576		case "string":
1577			foreach ($value[2] as &$part) {
1578				if (is_array($part)) {
1579					$strip = $part[0] == "variable";
1580					$part = $this->reduce($part);
1581					if ($strip) $part = $this->lib_e($part);
1582				}
1583			}
1584			return $value;
1585		case "escape":
1586			list(,$inner) = $value;
1587			return $this->lib_e($this->reduce($inner));
1588		case "function":
1589			$color = $this->funcToColor($value);
1590			if ($color) return $color;
1591
1592			list(, $name, $args) = $value;
1593			if ($name == "%") $name = "_sprintf";
1594
1595			$f = isset($this->libFunctions[$name]) ?
1596				$this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
1597
1598			if (is_callable($f)) {
1599				if ($args[0] == 'list')
1600					$args = self::compressList($args[2], $args[1]);
1601
1602				$ret = call_user_func($f, $this->reduce($args, true), $this);
1603
1604				if (is_null($ret)) {
1605					return array("string", "", array(
1606						$name, "(", $args, ")"
1607					));
1608				}
1609
1610				// convert to a typed value if the result is a php primitive
1611				if (is_numeric($ret)) $ret = array('number', $ret, "");
1612				elseif (!is_array($ret)) $ret = array('keyword', $ret);
1613
1614				return $ret;
1615			}
1616
1617			// plain function, reduce args
1618			$value[2] = $this->reduce($value[2]);
1619			return $value;
1620		case "unary":
1621			list(, $op, $exp) = $value;
1622			$exp = $this->reduce($exp);
1623
1624			if ($exp[0] == "number") {
1625				switch ($op) {
1626				case "+":
1627					return $exp;
1628				case "-":
1629					$exp[1] *= -1;
1630					return $exp;
1631				}
1632			}
1633			return array("string", "", array($op, $exp));
1634		}
1635
1636		if ($forExpression) {
1637			switch ($value[0]) {
1638			case "keyword":
1639				if ($color = $this->coerceColor($value)) {
1640					return $color;
1641				}
1642				break;
1643			case "raw_color":
1644				return $this->coerceColor($value);
1645			}
1646		}
1647
1648		return $value;
1649	}
1650
1651
1652	// coerce a value for use in color operation
1653	protected function coerceColor($value) {
1654		switch($value[0]) {
1655			case 'color': return $value;
1656			case 'raw_color':
1657				$c = array("color", 0, 0, 0);
1658				$colorStr = substr($value[1], 1);
1659				$num = hexdec($colorStr);
1660				$width = strlen($colorStr) == 3 ? 16 : 256;
1661
1662				for ($i = 3; $i > 0; $i--) { // 3 2 1
1663					$t = $num % $width;
1664					$num /= $width;
1665
1666					$c[$i] = $t * (256/$width) + $t * floor(16/$width);
1667				}
1668
1669				return $c;
1670			case 'keyword':
1671				$name = $value[1];
1672				if (isset(self::$cssColors[$name])) {
1673					$rgba = explode(',', self::$cssColors[$name]);
1674
1675					if(isset($rgba[3]))
1676						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
1677
1678					return array('color', $rgba[0], $rgba[1], $rgba[2]);
1679				}
1680				return null;
1681		}
1682	}
1683
1684	// make something string like into a string
1685	protected function coerceString($value) {
1686		switch ($value[0]) {
1687		case "string":
1688			return $value;
1689		case "keyword":
1690			return array("string", "", array($value[1]));
1691		}
1692		return null;
1693	}
1694
1695	// turn list of length 1 into value type
1696	protected function flattenList($value) {
1697		if ($value[0] == "list" && count($value[2]) == 1) {
1698			return $this->flattenList($value[2][0]);
1699		}
1700		return $value;
1701	}
1702
1703	public function toBool($a) {
1704		if ($a) return self::$TRUE;
1705		else return self::$FALSE;
1706	}
1707
1708	// evaluate an expression
1709	protected function evaluate($exp) {
1710		list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
1711
1712		$left = $this->reduce($left, true);
1713		$right = $this->reduce($right, true);
1714
1715		if ($leftColor = $this->coerceColor($left)) {
1716			$left = $leftColor;
1717		}
1718
1719		if ($rightColor = $this->coerceColor($right)) {
1720			$right = $rightColor;
1721		}
1722
1723		$ltype = $left[0];
1724		$rtype = $right[0];
1725
1726		// operators that work on all types
1727		if ($op == "and") {
1728			return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1729		}
1730
1731		if ($op == "=") {
1732			return $this->toBool($this->eq($left, $right) );
1733		}
1734
1735		if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
1736			return $str;
1737		}
1738
1739		// type based operators
1740		$fname = "op_${ltype}_${rtype}";
1741		if (is_callable(array($this, $fname))) {
1742			$out = $this->$fname($op, $left, $right);
1743			if (!is_null($out)) return $out;
1744		}
1745
1746		// make the expression look it did before being parsed
1747		$paddedOp = $op;
1748		if ($whiteBefore) $paddedOp = " " . $paddedOp;
1749		if ($whiteAfter) $paddedOp .= " ";
1750
1751		return array("string", "", array($left, $paddedOp, $right));
1752	}
1753
1754	protected function stringConcatenate($left, $right) {
1755		if ($strLeft = $this->coerceString($left)) {
1756			if ($right[0] == "string") {
1757				$right[1] = "";
1758			}
1759			$strLeft[2][] = $right;
1760			return $strLeft;
1761		}
1762
1763		if ($strRight = $this->coerceString($right)) {
1764			array_unshift($strRight[2], $left);
1765			return $strRight;
1766		}
1767	}
1768
1769    protected function convert( $number, $to )
1770    {
1771        $value = $this->assertNumber( $number );
1772        $from = $number[2];
1773
1774        // easy out
1775        if( $from == $to )
1776            return $number;
1777
1778        // check if the from value is a length
1779        if( ( $from_index = array_search( $from, self::$lengths ) ) !== false ) {
1780            // make sure to value is too
1781            if( in_array( $to, self::$lengths ) ) {
1782                // do the actual conversion
1783                $to_index = array_search( $to, self::$lengths );
1784                $px = $value * self::$lengths_to_base[ $from_index ];
1785                $result = $px * ( 1 / self::$lengths_to_base[ $to_index ] );
1786
1787                $result = round( $result, 8 );
1788                return array( "number", $result, $to );
1789            }
1790        }
1791
1792        // do the same check for times
1793        if( in_array( $from, self::$times ) ) {
1794            if( in_array( $to, self::$times ) ) {
1795                // currently only ms and s are valid
1796                if( $to == "ms" )
1797                    $result = $value * 1000;
1798                else
1799                    $result = $value / 1000;
1800
1801                $result = round( $result, 8 );
1802                return array( "number", $result, $to );
1803            }
1804        }
1805
1806        // lastly check for an angle
1807        if( in_array( $from, self::$angles ) ) {
1808            // convert whatever angle it is into degrees
1809            if( $from == "rad" )
1810                $deg = rad2deg( $value );
1811
1812            else if( $from == "turn" )
1813                $deg = $value * 360;
1814
1815            else if( $from == "grad" )
1816                $deg = $value / (400 / 360);
1817
1818            else
1819                $deg = $value;
1820
1821            // Then convert it from degrees into desired unit
1822            if( $to == "deg" )
1823                $result = $deg;
1824
1825            if( $to == "rad" )
1826                $result = deg2rad( $deg );
1827
1828            if( $to == "turn" )
1829                $result = $value / 360;
1830
1831            if( $to == "grad" )
1832                $result = $value * (400 / 360);
1833
1834            $result = round( $result, 8 );
1835            return array( "number", $result, $to );
1836        }
1837
1838        // we don't know how to convert these
1839        $this->throwError( "Cannot convert {$from} to {$to}" );
1840    }
1841
1842	// make sure a color's components don't go out of bounds
1843	protected function fixColor($c) {
1844		foreach (range(1, 3) as $i) {
1845			if ($c[$i] < 0) $c[$i] = 0;
1846			if ($c[$i] > 255) $c[$i] = 255;
1847		}
1848
1849		return $c;
1850	}
1851
1852	protected function op_number_color($op, $lft, $rgt) {
1853		if ($op == '+' || $op == '*') {
1854			return $this->op_color_number($op, $rgt, $lft);
1855		}
1856	}
1857
1858	protected function op_color_number($op, $lft, $rgt) {
1859		if ($rgt[0] == '%') $rgt[1] /= 100;
1860
1861		return $this->op_color_color($op, $lft,
1862			array_fill(1, count($lft) - 1, $rgt[1]));
1863	}
1864
1865	protected function op_color_color($op, $left, $right) {
1866		$out = array('color');
1867		$max = count($left) > count($right) ? count($left) : count($right);
1868		foreach (range(1, $max - 1) as $i) {
1869			$lval = isset($left[$i]) ? $left[$i] : 0;
1870			$rval = isset($right[$i]) ? $right[$i] : 0;
1871			switch ($op) {
1872			case '+':
1873				$out[] = $lval + $rval;
1874				break;
1875			case '-':
1876				$out[] = $lval - $rval;
1877				break;
1878			case '*':
1879				$out[] = $lval * $rval;
1880				break;
1881			case '%':
1882				$out[] = $lval % $rval;
1883				break;
1884			case '/':
1885				if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
1886				$out[] = $lval / $rval;
1887				break;
1888			default:
1889				$this->throwError('evaluate error: color op number failed on op '.$op);
1890			}
1891		}
1892		return $this->fixColor($out);
1893	}
1894
1895	function lib_red($color){
1896		$color = $this->coerceColor($color);
1897		if (is_null($color)) {
1898			$this->throwError('color expected for red()');
1899		}
1900
1901		return $color[1];
1902	}
1903
1904	function lib_green($color){
1905		$color = $this->coerceColor($color);
1906		if (is_null($color)) {
1907			$this->throwError('color expected for green()');
1908		}
1909
1910		return $color[2];
1911	}
1912
1913	function lib_blue($color){
1914		$color = $this->coerceColor($color);
1915		if (is_null($color)) {
1916			$this->throwError('color expected for blue()');
1917		}
1918
1919		return $color[3];
1920	}
1921
1922
1923	// operator on two numbers
1924	protected function op_number_number($op, $left, $right) {
1925		$unit = empty($left[2]) ? $right[2] : $left[2];
1926
1927		$value = 0;
1928		switch ($op) {
1929		case '+':
1930			$value = $left[1] + $right[1];
1931			break;
1932		case '*':
1933			$value = $left[1] * $right[1];
1934			break;
1935		case '-':
1936			$value = $left[1] - $right[1];
1937			break;
1938		case '%':
1939			$value = $left[1] % $right[1];
1940			break;
1941		case '/':
1942			if ($right[1] == 0) $this->throwError('parse error: divide by zero');
1943			$value = $left[1] / $right[1];
1944			break;
1945		case '<':
1946			return $this->toBool($left[1] < $right[1]);
1947		case '>':
1948			return $this->toBool($left[1] > $right[1]);
1949		case '>=':
1950			return $this->toBool($left[1] >= $right[1]);
1951		case '=<':
1952			return $this->toBool($left[1] <= $right[1]);
1953		default:
1954			$this->throwError('parse error: unknown number operator: '.$op);
1955		}
1956
1957		return array("number", $value, $unit);
1958	}
1959
1960
1961	/* environment functions */
1962
1963	protected function makeOutputBlock($type, $selectors = null) {
1964		$b = new stdclass;
1965		$b->lines = array();
1966		$b->children = array();
1967		$b->selectors = $selectors;
1968		$b->type = $type;
1969		$b->parent = $this->scope;
1970		return $b;
1971	}
1972
1973	// the state of execution
1974	protected function pushEnv($block = null) {
1975		$e = new stdclass;
1976		$e->parent = $this->env;
1977		$e->store = array();
1978		$e->block = $block;
1979
1980		$this->env = $e;
1981		return $e;
1982	}
1983
1984	// pop something off the stack
1985	protected function popEnv() {
1986		$old = $this->env;
1987		$this->env = $this->env->parent;
1988		return $old;
1989	}
1990
1991	// set something in the current env
1992	protected function set($name, $value) {
1993		$this->env->store[$name] = $value;
1994	}
1995
1996
1997	// get the highest occurrence entry for a name
1998	protected function get($name) {
1999		$current = $this->env;
2000
2001        // track scope to evaluate
2002        $scope_secondary = array();
2003
2004        $isArguments = $name == $this->vPrefix . 'arguments';
2005		while ($current) {
2006			if ($isArguments && isset($current->arguments)) {
2007				return array('list', ' ', $current->arguments);
2008			}
2009
2010			if (isset($current->store[$name]))
2011				return $current->store[$name];
2012			// has secondary scope?
2013			if (isset($current->storeParent))
2014				$scope_secondary[] = $current->storeParent;
2015
2016			if (isset($current->parent))
2017				$current = $current->parent;
2018			else
2019				$current = null;
2020		}
2021
2022		while (count($scope_secondary)) {
2023            // pop one off
2024            $current = array_shift($scope_secondary);
2025            while ($current) {
2026                if ($isArguments && isset($current->arguments)) {
2027                    return array('list', ' ', $current->arguments);
2028                }
2029
2030                if (isset($current->store[$name])) {
2031                    return $current->store[$name];
2032                }
2033
2034                // has secondary scope?
2035                if (isset($current->storeParent)) {
2036                    $scope_secondary[] = $current->storeParent;
2037                }
2038
2039                if (isset($current->parent)) {
2040                    $current = $current->parent;
2041                } else {
2042                    $current = null;
2043                }
2044            }
2045        }
2046
2047		$this->throwError("variable $name is undefined");
2048	}
2049
2050	// inject array of unparsed strings into environment as variables
2051	protected function injectVariables($args) {
2052		$this->pushEnv();
2053		$parser = new lessc_parser($this, __METHOD__);
2054		foreach ($args as $name => $strValue) {
2055			if ($name[0] != '@') $name = '@'.$name;
2056			$parser->count = 0;
2057			$parser->buffer = (string)$strValue;
2058			if (!$parser->propertyValue($value)) {
2059				throw new \Exception("failed to parse passed in variable $name: $strValue");
2060			}
2061
2062			$this->set($name, $value);
2063		}
2064	}
2065
2066	/**
2067	 * Initialize any static state, can initialize parser for a file
2068	 * $opts isn't used yet
2069	 */
2070	public function __construct($fname = null) {
2071		if ($fname !== null) {
2072			// used for deprecated parse method
2073			$this->_parseFile = $fname;
2074		}
2075	}
2076
2077	public function compile($string, $name = null) {
2078		$locale = setlocale(LC_NUMERIC, 0);
2079		setlocale(LC_NUMERIC, "C");
2080
2081		$this->parser = $this->makeParser($name);
2082		$root = $this->parser->parse($string);
2083
2084		$this->env = null;
2085		$this->scope = null;
2086        $this->allParsedFiles = array();
2087
2088		$this->formatter = $this->newFormatter();
2089
2090		if (!empty($this->registeredVars)) {
2091			$this->injectVariables($this->registeredVars);
2092		}
2093
2094		$this->sourceParser = $this->parser; // used for error messages
2095		$this->compileBlock($root);
2096
2097		ob_start();
2098		$this->formatter->block($this->scope);
2099		$out = ob_get_clean();
2100		setlocale(LC_NUMERIC, $locale);
2101		return $out;
2102	}
2103
2104	public function compileFile($fname, $outFname = null) {
2105		if (!is_readable($fname)) {
2106			throw new \Exception('load error: failed to find '.$fname);
2107		}
2108
2109		$pi = pathinfo($fname);
2110
2111		$oldImport = $this->importDir;
2112
2113		$this->importDir = (array)$this->importDir;
2114		$this->importDir[] = $pi['dirname'].'/';
2115
2116		$this->addParsedFile($fname);
2117
2118		$out = $this->compile(file_get_contents($fname), $fname);
2119
2120		$this->importDir = $oldImport;
2121
2122		if ($outFname !== null) {
2123			return file_put_contents($outFname, $out);
2124		}
2125
2126		return $out;
2127	}
2128
2129    /**
2130     * Based on explicit input/output files does a full change check on cache before compiling.
2131     *
2132     * @param string $in
2133     * @param string $out
2134     * @param boolean $force
2135     * @return string Compiled CSS results
2136     * @throws Exception
2137     */
2138    public function checkedCachedCompile($in, $out, $force = false) {
2139        if (!is_file($in) || !is_readable($in)) {
2140            throw new Exception('Invalid or unreadable input file specified.');
2141        }
2142        if (is_dir($out) || !is_writable(file_exists($out) ? $out : dirname($out))) {
2143            throw new Exception('Invalid or unwritable output file specified.');
2144        }
2145
2146        $outMeta = $out . '.meta';
2147        $metadata = null;
2148        if (!$force && is_file($outMeta)) {
2149            $metadata = unserialize(file_get_contents($outMeta));
2150        }
2151
2152        $output = $this->cachedCompile($metadata ? $metadata : $in);
2153
2154        if (!$metadata || $metadata['updated'] != $output['updated']) {
2155            $css = $output['compiled'];
2156            unset($output['compiled']);
2157            file_put_contents($out, $css);
2158            file_put_contents($outMeta, serialize($output));
2159        } else {
2160            $css = file_get_contents($out);
2161        }
2162
2163        return $css;
2164    }
2165
2166	// compile only if changed input has changed or output doesn't exist
2167	public function checkedCompile($in, $out) {
2168		if (!is_file($out) || filemtime($in) > filemtime($out)) {
2169			$this->compileFile($in, $out);
2170			return true;
2171		}
2172		return false;
2173	}
2174
2175	/**
2176	 * Execute lessphp on a .less file or a lessphp cache structure
2177	 *
2178	 * The lessphp cache structure contains information about a specific
2179	 * less file having been parsed. It can be used as a hint for future
2180	 * calls to determine whether or not a rebuild is required.
2181	 *
2182	 * The cache structure contains two important keys that may be used
2183	 * externally:
2184	 *
2185	 * compiled: The final compiled CSS
2186	 * updated: The time (in seconds) the CSS was last compiled
2187	 *
2188	 * The cache structure is a plain-ol' PHP associative array and can
2189	 * be serialized and unserialized without a hitch.
2190	 *
2191	 * @param mixed $in Input
2192	 * @param bool $force Force rebuild?
2193	 * @return array lessphp cache structure
2194	 */
2195	public function cachedCompile($in, $force = false) {
2196		// assume no root
2197		$root = null;
2198
2199		if (is_string($in)) {
2200			$root = $in;
2201		} elseif (is_array($in) and isset($in['root'])) {
2202			if ($force or ! isset($in['files'])) {
2203				// If we are forcing a recompile or if for some reason the
2204				// structure does not contain any file information we should
2205				// specify the root to trigger a rebuild.
2206				$root = $in['root'];
2207			} elseif (isset($in['files']) and is_array($in['files'])) {
2208				foreach ($in['files'] as $fname => $ftime ) {
2209					if (!file_exists($fname) or filemtime($fname) > $ftime) {
2210						// One of the files we knew about previously has changed
2211						// so we should look at our incoming root again.
2212						$root = $in['root'];
2213						break;
2214					}
2215				}
2216			}
2217		} else {
2218			// TODO: Throw an exception? We got neither a string nor something
2219			// that looks like a compatible lessphp cache structure.
2220			return null;
2221		}
2222
2223		if ($root !== null) {
2224			// If we have a root value which means we should rebuild.
2225			$out = array();
2226			$out['root'] = $root;
2227			$out['compiled'] = $this->compileFile($root);
2228			$out['files'] = $this->allParsedFiles();
2229			$out['updated'] = time();
2230			return $out;
2231		} else {
2232			// No changes, pass back the structure
2233			// we were given initially.
2234			return $in;
2235		}
2236
2237	}
2238
2239	// parse and compile buffer
2240	// This is deprecated
2241	public function parse($str = null, $initialVariables = null) {
2242		if (is_array($str)) {
2243			$initialVariables = $str;
2244			$str = null;
2245		}
2246
2247		$oldVars = $this->registeredVars;
2248		if ($initialVariables !== null) {
2249			$this->setVariables($initialVariables);
2250		}
2251
2252		if ($str == null) {
2253			if (empty($this->_parseFile)) {
2254				throw new \Exception("nothing to parse");
2255			}
2256
2257			$out = $this->compileFile($this->_parseFile);
2258		} else {
2259			$out = $this->compile($str);
2260		}
2261
2262		$this->registeredVars = $oldVars;
2263		return $out;
2264	}
2265
2266	protected function makeParser($name) {
2267		$parser = new lessc_parser($this, $name);
2268		$parser->writeComments = $this->preserveComments;
2269
2270		return $parser;
2271	}
2272
2273	public function setFormatter($name) {
2274		$this->formatterName = $name;
2275	}
2276
2277	protected function newFormatter() {
2278		$className = "lessc_formatter_lessjs";
2279		if (!empty($this->formatterName)) {
2280			if (!is_string($this->formatterName))
2281				return $this->formatterName;
2282			$className = "lessc_formatter_$this->formatterName";
2283		}
2284
2285		return new $className;
2286	}
2287
2288	public function setPreserveComments($preserve) {
2289		$this->preserveComments = $preserve;
2290	}
2291
2292	public function registerFunction($name, $func) {
2293		$this->libFunctions[$name] = $func;
2294	}
2295
2296	public function unregisterFunction($name) {
2297		unset($this->libFunctions[$name]);
2298	}
2299
2300	public function setVariables($variables) {
2301		$this->registeredVars = array_merge($this->registeredVars, $variables);
2302	}
2303
2304	public function unsetVariable($name) {
2305		unset($this->registeredVars[$name]);
2306	}
2307
2308	public function setImportDir($dirs) {
2309		$this->importDir = (array)$dirs;
2310	}
2311
2312	public function addImportDir($dir) {
2313		$this->importDir = (array)$this->importDir;
2314		$this->importDir[] = $dir;
2315	}
2316
2317	public function allParsedFiles() {
2318		return $this->allParsedFiles;
2319	}
2320
2321	public function addParsedFile($file) {
2322		$this->allParsedFiles[realpath($file)] = filemtime($file);
2323	}
2324
2325	/**
2326	 * Uses the current value of $this->count to show line and line number
2327	 */
2328	public function throwError($msg = null) {
2329		if ($this->sourceLoc >= 0) {
2330			$this->sourceParser->throwError($msg, $this->sourceLoc);
2331		}
2332		throw new \Exception($msg);
2333	}
2334
2335	// compile file $in to file $out if $in is newer than $out
2336	// returns true when it compiles, false otherwise
2337	public static function ccompile($in, $out, $less = null) {
2338		if ($less === null) {
2339			$less = new self;
2340		}
2341		return $less->checkedCompile($in, $out);
2342	}
2343
2344	public static function cexecute($in, $force = false, $less = null) {
2345		if ($less === null) {
2346			$less = new self;
2347		}
2348		return $less->cachedCompile($in, $force);
2349	}
2350
2351	static protected $cssColors = array(
2352		'aliceblue' => '240,248,255',
2353		'antiquewhite' => '250,235,215',
2354		'aqua' => '0,255,255',
2355		'aquamarine' => '127,255,212',
2356		'azure' => '240,255,255',
2357		'beige' => '245,245,220',
2358		'bisque' => '255,228,196',
2359		'black' => '0,0,0',
2360		'blanchedalmond' => '255,235,205',
2361		'blue' => '0,0,255',
2362		'blueviolet' => '138,43,226',
2363		'brown' => '165,42,42',
2364		'burlywood' => '222,184,135',
2365		'cadetblue' => '95,158,160',
2366		'chartreuse' => '127,255,0',
2367		'chocolate' => '210,105,30',
2368		'coral' => '255,127,80',
2369		'cornflowerblue' => '100,149,237',
2370		'cornsilk' => '255,248,220',
2371		'crimson' => '220,20,60',
2372		'cyan' => '0,255,255',
2373		'darkblue' => '0,0,139',
2374		'darkcyan' => '0,139,139',
2375		'darkgoldenrod' => '184,134,11',
2376		'darkgray' => '169,169,169',
2377		'darkgreen' => '0,100,0',
2378		'darkgrey' => '169,169,169',
2379		'darkkhaki' => '189,183,107',
2380		'darkmagenta' => '139,0,139',
2381		'darkolivegreen' => '85,107,47',
2382		'darkorange' => '255,140,0',
2383		'darkorchid' => '153,50,204',
2384		'darkred' => '139,0,0',
2385		'darksalmon' => '233,150,122',
2386		'darkseagreen' => '143,188,143',
2387		'darkslateblue' => '72,61,139',
2388		'darkslategray' => '47,79,79',
2389		'darkslategrey' => '47,79,79',
2390		'darkturquoise' => '0,206,209',
2391		'darkviolet' => '148,0,211',
2392		'deeppink' => '255,20,147',
2393		'deepskyblue' => '0,191,255',
2394		'dimgray' => '105,105,105',
2395		'dimgrey' => '105,105,105',
2396		'dodgerblue' => '30,144,255',
2397		'firebrick' => '178,34,34',
2398		'floralwhite' => '255,250,240',
2399		'forestgreen' => '34,139,34',
2400		'fuchsia' => '255,0,255',
2401		'gainsboro' => '220,220,220',
2402		'ghostwhite' => '248,248,255',
2403		'gold' => '255,215,0',
2404		'goldenrod' => '218,165,32',
2405		'gray' => '128,128,128',
2406		'green' => '0,128,0',
2407		'greenyellow' => '173,255,47',
2408		'grey' => '128,128,128',
2409		'honeydew' => '240,255,240',
2410		'hotpink' => '255,105,180',
2411		'indianred' => '205,92,92',
2412		'indigo' => '75,0,130',
2413		'ivory' => '255,255,240',
2414		'khaki' => '240,230,140',
2415		'lavender' => '230,230,250',
2416		'lavenderblush' => '255,240,245',
2417		'lawngreen' => '124,252,0',
2418		'lemonchiffon' => '255,250,205',
2419		'lightblue' => '173,216,230',
2420		'lightcoral' => '240,128,128',
2421		'lightcyan' => '224,255,255',
2422		'lightgoldenrodyellow' => '250,250,210',
2423		'lightgray' => '211,211,211',
2424		'lightgreen' => '144,238,144',
2425		'lightgrey' => '211,211,211',
2426		'lightpink' => '255,182,193',
2427		'lightsalmon' => '255,160,122',
2428		'lightseagreen' => '32,178,170',
2429		'lightskyblue' => '135,206,250',
2430		'lightslategray' => '119,136,153',
2431		'lightslategrey' => '119,136,153',
2432		'lightsteelblue' => '176,196,222',
2433		'lightyellow' => '255,255,224',
2434		'lime' => '0,255,0',
2435		'limegreen' => '50,205,50',
2436		'linen' => '250,240,230',
2437		'magenta' => '255,0,255',
2438		'maroon' => '128,0,0',
2439		'mediumaquamarine' => '102,205,170',
2440		'mediumblue' => '0,0,205',
2441		'mediumorchid' => '186,85,211',
2442		'mediumpurple' => '147,112,219',
2443		'mediumseagreen' => '60,179,113',
2444		'mediumslateblue' => '123,104,238',
2445		'mediumspringgreen' => '0,250,154',
2446		'mediumturquoise' => '72,209,204',
2447		'mediumvioletred' => '199,21,133',
2448		'midnightblue' => '25,25,112',
2449		'mintcream' => '245,255,250',
2450		'mistyrose' => '255,228,225',
2451		'moccasin' => '255,228,181',
2452		'navajowhite' => '255,222,173',
2453		'navy' => '0,0,128',
2454		'oldlace' => '253,245,230',
2455		'olive' => '128,128,0',
2456		'olivedrab' => '107,142,35',
2457		'orange' => '255,165,0',
2458		'orangered' => '255,69,0',
2459		'orchid' => '218,112,214',
2460		'palegoldenrod' => '238,232,170',
2461		'palegreen' => '152,251,152',
2462		'paleturquoise' => '175,238,238',
2463		'palevioletred' => '219,112,147',
2464		'papayawhip' => '255,239,213',
2465		'peachpuff' => '255,218,185',
2466		'peru' => '205,133,63',
2467		'pink' => '255,192,203',
2468		'plum' => '221,160,221',
2469		'powderblue' => '176,224,230',
2470		'purple' => '128,0,128',
2471		'red' => '255,0,0',
2472		'rosybrown' => '188,143,143',
2473		'royalblue' => '65,105,225',
2474		'saddlebrown' => '139,69,19',
2475		'salmon' => '250,128,114',
2476		'sandybrown' => '244,164,96',
2477		'seagreen' => '46,139,87',
2478		'seashell' => '255,245,238',
2479		'sienna' => '160,82,45',
2480		'silver' => '192,192,192',
2481		'skyblue' => '135,206,235',
2482		'slateblue' => '106,90,205',
2483		'slategray' => '112,128,144',
2484		'slategrey' => '112,128,144',
2485		'snow' => '255,250,250',
2486		'springgreen' => '0,255,127',
2487		'steelblue' => '70,130,180',
2488		'tan' => '210,180,140',
2489		'teal' => '0,128,128',
2490		'thistle' => '216,191,216',
2491		'tomato' => '255,99,71',
2492		'transparent' => '0,0,0,0',
2493		'turquoise' => '64,224,208',
2494		'violet' => '238,130,238',
2495		'wheat' => '245,222,179',
2496		'white' => '255,255,255',
2497		'whitesmoke' => '245,245,245',
2498		'yellow' => '255,255,0',
2499		'yellowgreen' => '154,205,50'
2500	);
2501}
2502
2503// responsible for taking a string of LESS code and converting it into a
2504// syntax tree
2505class lessc_parser {
2506	static protected $nextBlockId = 0; // used to uniquely identify blocks
2507
2508	static protected $precedence = array(
2509		'=<' => 0,
2510		'>=' => 0,
2511		'=' => 0,
2512		'<' => 0,
2513		'>' => 0,
2514
2515		'+' => 1,
2516		'-' => 1,
2517		'*' => 2,
2518		'/' => 2,
2519		'%' => 2,
2520	);
2521
2522	static protected $whitePattern;
2523	static protected $commentMulti;
2524
2525	static protected $commentSingle = "//";
2526	static protected $commentMultiLeft = "/*";
2527	static protected $commentMultiRight = "*/";
2528
2529	// regex string to match any of the operators
2530	static protected $operatorString;
2531
2532	// these properties will supress division unless it's inside parenthases
2533	static protected $supressDivisionProps =
2534		array('/border-radius$/i', '/^font$/i');
2535
2536	protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
2537	protected $lineDirectives = array("charset");
2538
2539	/**
2540	 * if we are in parens we can be more liberal with whitespace around
2541	 * operators because it must evaluate to a single value and thus is less
2542	 * ambiguous.
2543	 *
2544	 * Consider:
2545	 *     property1: 10 -5; // is two numbers, 10 and -5
2546	 *     property2: (10 -5); // should evaluate to 5
2547	 */
2548	protected $inParens = false;
2549
2550	// caches preg escaped literals
2551	static protected $literalCache = array();
2552
2553	public function __construct($lessc, $sourceName = null) {
2554		$this->eatWhiteDefault = true;
2555		// reference to less needed for vPrefix, mPrefix, and parentSelector
2556		$this->lessc = $lessc;
2557
2558		$this->sourceName = $sourceName; // name used for error messages
2559
2560		$this->writeComments = false;
2561
2562		if (!self::$operatorString) {
2563			self::$operatorString =
2564				'('.implode('|', array_map(array('lessc', 'preg_quote'),
2565					array_keys(self::$precedence))).')';
2566
2567			$commentSingle = lessc::preg_quote(self::$commentSingle);
2568			$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
2569			$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
2570
2571			self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
2572			self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
2573		}
2574	}
2575
2576	public function parse($buffer) {
2577		$this->count = 0;
2578		$this->line = 1;
2579
2580		$this->env = null; // block stack
2581		$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
2582		$this->pushSpecialBlock("root");
2583		$this->eatWhiteDefault = true;
2584		$this->seenComments = array();
2585
2586		// trim whitespace on head
2587		// if (preg_match('/^\s+/', $this->buffer, $m)) {
2588		// 	$this->line += substr_count($m[0], "\n");
2589		// 	$this->buffer = ltrim($this->buffer);
2590		// }
2591		$this->whitespace();
2592
2593		// parse the entire file
2594		while (false !== $this->parseChunk());
2595
2596		if ($this->count != strlen($this->buffer))
2597			$this->throwError();
2598
2599		// TODO report where the block was opened
2600		if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
2601			throw new \Exception('parse error: unclosed block');
2602
2603		return $this->env;
2604	}
2605
2606	/**
2607	 * Parse a single chunk off the head of the buffer and append it to the
2608	 * current parse environment.
2609	 * Returns false when the buffer is empty, or when there is an error.
2610	 *
2611	 * This function is called repeatedly until the entire document is
2612	 * parsed.
2613	 *
2614	 * This parser is most similar to a recursive descent parser. Single
2615	 * functions represent discrete grammatical rules for the language, and
2616	 * they are able to capture the text that represents those rules.
2617	 *
2618	 * Consider the function lessc::keyword(). (all parse functions are
2619	 * structured the same)
2620	 *
2621	 * The function takes a single reference argument. When calling the
2622	 * function it will attempt to match a keyword on the head of the buffer.
2623	 * If it is successful, it will place the keyword in the referenced
2624	 * argument, advance the position in the buffer, and return true. If it
2625	 * fails then it won't advance the buffer and it will return false.
2626	 *
2627	 * All of these parse functions are powered by lessc::match(), which behaves
2628	 * the same way, but takes a literal regular expression. Sometimes it is
2629	 * more convenient to use match instead of creating a new function.
2630	 *
2631	 * Because of the format of the functions, to parse an entire string of
2632	 * grammatical rules, you can chain them together using &&.
2633	 *
2634	 * But, if some of the rules in the chain succeed before one fails, then
2635	 * the buffer position will be left at an invalid state. In order to
2636	 * avoid this, lessc::seek() is used to remember and set buffer positions.
2637	 *
2638	 * Before parsing a chain, use $s = $this->seek() to remember the current
2639	 * position into $s. Then if a chain fails, use $this->seek($s) to
2640	 * go back where we started.
2641	 */
2642	protected function parseChunk() {
2643		if (empty($this->buffer)) return false;
2644		$s = $this->seek();
2645
2646		if ($this->whitespace()) {
2647			return true;
2648		}
2649
2650		// setting a property
2651		if ($this->keyword($key) && $this->assign() &&
2652			$this->propertyValue($value, $key) && $this->end())
2653		{
2654			$this->append(array('assign', $key, $value), $s);
2655			return true;
2656		} else {
2657			$this->seek($s);
2658		}
2659
2660
2661		// look for special css blocks
2662		if ($this->literal('@', false)) {
2663			$this->count--;
2664
2665			// media
2666			if ($this->literal('@media')) {
2667				if (($this->mediaQueryList($mediaQueries) || true)
2668					&& $this->literal('{'))
2669				{
2670					$media = $this->pushSpecialBlock("media");
2671					$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
2672					return true;
2673				} else {
2674					$this->seek($s);
2675					return false;
2676				}
2677			}
2678
2679			if ($this->literal("@", false) && $this->keyword($dirName)) {
2680				if ($this->isDirective($dirName, $this->blockDirectives)) {
2681					if (($this->openString("{", $dirValue, null, array(";")) || true) &&
2682						$this->literal("{"))
2683					{
2684						$dir = $this->pushSpecialBlock("directive");
2685						$dir->name = $dirName;
2686						if (isset($dirValue)) $dir->value = $dirValue;
2687						return true;
2688					}
2689				} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
2690					if ($this->propertyValue($dirValue) && $this->end()) {
2691						$this->append(array("directive", $dirName, $dirValue));
2692						return true;
2693					}
2694				} elseif ($this->literal(":", true)) {
2695					//Ruleset Definition
2696					if (($this->openString("{", $dirValue, null, array(";")) || true) &&
2697							$this->literal("{"))
2698					{
2699						$dir = $this->pushBlock($this->fixTags(array("@".$dirName)));
2700						$dir->name = $dirName;
2701						if (isset($dirValue)) $dir->value = $dirValue;
2702						return true;
2703					}
2704				}
2705			}
2706
2707			$this->seek($s);
2708		}
2709
2710		// setting a variable
2711		if ($this->variable($var) && $this->assign() &&
2712			$this->propertyValue($value) && $this->end())
2713		{
2714			$this->append(array('assign', $var, $value), $s);
2715			return true;
2716		} else {
2717			$this->seek($s);
2718		}
2719
2720		if ($this->import($importValue)) {
2721			$this->append($importValue, $s);
2722			return true;
2723		}
2724
2725		// opening parametric mixin
2726		if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
2727			($this->guards($guards) || true) &&
2728			$this->literal('{'))
2729		{
2730			$block = $this->pushBlock($this->fixTags(array($tag)));
2731			$block->args = $args;
2732			$block->isVararg = $isVararg;
2733			if (!empty($guards)) $block->guards = $guards;
2734			return true;
2735		} else {
2736			$this->seek($s);
2737		}
2738
2739		// opening a simple block
2740		if ($this->tags($tags) && $this->literal('{', false)) {
2741			$tags = $this->fixTags($tags);
2742			$this->pushBlock($tags);
2743			return true;
2744		} else {
2745			$this->seek($s);
2746		}
2747
2748		// closing a block
2749		if ($this->literal('}', false)) {
2750			try {
2751				$block = $this->pop();
2752			} catch (\Exception $e) {
2753				$this->seek($s);
2754				$this->throwError($e->getMessage());
2755			}
2756
2757			$hidden = false;
2758			if (is_null($block->type)) {
2759				$hidden = true;
2760				if (!isset($block->args)) {
2761					foreach ($block->tags as $tag) {
2762						if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) {
2763							$hidden = false;
2764							break;
2765						}
2766					}
2767				}
2768
2769				foreach ($block->tags as $tag) {
2770					if (is_string($tag)) {
2771						$this->env->children[$tag][] = $block;
2772					}
2773				}
2774			}
2775
2776			if (!$hidden) {
2777				$this->append(array('block', $block), $s);
2778			}
2779
2780			// this is done here so comments aren't bundled into he block that
2781			// was just closed
2782			$this->whitespace();
2783			return true;
2784		}
2785
2786		// mixin
2787		if ($this->mixinTags($tags) &&
2788			($this->argumentDef($argv, $isVararg) || true) &&
2789			($this->keyword($suffix) || true) && $this->end())
2790		{
2791			$tags = $this->fixTags($tags);
2792			$this->append(array('mixin', $tags, $argv, $suffix), $s);
2793			return true;
2794		} else {
2795			$this->seek($s);
2796		}
2797
2798		// spare ;
2799		if ($this->literal(';')) return true;
2800
2801		return false; // got nothing, throw error
2802	}
2803
2804	protected function isDirective($dirname, $directives) {
2805		// TODO: cache pattern in parser
2806		$pattern = implode("|",
2807			array_map(array("lessc", "preg_quote"), $directives));
2808		$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
2809
2810		return preg_match($pattern, $dirname);
2811	}
2812
2813	protected function fixTags($tags) {
2814		// move @ tags out of variable namespace
2815		foreach ($tags as &$tag) {
2816			if ($tag[0] == $this->lessc->vPrefix)
2817				$tag[0] = $this->lessc->mPrefix;
2818		}
2819		return $tags;
2820	}
2821
2822	// a list of expressions
2823	protected function expressionList(&$exps) {
2824		$values = array();
2825
2826		while ($this->expression($exp)) {
2827			$values[] = $exp;
2828		}
2829
2830		if (count($values) == 0) return false;
2831
2832		$exps = lessc::compressList($values, ' ');
2833		return true;
2834	}
2835
2836	/**
2837	 * Attempt to consume an expression.
2838	 * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
2839	 */
2840	protected function expression(&$out) {
2841		if ($this->value($lhs)) {
2842			$out = $this->expHelper($lhs, 0);
2843
2844			// look for / shorthand
2845			if (!empty($this->env->supressedDivision)) {
2846				unset($this->env->supressedDivision);
2847				$s = $this->seek();
2848				if ($this->literal("/") && $this->value($rhs)) {
2849					$out = array("list", "",
2850						array($out, array("keyword", "/"), $rhs));
2851				} else {
2852					$this->seek($s);
2853				}
2854			}
2855
2856			return true;
2857		}
2858		return false;
2859	}
2860
2861	/**
2862	 * recursively parse infix equation with $lhs at precedence $minP
2863	 */
2864	protected function expHelper($lhs, $minP) {
2865		$this->inExp = true;
2866		$ss = $this->seek();
2867
2868		while (true) {
2869			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
2870				ctype_space($this->buffer[$this->count - 1]);
2871
2872			// If there is whitespace before the operator, then we require
2873			// whitespace after the operator for it to be an expression
2874			$needWhite = $whiteBefore && !$this->inParens;
2875
2876			if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
2877				if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
2878					foreach (self::$supressDivisionProps as $pattern) {
2879						if (preg_match($pattern, $this->env->currentProperty)) {
2880							$this->env->supressedDivision = true;
2881							break 2;
2882						}
2883					}
2884				}
2885
2886
2887				$whiteAfter = isset($this->buffer[$this->count - 1]) &&
2888					ctype_space($this->buffer[$this->count - 1]);
2889
2890				if (!$this->value($rhs)) break;
2891
2892				// peek for next operator to see what to do with rhs
2893				if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
2894					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
2895				}
2896
2897				$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
2898				$ss = $this->seek();
2899
2900				continue;
2901			}
2902
2903			break;
2904		}
2905
2906		$this->seek($ss);
2907
2908		return $lhs;
2909	}
2910
2911	// consume a list of values for a property
2912	public function propertyValue(&$value, $keyName = null) {
2913		$values = array();
2914
2915		if ($keyName !== null) $this->env->currentProperty = $keyName;
2916
2917		$s = null;
2918		while ($this->expressionList($v)) {
2919			$values[] = $v;
2920			$s = $this->seek();
2921			if (!$this->literal(',')) break;
2922		}
2923
2924		if ($s) $this->seek($s);
2925
2926		if ($keyName !== null) unset($this->env->currentProperty);
2927
2928		if (count($values) == 0) return false;
2929
2930		$value = lessc::compressList($values, ', ');
2931		return true;
2932	}
2933
2934	protected function parenValue(&$out) {
2935		$s = $this->seek();
2936
2937		// speed shortcut
2938		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
2939			return false;
2940		}
2941
2942		$inParens = $this->inParens;
2943		if ($this->literal("(") &&
2944			($this->inParens = true) && $this->expression($exp) &&
2945			$this->literal(")"))
2946		{
2947			$out = $exp;
2948			$this->inParens = $inParens;
2949			return true;
2950		} else {
2951			$this->inParens = $inParens;
2952			$this->seek($s);
2953		}
2954
2955		return false;
2956	}
2957
2958	// a single value
2959	protected function value(&$value) {
2960		$s = $this->seek();
2961
2962		// speed shortcut
2963		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
2964			// negation
2965			if ($this->literal("-", false) &&
2966				(($this->variable($inner) && $inner = array("variable", $inner)) ||
2967				$this->unit($inner) ||
2968				$this->parenValue($inner)))
2969			{
2970				$value = array("unary", "-", $inner);
2971				return true;
2972			} else {
2973				$this->seek($s);
2974			}
2975		}
2976
2977		if ($this->parenValue($value)) return true;
2978		if ($this->unit($value)) return true;
2979		if ($this->color($value)) return true;
2980		if ($this->func($value)) return true;
2981		if ($this->stringValue($value)) return true;
2982
2983		if ($this->keyword($word)) {
2984			$value = array('keyword', $word);
2985			return true;
2986		}
2987
2988		// try a variable
2989		if ($this->variable($var)) {
2990			$value = array('variable', $var);
2991			return true;
2992		}
2993
2994		// unquote string (should this work on any type?
2995		if ($this->literal("~") && $this->stringValue($str)) {
2996			$value = array("escape", $str);
2997			return true;
2998		} else {
2999			$this->seek($s);
3000		}
3001
3002		// css hack: \0
3003		if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
3004			$value = array('keyword', '\\'.$m[1]);
3005			return true;
3006		} else {
3007			$this->seek($s);
3008		}
3009
3010		return false;
3011	}
3012
3013	// an import statement
3014	protected function import(&$out) {
3015		if (!$this->literal('@import')) return false;
3016
3017		// @import "something.css" media;
3018		// @import url("something.css") media;
3019		// @import url(something.css) media;
3020
3021		if ($this->propertyValue($value)) {
3022			$out = array("import", $value);
3023			return true;
3024		}
3025	}
3026
3027	protected function mediaQueryList(&$out) {
3028		if ($this->genericList($list, "mediaQuery", ",", false)) {
3029			$out = $list[2];
3030			return true;
3031		}
3032		return false;
3033	}
3034
3035	protected function mediaQuery(&$out) {
3036		$s = $this->seek();
3037
3038		$expressions = null;
3039		$parts = array();
3040
3041		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
3042			$prop = array("mediaType");
3043			if (isset($only)) $prop[] = "only";
3044			if (isset($not)) $prop[] = "not";
3045			$prop[] = $mediaType;
3046			$parts[] = $prop;
3047		} else {
3048			$this->seek($s);
3049		}
3050
3051
3052		if (!empty($mediaType) && !$this->literal("and")) {
3053			// ~
3054		} else {
3055			$this->genericList($expressions, "mediaExpression", "and", false);
3056			if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
3057		}
3058
3059		if (count($parts) == 0) {
3060			$this->seek($s);
3061			return false;
3062		}
3063
3064		$out = $parts;
3065		return true;
3066	}
3067
3068	protected function mediaExpression(&$out) {
3069		$s = $this->seek();
3070		$value = null;
3071		if ($this->literal("(") &&
3072			$this->keyword($feature) &&
3073			($this->literal(":") && $this->expression($value) || true) &&
3074			$this->literal(")"))
3075		{
3076			$out = array("mediaExp", $feature);
3077			if ($value) $out[] = $value;
3078			return true;
3079		} elseif ($this->variable($variable)) {
3080			$out = array('variable', $variable);
3081			return true;
3082		}
3083
3084		$this->seek($s);
3085		return false;
3086	}
3087
3088	// an unbounded string stopped by $end
3089	protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
3090		$oldWhite = $this->eatWhiteDefault;
3091		$this->eatWhiteDefault = false;
3092
3093		$stop = array("'", '"', "@{", $end);
3094		$stop = array_map(array("lessc", "preg_quote"), $stop);
3095		// $stop[] = self::$commentMulti;
3096
3097		if (!is_null($rejectStrs)) {
3098			$stop = array_merge($stop, $rejectStrs);
3099		}
3100
3101		$patt = '(.*?)('.implode("|", $stop).')';
3102
3103		$nestingLevel = 0;
3104
3105		$content = array();
3106		while ($this->match($patt, $m, false)) {
3107			if (!empty($m[1])) {
3108				$content[] = $m[1];
3109				if ($nestingOpen) {
3110					$nestingLevel += substr_count($m[1], $nestingOpen);
3111				}
3112			}
3113
3114			$tok = $m[2];
3115
3116			$this->count-= strlen($tok);
3117			if ($tok == $end) {
3118				if ($nestingLevel == 0) {
3119					break;
3120				} else {
3121					$nestingLevel--;
3122				}
3123			}
3124
3125			if (($tok == "'" || $tok == '"') && $this->stringValue($str)) {
3126				$content[] = $str;
3127				continue;
3128			}
3129
3130			if ($tok == "@{" && $this->interpolation($inter)) {
3131				$content[] = $inter;
3132				continue;
3133			}
3134
3135			if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
3136				break;
3137			}
3138
3139			$content[] = $tok;
3140			$this->count+= strlen($tok);
3141		}
3142
3143		$this->eatWhiteDefault = $oldWhite;
3144
3145		if (count($content) == 0) return false;
3146
3147		// trim the end
3148		if (is_string(end($content))) {
3149			$content[count($content) - 1] = rtrim(end($content));
3150		}
3151
3152		$out = array("string", "", $content);
3153		return true;
3154	}
3155
3156	protected function stringValue(&$out) {
3157		$s = $this->seek();
3158		if ($this->literal('"', false)) {
3159			$delim = '"';
3160		} elseif ($this->literal("'", false)) {
3161			$delim = "'";
3162		} else {
3163			return false;
3164		}
3165
3166		$content = array();
3167
3168		// look for either ending delim , escape, or string interpolation
3169		$patt = '([^\n]*?)(@\{|\\\\|' .
3170			lessc::preg_quote($delim).')';
3171
3172		$oldWhite = $this->eatWhiteDefault;
3173		$this->eatWhiteDefault = false;
3174
3175		while ($this->match($patt, $m, false)) {
3176			$content[] = $m[1];
3177			if ($m[2] == "@{") {
3178				$this->count -= strlen($m[2]);
3179				if ($this->interpolation($inter, false)) {
3180					$content[] = $inter;
3181				} else {
3182					$this->count += strlen($m[2]);
3183					$content[] = "@{"; // ignore it
3184				}
3185			} elseif ($m[2] == '\\') {
3186				$content[] = $m[2];
3187				if ($this->literal($delim, false)) {
3188					$content[] = $delim;
3189				}
3190			} else {
3191				$this->count -= strlen($delim);
3192				break; // delim
3193			}
3194		}
3195
3196		$this->eatWhiteDefault = $oldWhite;
3197
3198		if ($this->literal($delim)) {
3199			$out = array("string", $delim, $content);
3200			return true;
3201		}
3202
3203		$this->seek($s);
3204		return false;
3205	}
3206
3207	protected function interpolation(&$out) {
3208		$oldWhite = $this->eatWhiteDefault;
3209		$this->eatWhiteDefault = true;
3210
3211		$s = $this->seek();
3212		if ($this->literal("@{") &&
3213			$this->openString("}", $interp, null, array("'", '"', ";")) &&
3214			$this->literal("}", false))
3215		{
3216			$out = array("interpolate", $interp);
3217			$this->eatWhiteDefault = $oldWhite;
3218			if ($this->eatWhiteDefault) $this->whitespace();
3219			return true;
3220		}
3221
3222		$this->eatWhiteDefault = $oldWhite;
3223		$this->seek($s);
3224		return false;
3225	}
3226
3227	protected function unit(&$unit) {
3228		// speed shortcut
3229		if (isset($this->buffer[$this->count])) {
3230			$char = $this->buffer[$this->count];
3231			if (!ctype_digit($char) && $char != ".") return false;
3232		}
3233
3234		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
3235			$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
3236			return true;
3237		}
3238		return false;
3239	}
3240
3241	// a # color
3242	protected function color(&$out) {
3243		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
3244			if (strlen($m[1]) > 7) {
3245				$out = array("string", "", array($m[1]));
3246			} else {
3247				$out = array("raw_color", $m[1]);
3248			}
3249			return true;
3250		}
3251
3252		return false;
3253	}
3254
3255	// consume an argument definition list surrounded by ()
3256	// each argument is a variable name with optional value
3257	// or at the end a ... or a variable named followed by ...
3258	// arguments are separated by , unless a ; is in the list, then ; is the
3259	// delimiter.
3260	protected function argumentDef(&$args, &$isVararg) {
3261		$s = $this->seek();
3262		if (!$this->literal('(')) return false;
3263
3264		$values = array();
3265		$delim = ",";
3266		$method = "expressionList";
3267
3268		$isVararg = false;
3269		while (true) {
3270			if ($this->literal("...")) {
3271				$isVararg = true;
3272				break;
3273			}
3274
3275			if ($this->$method($value)) {
3276				if ($value[0] == "variable") {
3277					$arg = array("arg", $value[1]);
3278					$ss = $this->seek();
3279
3280					if ($this->assign() && $this->$method($rhs)) {
3281						$arg[] = $rhs;
3282					} else {
3283						$this->seek($ss);
3284						if ($this->literal("...")) {
3285							$arg[0] = "rest";
3286							$isVararg = true;
3287						}
3288					}
3289
3290					$values[] = $arg;
3291					if ($isVararg) break;
3292					continue;
3293				} else {
3294					$values[] = array("lit", $value);
3295				}
3296			}
3297
3298
3299			if (!$this->literal($delim)) {
3300				if ($delim == "," && $this->literal(";")) {
3301					// found new delim, convert existing args
3302					$delim = ";";
3303					$method = "propertyValue";
3304
3305					// transform arg list
3306					if (isset($values[1])) { // 2 items
3307						$newList = array();
3308						foreach ($values as $i => $arg) {
3309							switch($arg[0]) {
3310							case "arg":
3311								if ($i) {
3312									$this->throwError("Cannot mix ; and , as delimiter types");
3313								}
3314								$newList[] = $arg[2];
3315								break;
3316							case "lit":
3317								$newList[] = $arg[1];
3318								break;
3319							case "rest":
3320								$this->throwError("Unexpected rest before semicolon");
3321							}
3322						}
3323
3324						$newList = array("list", ", ", $newList);
3325
3326						switch ($values[0][0]) {
3327						case "arg":
3328							$newArg = array("arg", $values[0][1], $newList);
3329							break;
3330						case "lit":
3331							$newArg = array("lit", $newList);
3332							break;
3333						}
3334
3335					} elseif ($values) { // 1 item
3336						$newArg = $values[0];
3337					}
3338
3339					if ($newArg) {
3340						$values = array($newArg);
3341					}
3342				} else {
3343					break;
3344				}
3345			}
3346		}
3347
3348		if (!$this->literal(')')) {
3349			$this->seek($s);
3350			return false;
3351		}
3352
3353		$args = $values;
3354
3355		return true;
3356	}
3357
3358	// consume a list of tags
3359	// this accepts a hanging delimiter
3360	protected function tags(&$tags, $simple = false, $delim = ',') {
3361		$tags = array();
3362		while ($this->tag($tt, $simple)) {
3363			$tags[] = $tt;
3364			if (!$this->literal($delim)) break;
3365		}
3366		if (count($tags) == 0) return false;
3367
3368		return true;
3369	}
3370
3371	// list of tags of specifying mixin path
3372	// optionally separated by > (lazy, accepts extra >)
3373	protected function mixinTags(&$tags) {
3374		$tags = array();
3375		while ($this->tag($tt, true)) {
3376			$tags[] = $tt;
3377			$this->literal(">");
3378		}
3379
3380		if (count($tags) == 0) return false;
3381
3382		return true;
3383	}
3384
3385	// a bracketed value (contained within in a tag definition)
3386	protected function tagBracket(&$parts, &$hasExpression) {
3387		// speed shortcut
3388		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
3389			return false;
3390		}
3391
3392		$s = $this->seek();
3393
3394		$hasInterpolation = false;
3395
3396		if ($this->literal("[", false)) {
3397			$attrParts = array("[");
3398			// keyword, string, operator
3399			while (true) {
3400				if ($this->literal("]", false)) {
3401					$this->count--;
3402					break; // get out early
3403				}
3404
3405				if ($this->match('\s+', $m)) {
3406					$attrParts[] = " ";
3407					continue;
3408				}
3409				if ($this->stringValue($str)) {
3410					// escape parent selector, (yuck)
3411					foreach ($str[2] as &$chunk) {
3412						$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
3413					}
3414
3415					$attrParts[] = $str;
3416					$hasInterpolation = true;
3417					continue;
3418				}
3419
3420				if ($this->keyword($word)) {
3421					$attrParts[] = $word;
3422					continue;
3423				}
3424
3425				if ($this->interpolation($inter, false)) {
3426					$attrParts[] = $inter;
3427					$hasInterpolation = true;
3428					continue;
3429				}
3430
3431				// operator, handles attr namespace too
3432				if ($this->match('[|-~\$\*\^=]+', $m)) {
3433					$attrParts[] = $m[0];
3434					continue;
3435				}
3436
3437				break;
3438			}
3439
3440			if ($this->literal("]", false)) {
3441				$attrParts[] = "]";
3442				foreach ($attrParts as $part) {
3443					$parts[] = $part;
3444				}
3445				$hasExpression = $hasExpression || $hasInterpolation;
3446				return true;
3447			}
3448			$this->seek($s);
3449		}
3450
3451		$this->seek($s);
3452		return false;
3453	}
3454
3455	// a space separated list of selectors
3456	protected function tag(&$tag, $simple = false) {
3457		if ($simple)
3458			$chars = '^@,:;{}\][>\(\) "\'';
3459		else
3460			$chars = '^@,;{}["\'';
3461
3462		$s = $this->seek();
3463
3464		$hasExpression = false;
3465		$parts = array();
3466		while ($this->tagBracket($parts, $hasExpression));
3467
3468		$oldWhite = $this->eatWhiteDefault;
3469		$this->eatWhiteDefault = false;
3470
3471		while (true) {
3472			if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
3473				$parts[] = $m[1];
3474				if ($simple) break;
3475
3476				while ($this->tagBracket($parts, $hasExpression));
3477				continue;
3478			}
3479
3480			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
3481				if ($this->interpolation($interp)) {
3482					$hasExpression = true;
3483					$interp[2] = true; // don't unescape
3484					$parts[] = $interp;
3485					continue;
3486				}
3487
3488				if ($this->literal("@")) {
3489					$parts[] = "@";
3490					continue;
3491				}
3492			}
3493
3494			if ($this->unit($unit)) { // for keyframes
3495				$parts[] = $unit[1];
3496				$parts[] = $unit[2];
3497				continue;
3498			}
3499
3500			break;
3501		}
3502
3503		$this->eatWhiteDefault = $oldWhite;
3504		if (!$parts) {
3505			$this->seek($s);
3506			return false;
3507		}
3508
3509		if ($hasExpression) {
3510			$tag = array("exp", array("string", "", $parts));
3511		} else {
3512			$tag = trim(implode($parts));
3513		}
3514
3515		$this->whitespace();
3516		return true;
3517	}
3518
3519	// a css function
3520	protected function func(&$func) {
3521		$s = $this->seek();
3522
3523		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
3524			$fname = $m[1];
3525
3526			$sPreArgs = $this->seek();
3527
3528			$args = array();
3529			while (true) {
3530				$ss = $this->seek();
3531				// this ugly nonsense is for ie filter properties
3532				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
3533					$args[] = array("string", "", array($name, "=", $value));
3534				} else {
3535					$this->seek($ss);
3536					if ($this->expressionList($value)) {
3537						$args[] = $value;
3538					}
3539				}
3540
3541				if (!$this->literal(',')) break;
3542			}
3543			$args = array('list', ',', $args);
3544
3545			if ($this->literal(')')) {
3546				$func = array('function', $fname, $args);
3547				return true;
3548			} elseif ($fname == 'url') {
3549				// couldn't parse and in url? treat as string
3550				$this->seek($sPreArgs);
3551				if ($this->openString(")", $string) && $this->literal(")")) {
3552					$func = array('function', $fname, $string);
3553					return true;
3554				}
3555			}
3556		}
3557
3558		$this->seek($s);
3559		return false;
3560	}
3561
3562	// consume a less variable
3563	protected function variable(&$name) {
3564		$s = $this->seek();
3565		if ($this->literal($this->lessc->vPrefix, false) &&
3566			($this->variable($sub) || $this->keyword($name)))
3567		{
3568			if (!empty($sub)) {
3569				$name = array('variable', $sub);
3570			} else {
3571				$name = $this->lessc->vPrefix.$name;
3572			}
3573			return true;
3574		}
3575
3576		$name = null;
3577		$this->seek($s);
3578		return false;
3579	}
3580
3581	/**
3582	 * Consume an assignment operator
3583	 * Can optionally take a name that will be set to the current property name
3584	 */
3585	protected function assign($name = null) {
3586		if ($name) $this->currentProperty = $name;
3587		return $this->literal(':') || $this->literal('=');
3588	}
3589
3590	// consume a keyword
3591	protected function keyword(&$word) {
3592		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
3593			$word = $m[1];
3594			return true;
3595		}
3596		return false;
3597	}
3598
3599	// consume an end of statement delimiter
3600	protected function end() {
3601		if ($this->literal(';', false)) {
3602			return true;
3603		} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
3604			// if there is end of file or a closing block next then we don't need a ;
3605			return true;
3606		}
3607		return false;
3608	}
3609
3610	protected function guards(&$guards) {
3611		$s = $this->seek();
3612
3613		if (!$this->literal("when")) {
3614			$this->seek($s);
3615			return false;
3616		}
3617
3618		$guards = array();
3619
3620		while ($this->guardGroup($g)) {
3621			$guards[] = $g;
3622			if (!$this->literal(",")) break;
3623		}
3624
3625		if (count($guards) == 0) {
3626			$guards = null;
3627			$this->seek($s);
3628			return false;
3629		}
3630
3631		return true;
3632	}
3633
3634	// a bunch of guards that are and'd together
3635	// TODO rename to guardGroup
3636	protected function guardGroup(&$guardGroup) {
3637		$s = $this->seek();
3638		$guardGroup = array();
3639		while ($this->guard($guard)) {
3640			$guardGroup[] = $guard;
3641			if (!$this->literal("and")) break;
3642		}
3643
3644		if (count($guardGroup) == 0) {
3645			$guardGroup = null;
3646			$this->seek($s);
3647			return false;
3648		}
3649
3650		return true;
3651	}
3652
3653	protected function guard(&$guard) {
3654		$s = $this->seek();
3655		$negate = $this->literal("not");
3656
3657		if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
3658			$guard = $exp;
3659			if ($negate) $guard = array("negate", $guard);
3660			return true;
3661		}
3662
3663		$this->seek($s);
3664		return false;
3665	}
3666
3667	/* raw parsing functions */
3668
3669	protected function literal($what, $eatWhitespace = null) {
3670		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3671
3672		// shortcut on single letter
3673		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3674			if ($this->buffer[$this->count] == $what) {
3675				if (!$eatWhitespace) {
3676					$this->count++;
3677					return true;
3678				}
3679				// goes below...
3680			} else {
3681				return false;
3682			}
3683		}
3684
3685		if (!isset(self::$literalCache[$what])) {
3686			self::$literalCache[$what] = lessc::preg_quote($what);
3687		}
3688
3689		return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
3690	}
3691
3692	protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3693		$s = $this->seek();
3694		$items = array();
3695		while ($this->$parseItem($value)) {
3696			$items[] = $value;
3697			if ($delim) {
3698				if (!$this->literal($delim)) break;
3699			}
3700		}
3701
3702		if (count($items) == 0) {
3703			$this->seek($s);
3704			return false;
3705		}
3706
3707		if ($flatten && count($items) == 1) {
3708			$out = $items[0];
3709		} else {
3710			$out = array("list", $delim, $items);
3711		}
3712
3713		return true;
3714	}
3715
3716
3717	// advance counter to next occurrence of $what
3718	// $until - don't include $what in advance
3719	// $allowNewline, if string, will be used as valid char set
3720	protected function to($what, &$out, $until = false, $allowNewline = false) {
3721		if (is_string($allowNewline)) {
3722			$validChars = $allowNewline;
3723		} else {
3724			$validChars = $allowNewline ? "." : "[^\n]";
3725		}
3726		if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
3727		if ($until) $this->count -= strlen($what); // give back $what
3728		$out = $m[1];
3729		return true;
3730	}
3731
3732	// try to match something on head of buffer
3733	protected function match($regex, &$out, $eatWhitespace = null) {
3734		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3735
3736		$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
3737		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3738			$this->count += strlen($out[0]);
3739			if ($eatWhitespace && $this->writeComments) $this->whitespace();
3740			return true;
3741		}
3742		return false;
3743	}
3744
3745	// match some whitespace
3746	protected function whitespace() {
3747		if ($this->writeComments) {
3748			$gotWhite = false;
3749			while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3750				if (isset($m[1]) && empty($this->seenComments[$this->count])) {
3751					$this->append(array("comment", $m[1]));
3752					$this->seenComments[$this->count] = true;
3753				}
3754				$this->count += strlen($m[0]);
3755				$gotWhite = true;
3756			}
3757			return $gotWhite;
3758		} else {
3759			$this->match("", $m);
3760			return strlen($m[0]) > 0;
3761		}
3762	}
3763
3764	// match something without consuming it
3765	protected function peek($regex, &$out = null, $from=null) {
3766		if (is_null($from)) $from = $this->count;
3767		$r = '/'.$regex.'/Ais';
3768		$result = preg_match($r, $this->buffer, $out, null, $from);
3769
3770		return $result;
3771	}
3772
3773	// seek to a spot in the buffer or return where we are on no argument
3774	protected function seek($where = null) {
3775		if ($where === null) return $this->count;
3776		else $this->count = $where;
3777		return true;
3778	}
3779
3780	/* misc functions */
3781
3782	public function throwError($msg = "parse error", $count = null) {
3783		$count = is_null($count) ? $this->count : $count;
3784
3785		$line = $this->line +
3786			substr_count(substr($this->buffer, 0, $count), "\n");
3787
3788		if (!empty($this->sourceName)) {
3789			$loc = "$this->sourceName on line $line";
3790		} else {
3791			$loc = "line: $line";
3792		}
3793
3794		// TODO this depends on $this->count
3795		if ($this->peek("(.*?)(\n|$)", $m, $count)) {
3796			throw new \Exception("$msg: failed at `$m[1]` $loc");
3797		} else {
3798			throw new \Exception("$msg: $loc");
3799		}
3800	}
3801
3802	protected function pushBlock($selectors=null, $type=null) {
3803		$b = new \stdClass();
3804		$b->parent = $this->env;
3805
3806		$b->type = $type;
3807		$b->id = self::$nextBlockId++;
3808
3809		$b->isVararg = false; // TODO: kill me from here
3810		$b->tags = $selectors;
3811
3812		$b->props = array();
3813		$b->children = array();
3814
3815        // add a reference to the parser so
3816        // we can access the parser to throw errors
3817        // or retrieve the sourceName of this block.
3818        $b->parser = $this;
3819
3820        // so we know the position of this block
3821        $b->count = $this->count;
3822
3823		$this->env = $b;
3824		return $b;
3825	}
3826
3827	// push a block that doesn't multiply tags
3828	protected function pushSpecialBlock($type) {
3829		return $this->pushBlock(null, $type);
3830	}
3831
3832	// append a property to the current block
3833	protected function append($prop, $pos = null) {
3834		if ($pos !== null) $prop[-1] = $pos;
3835		$this->env->props[] = $prop;
3836	}
3837
3838	// pop something off the stack
3839	protected function pop() {
3840		$old = $this->env;
3841		$this->env = $this->env->parent;
3842		return $old;
3843	}
3844
3845	// remove comments from $text
3846	// todo: make it work for all functions, not just url
3847	protected function removeComments($text) {
3848		$look = array(
3849			'url(', '//', '/*', '"', "'"
3850		);
3851
3852		$out = '';
3853		$min = null;
3854		while (true) {
3855			// find the next item
3856			foreach ($look as $token) {
3857				$pos = strpos($text, $token);
3858				if ($pos !== false) {
3859					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
3860				}
3861			}
3862
3863			if (is_null($min)) break;
3864
3865			$count = $min[1];
3866			$skip = 0;
3867			$newlines = 0;
3868			switch ($min[0]) {
3869			case 'url(':
3870				if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
3871					$count += strlen($m[0]) - strlen($min[0]);
3872				break;
3873			case '"':
3874			case "'":
3875				if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
3876					$count += strlen($m[0]) - 1;
3877				break;
3878			case '//':
3879				$skip = strpos($text, "\n", $count);
3880				if ($skip === false) $skip = strlen($text) - $count;
3881				else $skip -= $count;
3882				break;
3883			case '/*':
3884				if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
3885					$skip = strlen($m[0]);
3886					$newlines = substr_count($m[0], "\n");
3887				}
3888				break;
3889			}
3890
3891			if ($skip == 0) $count += strlen($min[0]);
3892
3893			$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
3894			$text = substr($text, $count + $skip);
3895
3896			$min = null;
3897		}
3898
3899		return $out.$text;
3900	}
3901
3902}
3903
3904class lessc_formatter_classic {
3905	public $indentChar = "  ";
3906
3907	public $break = "\n";
3908	public $open = " {";
3909	public $close = "}";
3910	public $selectorSeparator = ", ";
3911	public $assignSeparator = ":";
3912
3913	public $openSingle = " { ";
3914	public $closeSingle = " }";
3915
3916	public $disableSingle = false;
3917	public $breakSelectors = false;
3918
3919	public $compressColors = false;
3920
3921	public function __construct() {
3922		$this->indentLevel = 0;
3923	}
3924
3925	public function indentStr($n = 0) {
3926		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
3927	}
3928
3929	public function property($name, $value) {
3930		return $name . $this->assignSeparator . $value . ";";
3931	}
3932
3933	protected function isEmpty($block) {
3934		if (empty($block->lines)) {
3935			foreach ($block->children as $child) {
3936				if (!$this->isEmpty($child)) return false;
3937			}
3938
3939			return true;
3940		}
3941		return false;
3942	}
3943
3944	public function block($block) {
3945		if ($this->isEmpty($block)) return;
3946
3947		$inner = $pre = $this->indentStr();
3948
3949		$isSingle = !$this->disableSingle &&
3950			is_null($block->type) && count($block->lines) == 1;
3951
3952		if (!empty($block->selectors)) {
3953			$this->indentLevel++;
3954
3955			if ($this->breakSelectors) {
3956				$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
3957			} else {
3958				$selectorSeparator = $this->selectorSeparator;
3959			}
3960
3961			echo $pre .
3962				implode($selectorSeparator, $block->selectors);
3963			if ($isSingle) {
3964				echo $this->openSingle;
3965				$inner = "";
3966			} else {
3967				echo $this->open . $this->break;
3968				$inner = $this->indentStr();
3969			}
3970
3971		}
3972
3973		if (!empty($block->lines)) {
3974			$glue = $this->break.$inner;
3975			echo $inner . implode($glue, $block->lines);
3976			if (!$isSingle && !empty($block->children)) {
3977				echo $this->break;
3978			}
3979		}
3980
3981		foreach ($block->children as $child) {
3982			$this->block($child);
3983		}
3984
3985		if (!empty($block->selectors)) {
3986			if (!$isSingle && empty($block->children)) echo $this->break;
3987
3988			if ($isSingle) {
3989				echo $this->closeSingle . $this->break;
3990			} else {
3991				echo $pre . $this->close . $this->break;
3992			}
3993
3994			$this->indentLevel--;
3995		}
3996	}
3997}
3998
3999class lessc_formatter_compressed extends lessc_formatter_classic {
4000	public $disableSingle = true;
4001	public $open = "{";
4002	public $selectorSeparator = ",";
4003	public $assignSeparator = ":";
4004	public $break = "";
4005	public $compressColors = true;
4006
4007	public function indentStr($n = 0) {
4008		return "";
4009	}
4010}
4011
4012class lessc_formatter_lessjs extends lessc_formatter_classic {
4013	public $disableSingle = true;
4014	public $breakSelectors = true;
4015	public $assignSeparator = ": ";
4016	public $selectorSeparator = ",";
4017}
4018
4019
4020