<?php
/**
* Module Prestashop FooterCustom
*
* @author    Prestacrea
* @copyright Prestacrea
* @license   See PDF documentation
* @website   http://www.prestacrea.com
*
* lessphp v0.3.9
* http://leafo.net/lessphp
* LESS css compiler, adapted from http://lesscss.org
* Copyright 2012, Leaf Corcoran <leafot@gmail.com>
* Licensed under MIT or GPLv3, see LICENSE
*/

class Lessc
{
	static public $version = 'v0.3.9';
	static protected $true = array('keyword', 'true');
	static protected $false = array('keyword', 'false');

	protected $lib_functions = array();
	protected $registered_vars = array();
	protected $preserve_comments = false;

	public $v_prefix = '@';
	public $m_prefix = '$';
	public $parent_selector = '&';
	public $import_disabled = false;
	public $import_dir = '';

	protected $number_precision = null;
	protected $source_parser = null;
	protected $source_loc = null;

	static public $default_value = array('keyword', '');
	static protected $next_import_id = 0;

	protected function findImport($url)
	{
		foreach ((array)$this->import_dir as $dir) {
			$full = $dir.(Tools::substr($dir, -1) != '/' ? '/' : '').$url;
			if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full))
				return $file;
		}
		return null;
	}

	protected function fileExists($name)
	{
		return is_file($name);
	}

	public static function compressList($items, $delim)
	{
		if (!isset($items[1]) && isset($items[0]))
			return $items[0];
		else
			return array('list', $delim, $items);
	}

	public static function pregQuote($what)
	{
		return preg_quote($what, '/');
	}

	protected function tryImport($import_path, $parent_block, $out)
	{
		if ($import_path[0] == 'function' && $import_path[1] == 'url')
			$import_path = $this->flattenList($import_path[2]);
		$str = $this->coerceString($import_path);
		if ($str === null)
			return false;
		$url = $this->compileValue($this->libE($str));
		if (substr_compare($url, '.css', -4, 4) === 0)
			return false;
		$real_path = $this->findImport($url);
		if ($real_path === null)
			return false;
		if ($this->import_disabled)
			return array(false, '/* import disabled */');
		$this->addParsedFile($real_path);
		$parser = $this->makeParser($real_path);
		$root = $parser->parse(Tools::file_get_contents($real_path));
		foreach ($root->props as $prop) {
			if ($prop[0] == 'block')
				$prop[1]->parent = $parent_block;
		}
		foreach ($root->children as $child_name => $child) {
			if (isset($parent_block->children[$child_name]))
				$parent_block->children[$child_name] = array_merge($parent_block->children[$child_name], $child);
			else
				$parent_block->children[$child_name] = $child;
		}
		$pi = pathinfo($real_path);
		$dir = $pi['dirname'];
		list($top, $bottom) = $this->sortProps($root->props, true);
		$this->compileImportedProps($top, $parent_block, $out, $parser, $dir);
		return array(true, $bottom, $parser, $dir);
	}

	protected function compileImportedProps($props, $block, $out, $import_dir)
	{
		$old_source_parser = $this->source_parser;
		$old_import = $this->import_dir;
		$this->import_dir = (array)$this->import_dir;
		array_unshift($this->import_dir, $import_dir);
		foreach ($props as $prop)
			$this->compileProp($prop, $block, $out);
		$this->import_dir = $old_import;
		$this->source_parser = $old_source_parser;
	}

	protected function compileBlock($block)
	{
		switch ($block->type) {
			case 'root':
				$this->compileRoot($block);
				break;
			case null:
				$this->compileCSSBlock($block);
				break;
			case 'media':
				$this->compileMedia($block);
				break;
			case 'directive':
				$name = '@'.$block->name;
				if (!empty($block->value))
					$name .= ' '.$this->compileValue($this->reduce($block->value));
				$this->compileNestedBlock($block, array($name));
				break;
			default:
				$this->throwError("unknown block type: $block->type\n");
		}
	}

	protected function compileCSSBlock($block)
	{
		$env = $this->pushEnv();
		$selectors = $this->compileSelectors($block->tags);
		$env->selectors = $this->multiplySelectors($selectors);
		$out = $this->makeOutputBlock(null, $env->selectors);
		$this->scope->children[] = $out;
		$this->compileProps($block, $out);
		$block->scope = $env;
		$this->popEnv();
	}

	protected function compileMedia($media)
	{
		$env = $this->pushEnv($media);
		$parent_scope = $this->mediaParent($this->scope);
		$query = $this->compileMediaQuery($this->multiplyMedia($env));
		$this->scope = $this->makeOutputBlock($media->type, array($query));
		$parent_scope->children[] = $this->scope;
		$this->compileProps($media, $this->scope);
		if (count($this->scope->lines) > 0) {
			$orphan_selelectors = $this->findClosestSelectors();
			if (!is_null($orphan_selelectors)) {
				$orphan = $this->makeOutputBlock(null, $orphan_selelectors);
				$orphan->lines = $this->scope->lines;
				array_unshift($this->scope->children, $orphan);
				$this->scope->lines = array();
			}
		}
		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	protected function mediaParent($scope)
	{
		while (!empty($scope->parent)) {
			if (!empty($scope->type) && $scope->type != 'media')
				break;
			$scope = $scope->parent;
		}
		return $scope;
	}

	protected function compileNestedBlock($block, $selectors)
	{
		$this->pushEnv($block);
		$this->scope = $this->makeOutputBlock($block->type, $selectors);
		$this->scope->parent->children[] = $this->scope;
		$this->compileProps($block, $this->scope);
		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	protected function compileRoot($root)
	{
		$this->pushEnv();
		$this->scope = $this->makeOutputBlock($root->type);
		$this->compileProps($root, $this->scope);
		$this->popEnv();
	}

	protected function compileProps($block, $out)
	{
		foreach ($this->sortProps($block->props) as $prop)
			$this->compileProp($prop, $block, $out);
	}

	protected function sortProps($props, $split = false)
	{
		$vars = array();
		$imports = array();
		$other = array();
		foreach ($props as $prop) {
			switch ($prop[0]) {
				case 'assign':
					if (isset($prop[1][0]) && $prop[1][0] == $this->v_prefix)
						$vars[] = $prop;
					else
						$other[] = $prop;
					break;
				case 'import':
					$id = self::$next_import_id++;
					$prop[] = $id;
					$imports[] = $prop;
					$other[] = array('import_mixin', $id);
					break;
				default:
					$other[] = $prop;
			}
		}
		if ($split)
			return array(array_merge($vars, $imports), $other);
		else
			return array_merge($vars, $imports, $other);
	}

	protected function compileMediaQuery($queries)
	{
		$compiled_queries = array();
		foreach ($queries as $query) {
			$parts = array();
			foreach ($query as $q) {
				switch ($q[0]) {
					case 'mediaType':
						$parts[] = implode(' ', array_slice($q, 1));
						break;
					case 'mediaExp':
						if (isset($q[2]))
							$parts[] = "($q[1]: ".$this->compileValue($this->reduce($q[2])).')';
						else
							$parts[] = "($q[1])";
						break;
					case 'variable':
						$parts[] = $this->compileValue($this->reduce($q));
					break;
				}
			}
			if (count($parts) > 0)
				$compiled_queries[] = implode(' and ', $parts);
		}
		$out = '@media';
		if (!empty($parts))
			$out .= ' '.implode($this->formatter->selector_separator, $compiled_queries);
		return $out;
	}

	protected function multiplyMedia($env, $child_queries = null)
	{
		if (is_null($env) || !empty($env->block->type) && $env->block->type != 'media')
			return $child_queries;
		if (empty($env->block->type))
			return $this->multiplyMedia($env->parent, $child_queries);
		$out = array();
		$queries = $env->block->queries;
		if (is_null($child_queries))
			$out = $queries;
		else {
			foreach ($queries as $parent) {
				foreach ($child_queries as $child)
					$out[] = array_merge($parent, $child);
			}
		}
		return $this->multiplyMedia($env->parent, $out);
	}

	protected function expandParentSelectors(&$tag, $replace)
	{
		$parts = explode('$&$', $tag);
		$count = 0;
		foreach ($parts as &$part) {
			$part = str_replace($this->parent_selector, $replace, $part, $c);
			$count += $c;
		}
		$tag = implode($this->parent_selector, $parts);
		return $count;
	}

	protected function findClosestSelectors()
	{
		$env = $this->env;
		$selectors = null;
		while ($env !== null) {
			if (isset($env->selectors)) {
				$selectors = $env->selectors;
				break;
			}
			$env = $env->parent;
		}
		return $selectors;
	}

	protected function multiplySelectors($selectors)
	{
		$parent_selectors = $this->findClosestSelectors();
		if (is_null($parent_selectors)) {
			foreach ($selectors as &$s)
				$this->expandParentSelectors($s, '');
			return $selectors;
		}
		$out = array();
		foreach ($parent_selectors as $parent) {
			foreach ($selectors as $child) {
				$count = $this->expandParentSelectors($child, $parent);
				if ($count > 0)
					$out[] = trim($child);
				else
					$out[] = trim($parent.' '.$child);
			}
		}
		return $out;
	}

	protected function compileSelectors($selectors)
	{
		$out = array();
		foreach ($selectors as $s) {
			if (is_array($s)) {
				list(, $value) = $s;
				$out[] = trim($this->compileValue($this->reduce($value)));
			} else
				$out[] = $s;
		}
		return $out;
	}

	protected function eq($left, $right)
	{
		return $left == $right;
	}

	protected function patternMatch($block, $calling_args)
	{
		if (!empty($block->guards)) {
			$group_passed = false;
			foreach ($block->guards as $guard_group) {
				foreach ($guard_group as $guard) {
					$this->pushEnv();
					$this->zipSetArgs($block->args, $calling_args);
					$negate = false;
					if ($guard[0] == 'negate') {
						$guard = $guard[1];
						$negate = true;
					}
					$passed = $this->reduce($guard) == self::$true;
					if ($negate)
						$passed = !$passed;
					$this->popEnv();
					if ($passed)
						$group_passed = true;
					else {
						$group_passed = false;
						break;
					}
				}
				if ($group_passed)
					break;
			}
			if (!$group_passed)
				return false;
		}
		$num_calling = count($calling_args);
		if (empty($block->args))
			return $block->is_vararg || $num_calling == 0;
		$i = -1;
		foreach ($block->args as $i => $arg) {
			switch ($arg[0]) {
				case 'lit':
					if (empty($calling_args[$i]) || !$this->eq($arg[1], $calling_args[$i]))
						return false;
					break;
				case 'arg':
					if (!isset($calling_args[$i]) && !isset($arg[2]))
						return false;
					break;
				case 'rest':
					$i--;
					break 2;
			}
		}
		if ($block->is_vararg)
			return true;
		else {
			$num_matched = $i + 1;
			return $num_matched >= $num_calling;
		}
	}

	protected function patternMatchAll($blocks, $calling_args)
	{
		$matches = null;
		foreach ($blocks as $block) {
			if ($this->patternMatch($block, $calling_args))
				$matches[] = $block;
		}
		return $matches;
	}

	protected function findBlocks($search_in, $path, $args, $seen = array())
	{
		if ($search_in == null)
			return null;
		if (isset($seen[$search_in->id]))
			return null;
		$seen[$search_in->id] = true;
		$name = $path[0];
		if (isset($search_in->children[$name])) {
			$blocks = $search_in->children[$name];
			if (count($path) == 1) {
				$matches = $this->patternMatchAll($blocks, $args);
				if (!empty($matches))
					return $matches;
			} else {
				$matches = array();
				foreach ($blocks as $sub_block) {
					$sub_matches = $this->findBlocks($sub_block, array_slice($path, 1), $args, $seen);
					if (!is_null($sub_matches)) {
						foreach ($sub_matches as $sm)
							$matches[] = $sm;
					}
				}
				return count($matches) > 0 ? $matches : null;
			}
		}
		if ($search_in->parent === $search_in)
			return null;
		return $this->findBlocks($search_in->parent, $path, $args, $seen);
	}

	protected function zipSetArgs($args, $values)
	{
		$i = 0;
		$assigned_values = array();
		foreach ($args as $a) {
			if ($a[0] == 'arg') {
				if ($i < count($values) && !is_null($values[$i]))
					$value = $values[$i];
				elseif (isset($a[2]))
					$value = $a[2];
				else
					$value = null;
				$value = $this->reduce($value);
				$this->set($a[1], $value);
				$assigned_values[] = $value;
			}
			$i++;
		}
		$last = end($args);
		if ($last[0] == 'rest') {
			$rest = array_slice($values, count($args) - 1);
			$this->set($last[1], $this->reduce(array('list', ' ', $rest)));
		}
		$this->env->arguments = $assigned_values;
	}

	protected function compileProp($prop, $block, $out)
	{
		$this->source_loc = isset($prop[-1]) ? $prop[-1] : - 1;
		switch ($prop[0]) {
			case 'assign':
				list(, $name, $value) = $prop;
				if ($name[0] == $this->v_prefix)
					$this->set($name, $value);
				else {
					$out->lines[] = $this->formatter->property($name, $this->compileValue($this->reduce($value)));
				}
				break;
			case 'block':
				list(, $child) = $prop;
				$this->compileBlock($child);
				break;
			case 'mixin':
				list(, $path, $args, $suffix) = $prop;
				$args = array_map(array($this, 'reduce'), (array)$args);
				$mixins = $this->findBlocks($block, $path, $args);
				if ($mixins === null)
					break;
				foreach ($mixins as $mixin) {
					$have_scope = false;
					if (isset($mixin->parent->scope)) {
						$have_scope = true;
						$mixin_parent_env = $this->pushEnv();
						$mixin_parent_env->store_parent = $mixin->parent->scope;
					}
					$have_args = false;
					if (isset($mixin->args)) {
						$have_args = true;
						$this->pushEnv();
						$this->zipSetArgs($mixin->args, $args);
					}
					$old_parent = $mixin->parent;
					if ($mixin != $block)
						$mixin->parent = $block;
					foreach ($this->sortProps($mixin->props) as $sub_prop) {
						if ($suffix !== null && $sub_prop[0] == 'assign' && is_string($sub_prop[1]) && $sub_prop[1]{0} != $this->v_prefix) {
							$sub_prop[2] = array(
								'list', ' ',
								array($sub_prop[2], array('keyword', $suffix))
							);
						}
						$this->compileProp($sub_prop, $mixin, $out);
					}
					$mixin->parent = $old_parent;
					if ($have_args)
						$this->popEnv();
					if ($have_scope)
						$this->popEnv();
				}
				break;
			case 'raw':
				$out->lines[] = $prop[1];
				break;
			case 'directive':
				list(, $name, $value) = $prop;
				$out->lines[] = "@$name ".$this->compileValue($this->reduce($value)).';';
				break;
			case 'comment':
				$out->lines[] = $prop[1];
				break;
			case 'import';
				list(, $import_path, $import_id) = $prop;
				$import_path = $this->reduce($import_path);
				if (!isset($this->env->imports))
					$this->env->imports = array();

				$result = $this->tryImport($import_path, $block, $out);
				$this->env->imports[$import_id] = $result === false ?
					array(false, '@import '.$this->compileValue($import_path).';') :
					$result;
				break;
			case 'import_mixin':
				list(,$import_id) = $prop;
				$import = $this->env->imports[$import_id];
				if ($import[0] === false)
					$out->lines[] = $import[1];
				else {
					list(, $bottom, $parser, $import_dir) = $import;
					$this->compileImportedProps($bottom, $block, $out, $parser, $import_dir);
				}
				break;
			default:
				$this->throwError("unknown op: {$prop[0]}\n");
		}
	}

	protected function compileValue($value)
	{
		switch ($value[0]) {
			case 'list':
				return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
			case 'raw_color':
				if (!empty($this->formatter->compress_colors))
					return $this->compileValue($this->coerceColor($value));
				return $value[1];
			case 'keyword':
				return $value[1];
			case 'number':
				list(, $num, $unit) = $value;
				if ($this->number_precision !== null)
					$num = round($num, $this->number_precision);
				return $num.$unit;
			case 'string':
				list(, $delim, $content) = $value;
				foreach ($content as &$part) {
					if (is_array($part))
						$part = $this->compileValue($part);
				}
				return $delim.implode($content).$delim;
			case 'color':
				list(, $r, $g, $b) = $value;
				$r = round($r);
				$g = round($g);
				$b = round($b);
				if (count($value) == 5 && $value[4] != 1)
					return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
				$h = sprintf('#%02x%02x%02x', $r, $g, $b);
				if (!empty($this->formatter->compress_colors)) {
					if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6])
						$h = '#'.$h[1].$h[3].$h[5];
				}
				return $h;
			case 'function':
				list(, $name, $args) = $value;
				return $name.'('.$this->compileValue($args).')';
			default:
				$this->throwError("unknown value type: $value[0]");
		}
	}

	protected function libIsnumber($value)
	{
		return $this->toBool($value[0] == 'number');
	}

	protected function libIsstring($value)
	{
		return $this->toBool($value[0] == 'string');
	}

	protected function libIscolor($value)
	{
		return $this->toBool($this->coerceColor($value));
	}

	protected function libIskeyword($value)
	{
		return $this->toBool($value[0] == 'keyword');
	}

	protected function libIspixel($value)
	{
		return $this->toBool($value[0] == 'number' && $value[2] == 'px');
	}

	protected function libIspercentage($value)
	{
		return $this->toBool($value[0] == 'number' && $value[2] == '%');
	}

	protected function libIsem($value)
	{
		return $this->toBool($value[0] == 'number' && $value[2] == 'em');
	}

	protected function libIsrem($value)
	{
		return $this->toBool($value[0] == 'number' && $value[2] == 'rem');
	}

	protected function libRgbahex($color)
	{
		$color = $this->coerceColor($color);
		if (is_null($color))
			$this->throwError('color expected for rgbahex');
		return sprintf('#%02x%02x%02x%02x', isset($color[4]) ? $color[4] * 255 : 255, $color[1], $color[2], $color[3]);
	}

	protected function libArgb($color)
	{
		return $this->libRgbahex($color);
	}

	protected function libE($arg)
	{
		switch ($arg[0]) {
			case 'list':
				$items = $arg[2];
				if (isset($items[0]))
					return $this->libE($items[0]);
				return self::$default_value;
			case 'string':
				$arg[1] = '';
				return $arg;
			case 'keyword':
				return $arg;
			default:
				return array('keyword', $this->compileValue($arg));
		}
	}

	protected function libSprintf($args)
	{
		if ($args[0] != 'list')
			return $args;
		$values = $args[2];
		$string = array_shift($values);
		$template = $this->compileValue($this->libE($string));
		$i = 0;
		if (preg_match_all('/%[dsa]/', $template, $m)) {
			foreach ($m[0] as $match) {
				$val = isset($values[$i]) ? $this->reduce($values[$i]) : array('keyword', '');
				if ($color = $this->coerceColor($val))
					$val = $color;

				$i++;
				$rep = $this->compileValue($this->libE($val));
				$template = preg_replace('/'.self::pregQuote($match).'/', $rep, $template, 1);
			}
		}
		$d = $string[0] == 'string' ? $string[1] : '"';
		return array('string', $d, array($template));
	}

	protected function libFloor($arg)
	{
		$value = $this->assertNumber($arg);
		return array('number', floor($value), $arg[2]);
	}

	protected function libCeil($arg)
	{
		$value = $this->assertNumber($arg);
		return array('number', ceil($value), $arg[2]);
	}

	protected function libRound($arg)
	{
		$value = $this->assertNumber($arg);
		return array('number', round($value), $arg[2]);
	}

	protected function libUnit($arg)
	{
		if ($arg[0] == 'list') {
			list($number, $new_unit) = $arg[2];
			return array('number', $this->assertNumber($number),
				$this->compileValue($this->libE($new_unit)));
		}
		else
			return array('number', $this->assertNumber($arg), '');
	}

	protected function colorArgs($args)
	{
		if ($args[0] != 'list' || count($args[2]) < 2)
			return array(array('color', 0, 0, 0), 0);
		list($color, $delta) = $args[2];
		$color = $this->assertColor($color);
		$delta = (float)$delta[1];
		return array($color, $delta);
	}

	protected function libDarken($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function libLighten($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function libSaturate($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function libDesaturate($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function libSpin($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$hsl = $this->toHSL($color);
		$hsl[1] = $hsl[1] + $delta % 360;
		if ($hsl[1] < 0) $hsl[1] += 360;
		return $this->toRGB($hsl);
	}

	protected function libFadeout($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta / 100);
		return $color;
	}

	protected function libFadein($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta / 100);
		return $color;
	}

	protected function libHue($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[1]);
	}

	protected function libSaturation($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[2]);
	}

	protected function libLightness($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[3]);
	}

	protected function libAlpha($value)
	{
		if (!is_null($color = $this->coerceColor($value)))
			return isset($color[4]) ? $color[4] : 1;
	}

	protected function libFade($args)
	{
		list($color, $alpha) = $this->colorArgs($args);
		$color[4] = $this->clamp($alpha / 100.0);
		return $color;
	}

	protected function libPercentage($arg)
	{
		$num = $this->assertNumber($arg);
		return array('number', $num * 100, '%');
	}

	protected function libMix($args)
	{
		if ($args[0] != 'list' || count($args[2]) < 3)
			$this->throwError('mix expects (color1, color2, weight)');
		list($first, $second, $weight) = $args[2];
		$first = $this->assertColor($first);
		$second = $this->assertColor($second);
		$first_a = $this->libAlpha($first);
		$second_a = $this->libAlpha($second);
		$weight = $weight[1] / 100.0;
		$w = $weight * 2 - 1;
		$a = $first_a - $second_a;
		$w1 = (($w * $a == -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
		$w2 = 1.0 - $w1;
		$new = array('color',
			$w1 * $first[1] + $w2 * $second[1],
			$w1 * $first[2] + $w2 * $second[2],
			$w1 * $first[3] + $w2 * $second[3],
		);
		if ($first_a != 1.0 || $second_a != 1.0)
			$new[] = $first_a * $weight + $second_a * ($weight - 1);
		return $this->fixColor($new);
	}

	protected function libContrast($args)
	{
		if ($args[0] != 'list' || count($args[2]) < 3)
			return array(array('color', 0, 0, 0), 0);
		list($input_color, $dark_color, $light_color) = $args[2];
		$input_color = $this->assertColor($input_color);
		$dark_color = $this->assertColor($dark_color);
		$light_color = $this->assertColor($light_color);
		$hsl = $this->toHSL($input_color);
		if ($hsl[3] > 50)
			return $dark_color;
		return $light_color;
	}

	protected function assertColor($value, $error = 'expected color value')
	{
		$color = $this->coerceColor($value);
		if (is_null($color))
			$this->throwError($error);
		return $color;
	}

	protected function assertNumber($value, $error = 'expecting number')
	{
		if ($value[0] == 'number')
			return $value[1];
		$this->throwError($error);
	}

	protected function toHSL($color)
	{
		if ($color[0] == 'hsl')
			return $color;
		$r = $color[1] / 255;
		$g = $color[2] / 255;
		$b = $color[3] / 255;
		$min = min($r, $g, $b);
		$max = max($r, $g, $b);
		$l = ($min + $max) / 2;
		if ($min == $max)
			$s = $h = 0;
		else {
			if ($l < 0.5)
				$s = ($max - $min) / ($max + $min);
			else
				$s = ($max - $min) / (2.0 - $max - $min);

			if ($r == $max)
				$h = ($g - $b) / ($max - $min);
			elseif ($g == $max)
				$h = 2.0 + ($b - $r) / ($max - $min);
			elseif ($b == $max)
				$h = 4.0 + ($r - $g) / ($max - $min);

		}
		$out = array('hsl', ($h < 0 ? $h + 6 : $h) * 60, $s * 100, $l * 100,);
		if (count($color) > 4)
			$out[] = $color[4];
		return $out;
	}

	protected function toRGBHelper($comp, $temp1, $temp2)
	{
		if ($comp < 0)
			$comp += 1.0;
		elseif ($comp > 1)
			$comp -= 1.0;
		if (6 * $comp < 1)
			return $temp1 + ($temp2 - $temp1) * 6 * $comp;
		if (2 * $comp < 1)
			return $temp2;
		if (3 * $comp < 2)
			return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6;
		return $temp1;
	}

	protected function toRGB($color)
	{
		if ($color[0] == 'color')
			return $color;
		$h = $color[1] / 360;
		$s = $color[2] / 100;
		$l = $color[3] / 100;
		if ($s == 0)
			$r = $g = $b = $l;
		else {
			$temp2 = $l < 0.5 ?
				$l * (1.0 + $s) :
				$l + $s - $l * $s;

			$temp1 = 2.0 * $l - $temp2;
			$r = $this->toRGBHelper($h + 1 / 3, $temp1, $temp2);
			$g = $this->toRGBHelper($h, $temp1, $temp2);
			$b = $this->toRGBHelper($h - 1 / 3, $temp1, $temp2);
		}
		$out = array('color', $r * 255, $g * 255, $b * 255);
		if (count($color) > 4)
			$out[] = $color[4];
		return $out;
	}

	protected function clamp($v, $max = 1, $min = 0)
	{
		return min($max, max($min, $v));
	}

	protected function funcToColor($func)
	{
		$fname = $func[1];
		if ($func[2][0] != 'list')
			return false;
		$raw_components = $func[2][2];
		if ($fname == 'hsl' || $fname == 'hsla') {
			$hsl = array('hsl');
			$i = 0;
			foreach ($raw_components as $c) {
				$val = $this->reduce($c);
				$val = isset($val[1]) ? (float)$val[1] : 0;
				if ($i == 0)
					$clamp = 360;
				elseif ($i < 3)
					$clamp = 100;
				else
					$clamp = 1;
				$hsl[] = $this->clamp($val, $clamp);
				$i++;
			}
			$count_hsl = count($hsl);
			while ($count_hsl < 4) $hsl[] = 0;
			return $this->toRGB($hsl);
		} elseif ($fname == 'rgb' || $fname == 'rgba') {
			$components = array();
			$i = 1;
			foreach	($raw_components as $c) {
				$c = $this->reduce($c);
				if ($i < 4) {
					if ($c[0] == 'number' && $c[2] == '%')
						$components[] = 255 * ($c[1] / 100);
					else
						$components[] = (float)$c[1];
				} elseif ($i == 4) {
					if ($c[0] == 'number' && $c[2] == '%')
						$components[] = 1.0 * ($c[1] / 100);
					else
						$components[] = (float)$c[1];
				}
				else
					break;
				$i++;
			}
			$count_components = count($components);
			while ($count_components < 3)
				$components[] = 0;
			array_unshift($components, 'color');
			return $this->fixColor($components);
		}
		return false;
	}

	protected function reduce($value, $for_expression = false)
	{
		switch ($value[0]) {
			case 'interpolate':
				$reduced = $this->reduce($value[1]);
				$var = $this->compileValue($reduced);
				$res = $this->reduce(array('variable', $this->v_prefix.$var));
				if (empty($value[2]))
					$res = $this->libE($res);
				return $res;
			case 'variable':
				$key = $value[1];
				if (is_array($key)) {
					$key = $this->reduce($key);
					$key = $this->v_prefix.$this->compileValue($this->libE($key));
				}
				$seen =& $this->env->seenNames;
				if (!empty($seen[$key]))
					$this->throwError("infinite loop detected: $key");
				$seen[$key] = true;
				$out = $this->reduce($this->get($key, self::$default_value));
				$seen[$key] = false;
				return $out;
			case 'list':
				foreach ($value[2] as &$item)
					$item = $this->reduce($item, $for_expression);
				return $value;
			case 'expression':
				return $this->evaluate($value);
			case 'string':
				foreach ($value[2] as &$part) {
					if (is_array($part)) {
						$strip = $part[0] == 'variable';
						$part = $this->reduce($part);
						if ($strip)
							$part = $this->libE($part);
					}
				}
				return $value;
			case 'escape':
				list(,$inner) = $value;
				return $this->libE($this->reduce($inner));
			case 'function':
				$color = $this->funcToColor($value);
				if ($color)
					return $color;
				list(, $name, $args) = $value;
				if ($name == '%')
					$name = 'sprintf';
				$f = isset($this->lib_functions[$name]) ? $this->lib_functions[$name] : array($this, 'lib'.$name);
				if (is_callable($f)) {
					if ($args[0] == 'list')
						$args = self::compressList($args[2], $args[1]);
					$ret = call_user_func($f, $this->reduce($args, true), $this);
					if (is_null($ret)) {
						return array('string', '', array(
							$name, '(', $args, ')'
						));
					}
					if (is_numeric($ret))
						$ret = array('number', $ret, '');
					elseif (!is_array($ret))
						$ret = array('keyword', $ret);
					return $ret;
				}
				$value[2] = $this->reduce($value[2]);
				return $value;
			case 'unary':
				list(, $op, $exp) = $value;
				$exp = $this->reduce($exp);
				if ($exp[0] == 'number') {
					switch ($op) {
						case '+':
							return $exp;
						case '-':
							$exp[1] *= -1;
							return $exp;
					}
				}
				return array('string', '', array($op, $exp));
		}
		if ($for_expression) {
			switch ($value[0]) {
				case 'keyword':
					if ($color = $this->coerceColor($value))
						return $color;
					break;
				case 'raw_color':
					return $this->coerceColor($value);
			}
		}
		return $value;
	}

	protected function coerceColor($value)
	{
		switch ($value[0]) {
			case 'color':
				return $value;
			case 'raw_color':
				$c = array('color', 0, 0, 0);
				$color_str = Tools::substr($value[1], 1);
				$num = hexdec($color_str);
				$width = Tools::strlen($color_str) == 3 ? 16 : 256;
				for ($i = 3; $i > 0; $i--) {
					$t = $num % $width;
					$num /= $width;
					$c[$i] = $t * (256 / $width) + $t * floor(16 / $width);
				}
				return $c;
			case 'keyword':
				$name = $value[1];
				if (isset(self::$css_colors[$name])) {
					$rgba = explode(',', self::$css_colors[$name]);
					if (isset($rgba[3]))
						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
					return array('color', $rgba[0], $rgba[1], $rgba[2]);
				}
				return null;
		}
	}

	protected function coerceString($value)
	{
		switch ($value[0]) {
			case 'string':
				return $value;
			case 'keyword':
				return array('string', '', array($value[1]));
		}
		return null;
	}

	protected function flattenList($value)
	{
		if ($value[0] == 'list' && count($value[2]) == 1)
			return $this->flattenList($value[2][0]);
		return $value;
	}

	protected function toBool($a)
	{
		if ($a)
			return self::$true;
		else
			return self::$false;
	}

	protected function evaluate($exp)
	{
		list(, $op, $left, $right, $white_before, $white_after) = $exp;
		$left = $this->reduce($left, true);
		$right = $this->reduce($right, true);
		if ($left_color = $this->coerceColor($left))
			$left = $left_color;
		if ($right_color = $this->coerceColor($right))
			$right = $right_color;
		$ltype = $left[0];
		$rtype = $right[0];
		if ($op == 'and')
			return $this->toBool($left == self::$true && $right == self::$true);
		if ($op == '=')
			return $this->toBool($this->eq($left, $right) );
		if ($op == '+' && !is_null($str = $this->stringConcatenate($left, $right)))
			return $str;
		$fname = "op${ltype}${rtype}";
		if (is_callable(array($this, $fname))) {
			$out = $this->$fname($op, $left, $right);
			if (!is_null($out))
				return $out;
		}
		$padded_op = $op;
		if ($white_before)
			$padded_op = ' '.$padded_op;
		if ($white_after)
			$padded_op .= ' ';
		return array('string', '', array($left, $padded_op, $right));
	}

	protected function stringConcatenate($left, $right)
	{
		if ($str_left = $this->coerceString($left)) {
			if ($right[0] == 'string')
				$right[1] = '';
			$str_left[2][] = $right;
			return $str_left;
		}
		if ($str_right = $this->coerceString($right)) {
			array_unshift($str_right[2], $left);
			return $str_right;
		}
	}

	protected function fixColor($c)
	{
		foreach (range(1, 3) as $i) {
			if ($c[$i] < 0)
				$c[$i] = 0;
			if ($c[$i] > 255)
				$c[$i] = 255;
		}
		return $c;
	}

	protected function opNumberColor($op, $lft, $rgt)
	{
		if ($op == '+' || $op == '*')
			return $this->opColorNumber($op, $rgt, $lft);
	}

	protected function opColorNumber($op, $lft, $rgt)
	{
		if ($rgt[0] == '%')
			$rgt[1] /= 100;
		return $this->opColorColor($op, $lft, array_fill(1, count($lft) - 1, $rgt[1]));
	}

	protected function opColorColor($op, $left, $right)
	{
		$out = array('color');
		$max = count($left) > count($right) ? count($left) : count($right);
		foreach (range(1, $max - 1) as $i) {
			$lval = isset($left[$i]) ? $left[$i] : 0;
			$rval = isset($right[$i]) ? $right[$i] : 0;
			switch ($op) {
				case '+':
					$out[] = $lval + $rval;
					break;
				case '-':
					$out[] = $lval - $rval;
					break;
				case '*':
					$out[] = $lval * $rval;
					break;
				case '%':
					$out[] = $lval % $rval;
					break;
				case '/':
					if ($rval == 0)
						$this->throwError("evaluate error: can't divide by zero");
					$out[] = $lval / $rval;
					break;
				default:
					$this->throwError('evaluate error: color op number failed on op '.$op);
			}
		}
		return $this->fixColor($out);
	}

	public function libRed($color)
	{
		$color = $this->coerceColor($color);
		if (is_null($color))
			$this->throwError('color expected for red()');
		return $color[1];
	}

	public function libGreen($color)
	{
		$color = $this->coerceColor($color);
		if (is_null($color))
			$this->throwError('color expected for green()');
		return $color[2];
	}

	public function libBlue($color)
	{
		$color = $this->coerceColor($color);
		if (is_null($color))
			$this->throwError('color expected for blue()');
		return $color[3];
	}

	protected function opNumberNumber($op, $left, $right)
	{
		$unit = empty($left[2]) ? $right[2] : $left[2];
		$value = 0;
		switch ($op) {
			case '+':
				$value = $left[1] + $right[1];
				break;
			case '*':
				$value = $left[1] * $right[1];
				break;
			case '-':
				$value = $left[1] - $right[1];
				break;
			case '%':
				$value = $left[1] % $right[1];
				break;
			case '/':
				if ($right[1] == 0)
					$this->throwError('parse error: divide by zero');
				$value = $left[1] / $right[1];
				break;
			case '<':
				return $this->toBool($left[1] < $right[1]);
			case '>':
				return $this->toBool($left[1] > $right[1]);
			case '>=':
				return $this->toBool($left[1] >= $right[1]);
			case '=<':
				return $this->toBool($left[1] <= $right[1]);
			default:
				$this->throwError('parse error: unknown number operator: '.$op);
		}
		return array('number', $value, $unit);
	}

	protected function makeOutputBlock($type, $selectors = null)
	{
		$b = new stdclass;
		$b->lines = array();
		$b->children = array();
		$b->selectors = $selectors;
		$b->type = $type;
		$b->parent = $this->scope;
		return $b;
	}

	protected function pushEnv($block = null)
	{
		$e = new stdclass;
		$e->parent = $this->env;
		$e->store = array();
		$e->block = $block;
		$this->env = $e;
		return $e;
	}

	protected function popEnv()
	{
		$old = $this->env;
		$this->env = $this->env->parent;
		return $old;
	}

	protected function set($name, $value)
	{
		$this->env->store[$name] = $value;
	}

	protected function get($name, $default = null)
	{
		$current = $this->env;
		$is_arguments = $name == $this->v_prefix.'arguments';
		while ($current) {
			if ($is_arguments && isset($current->arguments))
				return array('list', ' ', $current->arguments);
			if (isset($current->store[$name]))
				return $current->store[$name];
			else {
				$current = isset($current->store_parent) ? $current->store_parent : $current->parent;
			}
		}
		return $default;
	}

	protected function injectVariables($args)
	{
		$value = null;
		$this->pushEnv();
		$parser = new LesscParser($this, __METHOD__);
		foreach ($args as $name => $str_value) {
			if ($name{0} != '@')
				$name = '@'.$name;
			$parser->count = 0;
			$parser->buffer = (string)$str_value;
			if (!$parser->propertyValue($value))
				throw new Exception("failed to parse passed in variable $name: $str_value");
			$this->set($name, $value);
		}
	}

	public function __construct($fname = null)
	{
		if ($fname !== null)
			$this->parse_file = $fname;
	}

	public function compile($string, $name = null)
	{
		$locale = setlocale(LC_NUMERIC, 0);
		setlocale(LC_NUMERIC, 'C');
		$this->parser = $this->makeParser($name);
		$root = $this->parser->parse($string);
		$this->env = null;
		$this->scope = null;
		$this->formatter = $this->newFormatter();
		if (!empty($this->registered_vars))
			$this->injectVariables($this->registered_vars);
		$this->source_parser = $this->parser;
		$this->compileBlock($root);
		ob_start();
		$this->formatter->block($this->scope);
		$out = ob_get_clean();
		setlocale(LC_NUMERIC, $locale);
		return $out;
	}

	public function compileFile($fname, $out_fname = null)
	{
		if (!is_readable($fname))
			throw new Exception('load error: failed to find '.$fname);
		$pi = pathinfo($fname);
		$old_import = $this->import_dir;
		$this->import_dir = (array)$this->import_dir;
		$this->import_dir[] = $pi['dirname'].'/';
		$this->all_parsed_files = array();
		$this->addParsedFile($fname);
		$out = $this->compile(Tools::file_get_contents($fname), $fname);
		$this->import_dir = $old_import;
		if ($out_fname !== null)
			return file_put_contents($out_fname, $out);
		return $out;
	}

	public function checkedCompile($in, $out)
	{
		if (!is_file($out) || filemtime($in) > filemtime($out)) {
			$this->compileFile($in, $out);
			return true;
		}
		return false;
	}

	public function cachedCompile($in, $force = false)
	{
		$root = null;
		if (is_string($in))
			$root = $in;
		elseif (is_array($in) && isset($in['root'])) {
			if ($force || ! isset($in['files']))
				$root = $in['root'];
			elseif (isset($in['files']) && is_array($in['files'])) {
				foreach ($in['files'] as $fname => $ftime) {
					if (!file_exists($fname) || filemtime($fname) > $ftime) {
						$root = $in['root'];
						break;
					}
				}
			}
		} else
			return null;
		if ($root !== null) {
			$out = array();
			$out['root'] = $root;
			$out['compiled'] = $this->compileFile($root);
			$out['files'] = $this->allParsedFiles();
			$out['updated'] = time();
			return $out;
		} else
			return $in;
	}

	public function parse($str = null, $initial_variables = null)
	{
		if (is_array($str)) {
			$initial_variables = $str;
			$str = null;
		}
		$old_vars = $this->registered_vars;
		if ($initial_variables !== null)
			$this->setVariables($initial_variables);
		if ($str == null) {
			if (empty($this->parse_file))
				throw new exception('nothing to parse');
			$out = $this->compileFile($this->parse_file);
		} else
			$out = $this->compile($str);
		$this->registered_vars = $old_vars;
		return $out;
	}

	protected function makeParser($name)
	{
		$parser = new LesscParser($this, $name);
		$parser->write_comments = $this->preserve_comments;
		return $parser;
	}

	public function setFormatter($name)
	{
		$this->formatter_name = $name;
	}

	protected function newFormatter()
	{
		$className = 'LesscFormatterLessjs';
		if (!empty($this->formatter_name)) {
			if (!is_string($this->formatter_name))
				return $this->formatter_name;
			$className = "LesscFormatter$this->formatter_name";
		}
		return new $className;
	}

	public function setPreserveComments($preserve)
	{
		$this->preserve_comments = $preserve;
	}

	public function registerFunction($name, $func)
	{
		$this->lib_functions[$name] = $func;
	}

	public function unregisterFunction($name)
	{
		unset($this->lib_functions[$name]);
	}

	public function setVariables($variables)
	{
		$this->registered_vars = array_merge($this->registered_vars, $variables);
	}

	public function unsetVariable($name)
	{
		unset($this->registered_vars[$name]);
	}

	public function setImportDir($dirs)
	{
		$this->import_dir = (array)$dirs;
	}

	public function addImportDir($dir)
	{
		$this->import_dir = (array)$this->import_dir;
		$this->import_dir[] = $dir;
	}

	public function allParsedFiles()
	{
		return $this->all_parsed_files;
	}

	protected function addParsedFile($file)
	{
		$this->all_parsed_files[realpath($file)] = filemtime($file);
	}

	protected function throwError($msg = null)
	{
		if ($this->source_loc >= 0)
			$this->source_parser->throwError($msg, $this->source_loc);
		throw new exception($msg);
	}

	public static function ccompile($in, $out, $less = null)
	{
		if ($less === null)
			$less = new self;
		return $less->checkedCompile($in, $out);
	}

	public static function cexecute($in, $force = false, $less = null)
	{
		if ($less === null)
			$less = new self;
		return $less->cachedCompile($in, $force);
	}

	static protected $css_colors = array(
		'aliceblue' => '240,248,255',
		'antiquewhite' => '250,235,215',
		'aqua' => '0,255,255',
		'aquamarine' => '127,255,212',
		'azure' => '240,255,255',
		'beige' => '245,245,220',
		'bisque' => '255,228,196',
		'black' => '0,0,0',
		'blanchedalmond' => '255,235,205',
		'blue' => '0,0,255',
		'blueviolet' => '138,43,226',
		'brown' => '165,42,42',
		'burlywood' => '222,184,135',
		'cadetblue' => '95,158,160',
		'chartreuse' => '127,255,0',
		'chocolate' => '210,105,30',
		'coral' => '255,127,80',
		'cornflowerblue' => '100,149,237',
		'cornsilk' => '255,248,220',
		'crimson' => '220,20,60',
		'cyan' => '0,255,255',
		'darkblue' => '0,0,139',
		'darkcyan' => '0,139,139',
		'darkgoldenrod' => '184,134,11',
		'darkgray' => '169,169,169',
		'darkgreen' => '0,100,0',
		'darkgrey' => '169,169,169',
		'darkkhaki' => '189,183,107',
		'darkmagenta' => '139,0,139',
		'darkolivegreen' => '85,107,47',
		'darkorange' => '255,140,0',
		'darkorchid' => '153,50,204',
		'darkred' => '139,0,0',
		'darksalmon' => '233,150,122',
		'darkseagreen' => '143,188,143',
		'darkslateblue' => '72,61,139',
		'darkslategray' => '47,79,79',
		'darkslategrey' => '47,79,79',
		'darkturquoise' => '0,206,209',
		'darkviolet' => '148,0,211',
		'deeppink' => '255,20,147',
		'deepskyblue' => '0,191,255',
		'dimgray' => '105,105,105',
		'dimgrey' => '105,105,105',
		'dodgerblue' => '30,144,255',
		'firebrick' => '178,34,34',
		'floralwhite' => '255,250,240',
		'forestgreen' => '34,139,34',
		'fuchsia' => '255,0,255',
		'gainsboro' => '220,220,220',
		'ghostwhite' => '248,248,255',
		'gold' => '255,215,0',
		'goldenrod' => '218,165,32',
		'gray' => '128,128,128',
		'green' => '0,128,0',
		'greenyellow' => '173,255,47',
		'grey' => '128,128,128',
		'honeydew' => '240,255,240',
		'hotpink' => '255,105,180',
		'indianred' => '205,92,92',
		'indigo' => '75,0,130',
		'ivory' => '255,255,240',
		'khaki' => '240,230,140',
		'lavender' => '230,230,250',
		'lavenderblush' => '255,240,245',
		'lawngreen' => '124,252,0',
		'lemonchiffon' => '255,250,205',
		'lightblue' => '173,216,230',
		'lightcoral' => '240,128,128',
		'lightcyan' => '224,255,255',
		'lightgoldenrodyellow' => '250,250,210',
		'lightgray' => '211,211,211',
		'lightgreen' => '144,238,144',
		'lightgrey' => '211,211,211',
		'lightpink' => '255,182,193',
		'lightsalmon' => '255,160,122',
		'lightseagreen' => '32,178,170',
		'lightskyblue' => '135,206,250',
		'lightslategray' => '119,136,153',
		'lightslategrey' => '119,136,153',
		'lightsteelblue' => '176,196,222',
		'lightyellow' => '255,255,224',
		'lime' => '0,255,0',
		'limegreen' => '50,205,50',
		'linen' => '250,240,230',
		'magenta' => '255,0,255',
		'maroon' => '128,0,0',
		'mediumaquamarine' => '102,205,170',
		'mediumblue' => '0,0,205',
		'mediumorchid' => '186,85,211',
		'mediumpurple' => '147,112,219',
		'mediumseagreen' => '60,179,113',
		'mediumslateblue' => '123,104,238',
		'mediumspringgreen' => '0,250,154',
		'mediumturquoise' => '72,209,204',
		'mediumvioletred' => '199,21,133',
		'midnightblue' => '25,25,112',
		'mintcream' => '245,255,250',
		'mistyrose' => '255,228,225',
		'moccasin' => '255,228,181',
		'navajowhite' => '255,222,173',
		'navy' => '0,0,128',
		'oldlace' => '253,245,230',
		'olive' => '128,128,0',
		'olivedrab' => '107,142,35',
		'orange' => '255,165,0',
		'orangered' => '255,69,0',
		'orchid' => '218,112,214',
		'palegoldenrod' => '238,232,170',
		'palegreen' => '152,251,152',
		'paleturquoise' => '175,238,238',
		'palevioletred' => '219,112,147',
		'papayawhip' => '255,239,213',
		'peachpuff' => '255,218,185',
		'peru' => '205,133,63',
		'pink' => '255,192,203',
		'plum' => '221,160,221',
		'powderblue' => '176,224,230',
		'purple' => '128,0,128',
		'red' => '255,0,0',
		'rosybrown' => '188,143,143',
		'royalblue' => '65,105,225',
		'saddlebrown' => '139,69,19',
		'salmon' => '250,128,114',
		'sandybrown' => '244,164,96',
		'seagreen' => '46,139,87',
		'seashell' => '255,245,238',
		'sienna' => '160,82,45',
		'silver' => '192,192,192',
		'skyblue' => '135,206,235',
		'slateblue' => '106,90,205',
		'slategray' => '112,128,144',
		'slategrey' => '112,128,144',
		'snow' => '255,250,250',
		'springgreen' => '0,255,127',
		'steelblue' => '70,130,180',
		'tan' => '210,180,140',
		'teal' => '0,128,128',
		'thistle' => '216,191,216',
		'tomato' => '255,99,71',
		'transparent' => '0,0,0,0',
		'turquoise' => '64,224,208',
		'violet' => '238,130,238',
		'wheat' => '245,222,179',
		'white' => '255,255,255',
		'whitesmoke' => '245,245,245',
		'yellow' => '255,255,0',
		'yellowgreen' => '154,205,50'
	);
}

class LesscParser
{
	static protected $next_block_id = 0;
	static protected $precedence = array(
		'=<' => 0,
		'>=' => 0,
		'=' => 0,
		'<' => 0,
		'>' => 0,
		'+' => 1,
		'-' => 1,
		'*' => 2,
		'/' => 2,
		'%' => 2,
	);
	static protected $white_pattern;
	static protected $comment_multi;
	static protected $comment_single = '//';
	static protected $comment_multi_left = '/*';
	static protected $comment_multi_right = '*/';
	static protected $operator_string;
	static protected $supress_division_props = array('/border-radius$/i', '/^font$/i');

	protected $block_directives = array('font-face', 'keyframes', 'page', '-moz-document');
	protected $line_directives = array('charset');
	protected $in_parens = false;

	static protected $literal_cache = array();

	public function __construct($lessc, $source_name = null)
	{
		$this->eat_white_default = true;
		$this->lessc = $lessc;
		$this->source_name = $source_name;
		$this->write_comments = false;
		if (!self::$operator_string) {
			self::$operator_string = '('.implode('|', array_map(array('lessc', 'pregQuote'), array_keys(self::$precedence))).')';
			$comment_single = Lessc::pregQuote(self::$comment_single);
			$comment_multi_left = Lessc::pregQuote(self::$comment_multi_left);
			$comment_multi_right = Lessc::pregQuote(self::$comment_multi_right);
			self::$comment_multi = $comment_multi_left.'.*?'.$comment_multi_right;
			self::$white_pattern = '/'.$comment_single.'[^\n]*\s*|('.self::$comment_multi.')\s*|\s+/Ais';
		}
	}

	public function parse($buffer)
	{
		$this->count = 0;
		$this->line = 1;
		$this->env = null;
		$this->buffer = $this->write_comments ? $buffer : $this->removeComments($buffer);
		$this->pushSpecialBlock('root');
		$this->eat_white_default = true;
		$this->seen_comments = array();
		$this->whitespace();
		while (false !== $this->parseChunk());
		if ($this->count != Tools::strlen($this->buffer))
			$this->throwError();
		if (!is_null($this->env->parent))
			throw new exception('parse error: unclosed block');
		return $this->env;
	}

	protected function parseChunk()
	{
		$value = null;
		$key = null;
		$media_queries = null;
		$dir_name = null;
		$dir_value = null;
		$var = null;
		$import_value = null;
		$tag = null;
		$args = null;
		$is_vararg = null;
		$guards = null;
		$tags = null;
		$suffix = null;
		if (empty($this->buffer))
			return false;
		$s = $this->seek();
		if ($this->keyword($key) && $this->assign() && $this->propertyValue($value, $key) && $this->end()) {
			$this->append(array('assign', $key, $value), $s);
			return true;
		} else
			$this->seek($s);
		if ($this->literal('@', false)) {
			$this->count--;
			if ($this->literal('@media')) {
				if (($this->mediaQueryList($media_queries) || true) && $this->literal('{')) {
					$media = $this->pushSpecialBlock('media');
					$media->queries = is_null($media_queries) ? array() : $media_queries;
					return true;
				} else {
					$this->seek($s);
					return false;
				}
			}
			if ($this->literal('@', false) && $this->keyword($dir_name)) {
				if ($this->isDirective($dir_name, $this->block_directives)) {
					if (($this->openString('{', $dir_value, null, array(';')) || true) && $this->literal('{')) {
						$dir = $this->pushSpecialBlock('directive');
						$dir->name = $dir_name;
						if (isset($dir_value))
							$dir->value = $dir_value;
						return true;
					}
				} elseif ($this->isDirective($dir_name, $this->line_directives)) {
					if ($this->propertyValue($dir_value) && $this->end()) {
						$this->append(array('directive', $dir_name, $dir_value));
						return true;
					}
				}
			}
			$this->seek($s);
		}
		if ($this->variable($var) && $this->assign() && $this->propertyValue($value) && $this->end()) {
			$this->append(array('assign', $var, $value), $s);
			return true;
		} else
			$this->seek($s);
		if ($this->import($import_value)) {
			$this->append($import_value, $s);
			return true;
		}
		if ($this->tag($tag, true) && $this->argumentDef($args, $is_vararg) && ($this->guards($guards) || true) && $this->literal('{')) {
			$block = $this->pushBlock($this->fixTags(array($tag)));
			$block->args = $args;
			$block->is_vararg = $is_vararg;
			if (!empty($guards))
				$block->guards = $guards;
			return true;
		} else
			$this->seek($s);
		if ($this->tags($tags) && $this->literal('{')) {
			$tags = $this->fixTags($tags);
			$this->pushBlock($tags);
			return true;
		} else
			$this->seek($s);
		if ($this->literal('}', false)) {
			try {
				$block = $this->pop();
			} catch (exception $e) {
				$this->seek($s);
				$this->throwError($e->getMessage());
			}
			$hidden = false;
			if (is_null($block->type)) {
				$hidden = true;
				if (!isset($block->args)) {
					foreach ($block->tags as $tag) {
						if (!is_string($tag) || $tag{0} != $this->lessc->m_prefix) {
							$hidden = false;
							break;
						}
					}
				}
				foreach ($block->tags as $tag) {
					if (is_string($tag))
						$this->env->children[$tag][] = $block;
				}
			}
			if (!$hidden)
				$this->append(array('block', $block), $s);
			$this->whitespace();
			return true;
		}
		if ($this->mixinTags($tags) && ($this->argumentValues($argv) || true) && ($this->keyword($suffix) || true) && $this->end()) {
			$tags = $this->fixTags($tags);
			$this->append(array('mixin', $tags, $argv, $suffix), $s);
			return true;
		} else
			$this->seek($s);
		if ($this->literal(';'))
			return true;
		return false;
	}

	protected function isDirective($dir_name, $directives)
	{
		$pattern = implode('|',
			array_map(array('lessc', 'pregQuote'), $directives));
		$pattern = '/^(-[a-z-]+-)?('.$pattern.')$/i';
		return preg_match($pattern, $dir_name);
	}

	protected function fixTags($tags)
	{
		foreach ($tags as &$tag) {
			if ($tag{0} == $this->lessc->v_prefix)
				$tag[0] = $this->lessc->m_prefix;
		}
		return $tags;
	}

	protected function expressionList(&$exps)
	{
		$exp = null;
		$values = array();
		while ($this->expression($exp))
			$values[] = $exp;
		if (count($values) == 0)
			return false;
		$exps = Lessc::compressList($values, ' ');
		return true;
	}

	protected function expression(&$out)
	{
		$lhs = null;
		$rhs = null;
		if ($this->value($lhs)) {
			$out = $this->expHelper($lhs, 0);
			if (!empty($this->env->supressedDivision)) {
				unset($this->env->supressedDivision);
				$s = $this->seek();
				if ($this->literal('/') && $this->value($rhs)) {
					$out = array('list', '',
						array($out, array('keyword', '/'), $rhs));
				} else
					$this->seek($s);
			}
			return true;
		}
		return false;
	}

	protected function expHelper($lhs, $min_p)
	{
		$rhs = null;
		$m = null;
		$next = null;
		$this->in_exp = true;
		$ss = $this->seek();
		while (true) {
			$white_before = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]);
			$need_white = $white_before && !$this->in_parens;
			if ($this->match(self::$operator_string.($need_white ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $min_p) {
				if (!$this->in_parens && isset($this->env->current_property) && $m[1] == '/' && empty($this->env->supressedDivision)) {
					foreach (self::$supress_division_props as $pattern) {
						if (preg_match($pattern, $this->env->current_property)) {
							$this->env->supressedDivision = true;
							break 2;
						}
					}
				}
				$white_after = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]);
				if (!$this->value($rhs))
					break;
				if ($this->peek(self::$operator_string, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]])
					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
				$lhs = array('expression', $m[1], $lhs, $rhs, $white_before, $white_after);
				$ss = $this->seek();
				continue;
			}
			break;
		}
		$this->seek($ss);
		return $lhs;
	}

	public function propertyValue(&$value, $key_name = null)
	{
		$v = null;
		$values = array();
		if ($key_name !== null)
			$this->env->current_property = $key_name;
		$s = null;
		while ($this->expressionList($v)) {
			$values[] = $v;
			$s = $this->seek();
			if (!$this->literal(','))
				break;
		}
		if ($s)
			$this->seek($s);
		if ($key_name !== null)
			unset($this->env->current_property);
		if (count($values) == 0)
			return false;
		$value = Lessc::compressList($values, ', ');
		return true;
	}

	protected function parenValue(&$out)
	{
		$exp = null;
		$s = $this->seek();
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != '(')
			return false;
		$in_parens = $this->in_parens;
		if ($this->literal('(') && ($this->in_parens = true) && $this->expression($exp) && $this->literal(')')) {
			$out = $exp;
			$this->in_parens = $in_parens;
			return true;
		} else {
			$this->in_parens = $in_parens;
			$this->seek($s);
		}
		return false;
	}

	protected function value(&$value)
	{
		$var = null;
		$m = null;
		$inner = null;
		$word = null;
		$str = null;
		$s = $this->seek();
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == '-') {
			if ($this->literal('-', false) && (($this->variable($inner) && $inner = array('variable', $inner)) || $this->unit($inner) || $this->parenValue($inner))) {
				$value = array('unary', '-', $inner);
				return true;
			} else
				$this->seek($s);
		}
		if ($this->parenValue($value))
			return true;
		if ($this->unit($value))
			return true;
		if ($this->color($value))
			return true;
		if ($this->func($value))
			return true;
		if ($this->string($value))
			return true;
		if ($this->keyword($word)) {
			$value = array('keyword', $word);
			return true;
		}
		if ($this->variable($var)) {
			$value = array('variable', $var);
			return true;
		}
		if ($this->literal('~') && $this->string($str)) {
			$value = array('escape', $str);
			return true;
		} else
			$this->seek($s);
		if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
			$value = array('keyword', '\\'.$m[1]);
			return true;
		} else
			$this->seek($s);
		return false;
	}

	protected function import(&$out)
	{
		$value = null;
		if (!$this->literal('@import'))
			return false;
		if ($this->propertyValue($value)) {
			$out = array('import', $value);
			return true;
		}
	}

	protected function mediaQueryList(&$out)
	{
		$list = null;
		if ($this->genericList($list, 'mediaQuery', ',', false)) {
			$out = $list[2];
			return true;
		}
		return false;
	}

	protected function mediaQuery(&$out)
	{
		$media_type = null;
		$s = $this->seek();
		$expressions = null;
		$parts = array();
		if (($this->literal('only') && ($only = true) || $this->literal('not') && ($not = true) || true) && $this->keyword($media_type)) {
			$prop = array('mediaType');
			if (isset($only))
				$prop[] = 'only';
			if (isset($not))
				$prop[] = 'not';
			$prop[] = $media_type;
			$parts[] = $prop;
		} else
			$this->seek($s);
		if (!empty($media_type) && !$this->literal('and'))
			return;
		else {
			$this->genericList($expressions, 'mediaExpression', 'and', false);
			if (is_array($expressions))
				$parts = array_merge($parts, $expressions[2]);
		}
		if (count($parts) == 0) {
			$this->seek($s);
			return false;
		}
		$out = $parts;
		return true;
	}

	protected function mediaExpression(&$out)
	{
		$feature = null;
		$variable = null;
		$s = $this->seek();
		$value = null;
		if ($this->literal('(') && $this->keyword($feature) && ($this->literal(':') && $this->expression($value) || true) && $this->literal(')')) {
			$out = array('mediaExp', $feature);
			if ($value)
				$out[] = $value;
			return true;
		} elseif ($this->variable($variable)) {
			$out = array('variable', $variable);
			return true;
		}
		$this->seek($s);
		return false;
	}

	protected function openString($end, &$out, $nesting_open = null, $reject_strs = null)
	{
		$m = null;
		$str = null;
		$inter = null;
		$old_white = $this->eat_white_default;
		$this->eat_white_default = false;
		$stop = array("'", '"', '@{', $end);
		$stop = array_map(array('lessc', 'pregQuote'), $stop);
		if (!is_null($reject_strs))
			$stop = array_merge($stop, $reject_strs);
		$patt = '(.*?)('.implode('|', $stop).')';
		$nesting_level = 0;
		$content = array();
		while ($this->match($patt, $m, false)) {
			if (!empty($m[1])) {
				$content[] = $m[1];
				if ($nesting_open)
					$nesting_level += substr_count($m[1], $nesting_open);
			}
			$tok = $m[2];
			$this->count -= Tools::strlen($tok);
			if ($tok == $end) {
				if ($nesting_level == 0)
					break;
				else
					$nesting_level--;
			}
			if (($tok == "'" || $tok == '"') && $this->string($str)) {
				$content[] = $str;
				continue;
			}
			if ($tok == '@{' && $this->interpolation($inter)) {
				$content[] = $inter;
				continue;
			}
			if (!empty($reject_strs) && in_array($tok, $reject_strs))
				break;
			$content[] = $tok;
			$this->count += Tools::strlen($tok);
		}
		$this->eat_white_default = $old_white;
		if (count($content) == 0)
			return false;
		if (is_string(end($content)))
			$content[count($content) - 1] = rtrim(end($content));
		$out = array('string', '', $content);
		return true;
	}

	protected function string(&$out)
	{
		$m = null;
		$inter = null;
		$s = $this->seek();
		if ($this->literal('"', false))
			$delim = '"';
		elseif ($this->literal("'", false))
			$delim = "'";
		else
			return false;
		$content = array();
		$patt = '([^\n]*?)(@\{|\\\\|'.Lessc::pregQuote($delim).')';
		$old_white = $this->eat_white_default;
		$this->eat_white_default = false;
		while ($this->match($patt, $m, false)) {
			$content[] = $m[1];
			if ($m[2] == '@{') {
				$this->count -= Tools::strlen($m[2]);
				if ($this->interpolation($inter, false))
					$content[] = $inter;
				else {
					$this->count += Tools::strlen($m[2]);
					$content[] = '@{';
				}
			} elseif ($m[2] == '\\') {
				$content[] = $m[2];
				if ($this->literal($delim, false))
					$content[] = $delim;
			} else {
				$this->count -= Tools::strlen($delim);
				break;
			}
		}
		$this->eat_white_default = $old_white;
		if ($this->literal($delim)) {
			$out = array('string', $delim, $content);
			return true;
		}
		$this->seek($s);
		return false;
	}

	protected function interpolation(&$out)
	{
		$interp = null;
		$old_white = $this->eat_white_default;
		$this->eat_white_default = true;
		$s = $this->seek();
		if ($this->literal('@{') && $this->openString('}', $interp, null, array("'", '"', ';')) && $this->literal('}', false)) {
			$out = array('interpolate', $interp);
			$this->eat_white_default = $old_white;
			if ($this->eat_white_default)
				$this->whitespace();
			return true;
		}
		$this->eat_white_default = $old_white;
		$this->seek($s);
		return false;
	}

	protected function unit(&$unit)
	{
		$m = null;
		if (isset($this->buffer[$this->count])) {
			$char = $this->buffer[$this->count];
			if (!ctype_digit($char) && $char != '.')
				return false;
		}
		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
			$unit = array('number', $m[1], empty($m[2]) ? '' : $m[2]);
			return true;
		}
		return false;
	}

	protected function color(&$out)
	{
		$m = null;
		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
			if (Tools::strlen($m[1]) > 7)
				$out = array('string', '', array($m[1]));
			else
				$out = array('raw_color', $m[1]);
			return true;
		}
		return false;
	}

	protected function argumentValues(&$args, $delim = ',')
	{
		$value = null;
		$s = $this->seek();
		if (!$this->literal('('))
			return false;
		$values = array();
		while (true) {
			if ($this->expressionList($value))
				$values[] = $value;
			if (!$this->literal($delim))
				break;
			else {
				if ($value == null) $values[] = null;
				$value = null;
			}
		}
		if (!$this->literal(')')) {
			$this->seek($s);
			return false;
		}
		$args = $values;
		return true;
	}

	protected function argumentDef(&$args, &$is_vararg, $delim = ',')
	{
		$value = null;
		$vname = null;
		$literal = null;
		$s = $this->seek();
		if (!$this->literal('('))
			return false;
		$values = array();
		$is_vararg = false;
		while (true) {
			if ($this->literal('...')) {
				$is_vararg = true;
				break;
			}
			if ($this->variable($vname)) {
				$arg = array('arg', $vname);
				$ss = $this->seek();
				if ($this->assign() && $this->expressionList($value))
					$arg[] = $value;
				else {
					$this->seek($ss);
					if ($this->literal('...')) {
						$arg[0] = 'rest';
						$is_vararg = true;
					}
				}
				$values[] = $arg;
				if ($is_vararg)
					break;
				continue;
			}
			if ($this->value($literal))
				$values[] = array('lit', $literal);
			if (!$this->literal($delim))
				break;
		}
		if (!$this->literal(')')) {
			$this->seek($s);
			return false;
		}
		$args = $values;
		return true;
	}

	protected function tags(&$tags, $simple = false, $delim = ',')
	{
		$tt = null;
		$tags = array();
		while ($this->tag($tt, $simple)) {
			$tags[] = $tt;
			if (!$this->literal($delim))
				break;
		}
		if (count($tags) == 0)
			return false;
		return true;
	}

	protected function mixinTags(&$tags)
	{
		$tt = null;
		$tags = array();
		while ($this->tag($tt, true)) {
			$tags[] = $tt;
			$this->literal('>');
		}
		if (count($tags) == 0)
			return false;
		return true;
	}

	protected function tagBracket(&$value)
	{
		$c = null;
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != '[')
			return false;
		$s = $this->seek();
		if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) {
			$value = '['.$c.']';
			if ($this->whitespace())
				$value .= ' ';
			$value = str_replace($this->lessc->parent_selector, '$&$', $value);
			return true;
		}
		$this->seek($s);
		return false;
	}

	protected function tagExpression(&$value)
	{
		$exp = null;
		$s = $this->seek();
		if ($this->literal('(') && $this->expression($exp) && $this->literal(')')) {
			$value = array('exp', $exp);
			return true;
		}
		$this->seek($s);
		return false;
	}

	protected function tag(&$tag, $simple = false)
	{
		$m = null;
		$interp = null;
		$first = null;
		$brack = null;
		$unit = null;
		if ($simple)
			$chars = '^@,:;{}\][>\(\) "\'';
		else
			$chars = '^@,;{}["\'';
		$s = $this->seek();
		if (!$simple && $this->tagExpression($tag))
			return true;
		$has_expression = false;
		$parts = array();
		while ($this->tagBracket($first))
			$parts[] = $first;
		$old_white = $this->eat_white_default;
		$this->eat_white_default = false;
		while (true) {
			if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
				$parts[] = $m[1];
				if ($simple)
					break;
				while ($this->tagBracket($brack))
					$parts[] = $brack;
				continue;
			}
			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == '@') {
				if ($this->interpolation($interp)) {
					$has_expression = true;
					$interp[2] = true;
					$parts[] = $interp;
					continue;
				}
				if ($this->literal('@')) {
					$parts[] = '@';
					continue;
				}
			}
			if ($this->unit($unit)) {
				$parts[] = $unit[1];
				$parts[] = $unit[2];
				continue;
			}
			break;
		}
		$this->eat_white_default = $old_white;
		if (!$parts) {
			$this->seek($s);
			return false;
		}
		if ($has_expression)
			$tag = array('exp', array('string', '', $parts));
		else
			$tag = trim(implode($parts));
		$this->whitespace();
		return true;
	}

	protected function func(&$func)
	{
		$value = null;
		$m = null;
		$name = null;
		$string = null;
		$s = $this->seek();
		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
			$fname = $m[1];
			$s_pre_args = $this->seek();
			$args = array();
			while (true) {
				$ss = $this->seek();
				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value))
					$args[] = array('string', '', array($name, '=', $value));
				else {
					$this->seek($ss);
					if ($this->expressionList($value))
						$args[] = $value;
				}
				if (!$this->literal(','))
					break;
			}
			$args = array('list', ',', $args);
			if ($this->literal(')')) {
				$func = array('function', $fname, $args);
				return true;
			} elseif ($fname == 'url') {
				$this->seek($s_pre_args);
				if ($this->openString(')', $string) && $this->literal(')')) {
					$func = array('function', $fname, $string);
					return true;
				}
			}
		}
		$this->seek($s);
		return false;
	}

	protected function variable(&$name)
	{
		$sub = null;
		$s = $this->seek();
		if ($this->literal($this->lessc->v_prefix, false) && ($this->variable($sub) || $this->keyword($name))) {
			if (!empty($sub))
				$name = array('variable', $sub);
			else
				$name = $this->lessc->v_prefix.$name;
			return true;
		}
		$name = null;
		$this->seek($s);
		return false;
	}

	protected function assign($name = null)
	{
		if ($name)
			$this->current_property = $name;
		return $this->literal(':') || $this->literal('=');
	}

	protected function keyword(&$word)
	{
		$m = null;
		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
			$word = $m[1];
			return true;
		}
		return false;
	}

	protected function end()
	{
		if ($this->literal(';'))
			return true;
		elseif ($this->count == Tools::strlen($this->buffer) || $this->buffer{$this->count} == '}')
			return true;
		return false;
	}

	protected function guards(&$guards)
	{
		$g = null;
		$s = $this->seek();
		if (!$this->literal('when')) {
			$this->seek($s);
			return false;
		}
		$guards = array();
		while ($this->guardGroup($g)) {
			$guards[] = $g;
			if (!$this->literal(','))
				break;
		}
		if (count($guards) == 0) {
			$guards = null;
			$this->seek($s);
			return false;
		}
		return true;
	}

	protected function guardGroup(&$guard_group)
	{
		$guard = null;
		$s = $this->seek();
		$guard_group = array();
		while ($this->guard($guard)) {
			$guard_group[] = $guard;
			if (!$this->literal('and'))
				break;
		}
		if (count($guard_group) == 0) {
			$guard_group = null;
			$this->seek($s);
			return false;
		}
		return true;
	}

	protected function guard(&$guard)
	{
		$exp = null;
		$s = $this->seek();
		$negate = $this->literal('not');
		if ($this->literal('(') && $this->expression($exp) && $this->literal(')')) {
			$guard = $exp;
			if ($negate)
				$guard = array('negate', $guard);
			return true;
		}
		$this->seek($s);
		return false;
	}

	protected function literal($what, $eat_whitespace = null)
	{
		$m = null;
		if ($eat_whitespace === null)
			$eat_whitespace = $this->eat_white_default;
		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
			if ($this->buffer[$this->count] == $what) {
				if (!$eat_whitespace) {
					$this->count++;
					return true;
				}
			} else
				return false;
		}
		if (!isset(self::$literal_cache[$what]))
			self::$literal_cache[$what] = Lessc::pregQuote($what);
		return $this->match(self::$literal_cache[$what], $m, $eat_whitespace);
	}

	protected function genericList(&$out, $parse_item, $delim = '', $flatten = true)
	{
		$value = null;
		$s = $this->seek();
		$items = array();
		while ($this->$parse_item($value)) {
			$items[] = $value;
			if ($delim)
				if (!$this->literal($delim))
					break;
		}
		if (count($items) == 0) {
			$this->seek($s);
			return false;
		}
		if ($flatten && count($items) == 1)
			$out = $items[0];
		else
			$out = array('list', $delim, $items);
		return true;
	}

	protected function to($what, &$out, $until = false, $allow_newline = false)
	{
		$m = null;
		if (is_string($allow_newline))
			$valid_chars = $allow_newline;
		else
			$valid_chars = $allow_newline ? '.' : "[^\n]";
		if (!$this->match('('.$valid_chars.'*?)'.Lessc::pregQuote($what), $m, !$until))
			return false;
		if ($until)
			$this->count -= Tools::strlen($what);
		$out = $m[1];
		return true;
	}

	protected function match($regex, &$out, $eat_whitespace = null)
	{
		if ($eat_whitespace === null)
			$eat_whitespace = $this->eat_white_default;
		$r = '/'.$regex.($eat_whitespace && !$this->write_comments ? '\s*' : '').'/Ais';
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
			$this->count += Tools::strlen($out[0]);
			if ($eat_whitespace && $this->write_comments)
				$this->whitespace();
			return true;
		}
		return false;
	}

	protected function whitespace()
	{
		if ($this->write_comments) {
			$got_white = false;
			$count_comments = $this->count;
			while (preg_match(self::$white_pattern, $this->buffer, $m, null, $count_comments)) {
				if (isset($m[1]) && empty($this->comments_seen[$this->count])) {
					$this->append(array('comment', $m[1]));
					$this->comments_seen[$this->count] = true;
				}
				$this->count += Tools::strlen($m[0]);
				$got_white = true;
			}
			return $got_white;
		} else {
			$this->match('', $m);
			return Tools::strlen($m[0]) > 0;
		}
	}

	protected function peek($regex, &$out = null, $from = null)
	{
		if (is_null($from))
			$from = $this->count;
		$r = '/'.$regex.'/Ais';
		$result = preg_match($r, $this->buffer, $out, null, $from);
		return $result;
	}

	protected function seek($where = null)
	{
		if ($where === null)
			return $this->count;
		else
			$this->count = $where;
		return true;
	}

	public function throwError($msg = 'parse error', $count = null)
	{
		$m = null;
		$count = is_null($count) ? $this->count : $count;
		$line = $this->line + substr_count(Tools::substr($this->buffer, 0, $count), "\n");
		if (!empty($this->source_name))
			$loc = "$this->source_name on line $line";
		else
			$loc = "line: $line";
		if ($this->peek("(.*?)(\n|$)", $m, $count))
			throw new exception("$msg: failed at `$m[1]` $loc");
		else
			throw new exception("$msg: $loc");
	}

	protected function pushBlock($selectors = null, $type = null)
	{
		$b = new stdclass;
		$b->parent = $this->env;
		$b->type = $type;
		$b->id = self::$next_block_id++;
		$b->is_vararg = false;
		$b->tags = $selectors;
		$b->props = array();
		$b->children = array();
		$this->env = $b;
		return $b;
	}

	protected function pushSpecialBlock($type)
	{
		return $this->pushBlock(null, $type);
	}

	protected function append($prop, $pos = null)
	{
		if ($pos !== null)
			$prop[-1] = $pos;
		$this->env->props[] = $prop;
	}

	protected function pop()
	{
		$old = $this->env;
		$this->env = $this->env->parent;
		return $old;
	}

	protected function removeComments($text)
	{
		$look = array('url(', '//', '/*', '"', "'");
		$out = '';
		$min = null;
		while (true) {
			foreach ($look as $token) {
				$pos = strpos($text, $token);
				if ($pos !== false)
					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
			}
			if (is_null($min))
				break;
			$count = $min[1];
			$skip = 0;
			$newlines = 0;
			switch ($min[0]) {
				case 'url(':
					if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
						$count += Tools::strlen($m[0]) - Tools::strlen($min[0]);
					break;
				case '"':
				case "'":
					if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count))
						$count += Tools::strlen($m[0]) - 1;
					break;
				case '//':
					$skip = strpos($text, "\n", $count);
					if ($skip === false)
						$skip = Tools::strlen($text) - $count;
					else
						$skip -= $count;
					break;
				case '/*':
					if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
						$skip = Tools::strlen($m[0]);
						$newlines = substr_count($m[0], "\n");
					}
					break;
			}
			if ($skip == 0)
				$count += Tools::strlen($min[0]);
			$out .= Tools::substr($text, 0, $count).str_repeat("\n", $newlines);
			$text = Tools::substr($text, $count + $skip);
			$min = null;
		}
		return $out.$text;
	}
}

class LesscFormatterClassic
{
	public $indent_char = '  ';
	public $break = "\n";
	public $open = ' {';
	public $close = '}';
	public $selector_separator = ', ';
	public $assign_separator = ':';
	public $open_single = ' { ';
	public $close_single = ' }';
	public $disable_single = false;
	public $break_selectors = false;
	public $compress_colors = false;

	public function __construct()
	{
		$this->indent_level = 0;
	}

	public function indentStr($n = 0)
	{
		return str_repeat($this->indent_char, max($this->indent_level + $n, 0));
	}

	public function property($name, $value)
	{
		return $name.$this->assign_separator.$value.';';
	}

	protected function isEmpty($block)
	{
		if (empty($block->lines)) {
			foreach ($block->children as $child)
				if (!$this->isEmpty($child))
					return false;
			return true;
		}
		return false;
	}

	public function block($block)
	{
		if ($this->isEmpty($block))
			return;
		$inner = $pre = $this->indentStr();
		$is_single = !$this->disable_single && is_null($block->type) && count($block->lines) == 1;
		if (!empty($block->selectors)) {
			$this->indent_level++;
			if ($this->break_selectors)
				$selector_separator = $this->selector_separator.$this->break.$pre;
			else
				$selector_separator = $this->selector_separator;
			echo $pre.implode($selector_separator, $block->selectors);
			if ($is_single) {
				echo $this->open_single;
				$inner = '';
			} else {
				echo $this->open.$this->break;
				$inner = $this->indentStr();
			}
		}
		if (!empty($block->lines)) {
			$glue = $this->break.$inner;
			echo $inner.implode($glue, $block->lines);
			if (!$is_single && !empty($block->children))
				echo $this->break;
		}
		foreach ($block->children as $child)
			$this->block($child);
		if (!empty($block->selectors)) {
			if (!$is_single && empty($block->children))
				echo $this->break;
			if ($is_single)
				echo $this->close_single.$this->break;
			else
				echo $pre.$this->close.$this->break;
			$this->indent_level--;
		}
	}
}

class LesscFormatterCompressed extends LesscFormatterClassic
{
	public $disable_single = true;
	public $open = '{';
	public $selector_separator = ',';
	public $assign_separator = ':';
	public $break = '';
	public $compress_colors = true;
}

class LesscFormatterLessjs extends LesscFormatterClassic
{
	public $disable_single = true;
	public $break_selectors = true;
	public $assign_separator = ': ';
	public $selector_separator = ',';
}
