openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Less.php (260594B)


      1 <?php
      2 
      3 /**
      4  * Class for parsing and compiling less files into css
      5  *
      6  * @package Less
      7  * @subpackage parser
      8  *
      9  */
     10 class Less_Parser{
     11 
     12 
     13 	/**
     14 	 * Default parser options
     15 	 */
     16 	public static $default_options = array(
     17 		'compress'				=> false,			// option - whether to compress
     18 		'strictUnits'			=> false,			// whether units need to evaluate correctly
     19 		'strictMath'			=> false,			// whether math has to be within parenthesis
     20 		'relativeUrls'			=> true,			// option - whether to adjust URL's to be relative
     21 		'urlArgs'				=> '',				// whether to add args into url tokens
     22 		'numPrecision'			=> 8,
     23 
     24 		'import_dirs'			=> array(),
     25 		'import_callback'		=> null,
     26 		'cache_dir'				=> null,
     27 		'cache_method'			=> 'php', 			// false, 'serialize', 'php', 'var_export', 'callback';
     28 		'cache_callback_get'	=> null,
     29 		'cache_callback_set'	=> null,
     30 
     31 		'sourceMap'				=> false,			// whether to output a source map
     32 		'sourceMapBasepath'		=> null,
     33 		'sourceMapWriteTo'		=> null,
     34 		'sourceMapURL'			=> null,
     35 
     36 		'indentation' 			=> '  ',
     37 
     38 		'plugins'				=> array(),
     39 
     40 	);
     41 
     42 	public static $options = array();
     43 
     44 
     45 	private $input;					// Less input string
     46 	private $input_len;				// input string length
     47 	private $pos;					// current index in `input`
     48 	private $saveStack = array();	// holds state for backtracking
     49 	private $furthest;
     50 	private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
     51 
     52 	/**
     53 	 * @var Less_Environment
     54 	 */
     55 	private $env;
     56 
     57 	protected $rules = array();
     58 
     59 	private static $imports = array();
     60 
     61 	public static $has_extends = false;
     62 
     63 	public static $next_id = 0;
     64 
     65 	/**
     66 	 * Filename to contents of all parsed the files
     67 	 *
     68 	 * @var array
     69 	 */
     70 	public static $contentsMap = array();
     71 
     72 
     73 	/**
     74 	 * @param Less_Environment|array|null $env
     75 	 */
     76 	public function __construct( $env = null ){
     77 
     78 		// Top parser on an import tree must be sure there is one "env"
     79 		// which will then be passed around by reference.
     80 		if( $env instanceof Less_Environment ){
     81 			$this->env = $env;
     82 		}else{
     83 			$this->SetOptions(Less_Parser::$default_options);
     84 			$this->Reset( $env );
     85 		}
     86 
     87 		// mbstring.func_overload > 1 bugfix
     88 		// The encoding value must be set for each source file,
     89 		// therefore, to conserve resources and improve the speed of this design is taken here
     90 		if (ini_get('mbstring.func_overload')) {
     91 			$this->mb_internal_encoding = ini_get('mbstring.internal_encoding');
     92 			@ini_set('mbstring.internal_encoding', 'ascii');
     93 		}
     94 
     95 	}
     96 
     97 
     98 	/**
     99 	 * Reset the parser state completely
    100 	 *
    101 	 */
    102 	public function Reset( $options = null ){
    103 		$this->rules = array();
    104 		self::$imports = array();
    105 		self::$has_extends = false;
    106 		self::$imports = array();
    107 		self::$contentsMap = array();
    108 
    109 		$this->env = new Less_Environment($options);
    110 		$this->env->Init();
    111 
    112 		//set new options
    113 		if( is_array($options) ){
    114 			$this->SetOptions(Less_Parser::$default_options);
    115 			$this->SetOptions($options);
    116 		}
    117 	}
    118 
    119 	/**
    120 	 * Set one or more compiler options
    121 	 *  options: import_dirs, cache_dir, cache_method
    122 	 *
    123 	 */
    124 	public function SetOptions( $options ){
    125 		foreach($options as $option => $value){
    126 			$this->SetOption($option,$value);
    127 		}
    128 	}
    129 
    130 	/**
    131 	 * Set one compiler option
    132 	 *
    133 	 */
    134 	public function SetOption($option,$value){
    135 
    136 		switch($option){
    137 
    138 			case 'import_dirs':
    139 				$this->SetImportDirs($value);
    140 			return;
    141 
    142 			case 'cache_dir':
    143 				if( is_string($value) ){
    144 					Less_Cache::SetCacheDir($value);
    145 					Less_Cache::CheckCacheDir();
    146 				}
    147 			return;
    148 		}
    149 
    150 		Less_Parser::$options[$option] = $value;
    151 	}
    152 
    153 	/**
    154 	 * Registers a new custom function
    155 	 *
    156 	 * @param  string   $name     function name
    157 	 * @param  callable $callback callback
    158 	 */
    159 	public function registerFunction($name, $callback) {
    160 		$this->env->functions[$name] = $callback;
    161 	}
    162 
    163 	/**
    164 	 * Removed an already registered function
    165 	 *
    166 	 * @param  string $name function name
    167 	 */
    168 	public function unregisterFunction($name) {
    169 		if( isset($this->env->functions[$name]) )
    170 			unset($this->env->functions[$name]);
    171 	}
    172 
    173 
    174 	/**
    175 	 * Get the current css buffer
    176 	 *
    177 	 * @return string
    178 	 */
    179 	public function getCss(){
    180 
    181 		$precision = ini_get('precision');
    182 		@ini_set('precision',16);
    183 		$locale = setlocale(LC_NUMERIC, 0);
    184 		setlocale(LC_NUMERIC, "C");
    185 
    186 		try {
    187 
    188 	 		$root = new Less_Tree_Ruleset(array(), $this->rules );
    189 			$root->root = true;
    190 			$root->firstRoot = true;
    191 
    192 
    193 			$this->PreVisitors($root);
    194 
    195 			self::$has_extends = false;
    196 			$evaldRoot = $root->compile($this->env);
    197 
    198 
    199 
    200 			$this->PostVisitors($evaldRoot);
    201 
    202 			if( Less_Parser::$options['sourceMap'] ){
    203 				$generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
    204 				// will also save file
    205 				// FIXME: should happen somewhere else?
    206 				$css = $generator->generateCSS();
    207 			}else{
    208 				$css = $evaldRoot->toCSS();
    209 			}
    210 
    211 			if( Less_Parser::$options['compress'] ){
    212 				$css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
    213 			}
    214 
    215 		} catch (Exception $exc) {
    216 			// Intentional fall-through so we can reset environment
    217 		}
    218 
    219 		//reset php settings
    220 		@ini_set('precision',$precision);
    221 		setlocale(LC_NUMERIC, $locale);
    222 
    223 		// If you previously defined $this->mb_internal_encoding
    224 		// is required to return the encoding as it was before
    225 		if ($this->mb_internal_encoding != '') {
    226 			@ini_set("mbstring.internal_encoding", $this->mb_internal_encoding);
    227 			$this->mb_internal_encoding = '';
    228 		}
    229 
    230 		// Rethrow exception after we handled resetting the environment
    231 		if (!empty($exc)) {
    232 			throw $exc;
    233 		}
    234 
    235 
    236 
    237 		return $css;
    238 	}
    239 
    240 	/**
    241 	 * Run pre-compile visitors
    242 	 *
    243 	 */
    244 	private function PreVisitors($root){
    245 
    246 		if( Less_Parser::$options['plugins'] ){
    247 			foreach(Less_Parser::$options['plugins'] as $plugin){
    248 				if( !empty($plugin->isPreEvalVisitor) ){
    249 					$plugin->run($root);
    250 				}
    251 			}
    252 		}
    253 	}
    254 
    255 
    256 	/**
    257 	 * Run post-compile visitors
    258 	 *
    259 	 */
    260 	private function PostVisitors($evaldRoot){
    261 
    262 		$visitors = array();
    263 		$visitors[] = new Less_Visitor_joinSelector();
    264 		if( self::$has_extends ){
    265 			$visitors[] = new Less_Visitor_processExtends();
    266 		}
    267 		$visitors[] = new Less_Visitor_toCSS();
    268 
    269 
    270 		if( Less_Parser::$options['plugins'] ){
    271 			foreach(Less_Parser::$options['plugins'] as $plugin){
    272 				if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
    273 					continue;
    274 				}
    275 
    276 				if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
    277 					array_unshift( $visitors, $plugin);
    278 				}else{
    279 					$visitors[] = $plugin;
    280 				}
    281 			}
    282 		}
    283 
    284 
    285 		for($i = 0; $i < count($visitors); $i++ ){
    286 			$visitors[$i]->run($evaldRoot);
    287 		}
    288 
    289 	}
    290 
    291 
    292 	/**
    293 	 * Parse a Less string into css
    294 	 *
    295 	 * @param string $str The string to convert
    296 	 * @param string $uri_root The url of the file
    297 	 * @return Less_Tree_Ruleset|Less_Parser
    298 	 */
    299 	public function parse( $str, $file_uri = null ){
    300 
    301 		if( !$file_uri ){
    302 			$uri_root = '';
    303 			$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
    304 		}else{
    305 			$file_uri = self::WinPath($file_uri);
    306 			$filename = $file_uri;
    307 			$uri_root = dirname($file_uri);
    308 		}
    309 
    310 		$previousFileInfo = $this->env->currentFileInfo;
    311 		$uri_root = self::WinPath($uri_root);
    312 		$this->SetFileInfo($filename, $uri_root);
    313 
    314 		$this->input = $str;
    315 		$this->_parse();
    316 
    317 		if( $previousFileInfo ){
    318 			$this->env->currentFileInfo = $previousFileInfo;
    319 		}
    320 
    321 		return $this;
    322 	}
    323 
    324 
    325 	/**
    326 	 * Parse a Less string from a given file
    327 	 *
    328 	 * @throws Less_Exception_Parser
    329 	 * @param string $filename The file to parse
    330 	 * @param string $uri_root The url of the file
    331 	 * @param bool $returnRoot Indicates whether the return value should be a css string a root node
    332 	 * @return Less_Tree_Ruleset|Less_Parser
    333 	 */
    334 	public function parseFile( $filename, $uri_root = '', $returnRoot = false){
    335 
    336 		if( !file_exists($filename) ){
    337 			$this->Error(sprintf('File `%s` not found.', $filename));
    338 		}
    339 
    340 
    341 		// fix uri_root?
    342 		// Instead of The mixture of file path for the first argument and directory path for the second argument has bee
    343 		if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
    344 			$uri_root = dirname($uri_root);
    345 		}
    346 
    347 
    348 		$previousFileInfo = $this->env->currentFileInfo;
    349 
    350 
    351 		if( $filename ){
    352 			$filename = self::WinPath(realpath($filename));
    353 		}
    354 		$uri_root = self::WinPath($uri_root);
    355 
    356 		$this->SetFileInfo($filename, $uri_root);
    357 
    358 		self::AddParsedFile($filename);
    359 
    360 		if( $returnRoot ){
    361 			$rules = $this->GetRules( $filename );
    362 			$return = new Less_Tree_Ruleset(array(), $rules );
    363 		}else{
    364 			$this->_parse( $filename );
    365 			$return = $this;
    366 		}
    367 
    368 		if( $previousFileInfo ){
    369 			$this->env->currentFileInfo = $previousFileInfo;
    370 		}
    371 
    372 		return $return;
    373 	}
    374 
    375 
    376 	/**
    377 	 * Allows a user to set variables values
    378 	 * @param array $vars
    379 	 * @return Less_Parser
    380 	 */
    381 	public function ModifyVars( $vars ){
    382 
    383 		$this->input = Less_Parser::serializeVars( $vars );
    384 		$this->_parse();
    385 
    386 		return $this;
    387 	}
    388 
    389 
    390 	/**
    391 	 * @param string $filename
    392 	 */
    393 	public function SetFileInfo( $filename, $uri_root = ''){
    394 
    395 		$filename = Less_Environment::normalizePath($filename);
    396 		$dirname = preg_replace('/[^\/\\\\]*$/','',$filename);
    397 
    398 		if( !empty($uri_root) ){
    399 			$uri_root = rtrim($uri_root,'/').'/';
    400 		}
    401 
    402 		$currentFileInfo = array();
    403 
    404 		//entry info
    405 		if( isset($this->env->currentFileInfo) ){
    406 			$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
    407 			$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
    408 			$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
    409 
    410 		}else{
    411 			$currentFileInfo['entryPath'] = $dirname;
    412 			$currentFileInfo['entryUri'] = $uri_root;
    413 			$currentFileInfo['rootpath'] = $dirname;
    414 		}
    415 
    416 		$currentFileInfo['currentDirectory'] = $dirname;
    417 		$currentFileInfo['currentUri'] = $uri_root.basename($filename);
    418 		$currentFileInfo['filename'] = $filename;
    419 		$currentFileInfo['uri_root'] = $uri_root;
    420 
    421 
    422 		//inherit reference
    423 		if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
    424 			$currentFileInfo['reference'] = true;
    425 		}
    426 
    427 		$this->env->currentFileInfo = $currentFileInfo;
    428 	}
    429 
    430 
    431 	/**
    432 	 * @deprecated 1.5.1.2
    433 	 *
    434 	 */
    435 	public function SetCacheDir( $dir ){
    436 
    437 		if( !file_exists($dir) ){
    438 			if( mkdir($dir) ){
    439 				return true;
    440 			}
    441 			throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);
    442 
    443 		}elseif( !is_dir($dir) ){
    444 			throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);
    445 
    446 		}elseif( !is_writable($dir) ){
    447 			throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);
    448 
    449 		}else{
    450 			$dir = self::WinPath($dir);
    451 			Less_Cache::$cache_dir = rtrim($dir,'/').'/';
    452 			return true;
    453 		}
    454 	}
    455 
    456 
    457 	/**
    458 	 * Set a list of directories or callbacks the parser should use for determining import paths
    459 	 *
    460 	 * @param array $dirs
    461 	 */
    462 	public function SetImportDirs( $dirs ){
    463 		Less_Parser::$options['import_dirs'] = array();
    464 
    465 		foreach($dirs as $path => $uri_root){
    466 
    467 			$path = self::WinPath($path);
    468 			if( !empty($path) ){
    469 				$path = rtrim($path,'/').'/';
    470 			}
    471 
    472 			if ( !is_callable($uri_root) ){
    473 				$uri_root = self::WinPath($uri_root);
    474 				if( !empty($uri_root) ){
    475 					$uri_root = rtrim($uri_root,'/').'/';
    476 				}
    477 			}
    478 
    479 			Less_Parser::$options['import_dirs'][$path] = $uri_root;
    480 		}
    481 	}
    482 
    483 	/**
    484 	 * @param string $file_path
    485 	 */
    486 	private function _parse( $file_path = null ){
    487 		$this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
    488 	}
    489 
    490 
    491 	/**
    492 	 * Return the results of parsePrimary for $file_path
    493 	 * Use cache and save cached results if possible
    494 	 *
    495 	 * @param string|null $file_path
    496 	 */
    497 	private function GetRules( $file_path ){
    498 
    499 		$this->SetInput($file_path);
    500 
    501 		$cache_file = $this->CacheFile( $file_path );
    502 		if( $cache_file ){
    503 			if( Less_Parser::$options['cache_method'] == 'callback' ){
    504 				if( is_callable(Less_Parser::$options['cache_callback_get']) ){
    505 					$cache = call_user_func_array(
    506 						Less_Parser::$options['cache_callback_get'],
    507 						array($this, $file_path, $cache_file)
    508 					);
    509 
    510 					if( $cache ){
    511 						$this->UnsetInput();
    512 						return $cache;
    513 					}
    514 				}
    515 
    516 			}elseif( file_exists($cache_file) ){
    517 				switch(Less_Parser::$options['cache_method']){
    518 
    519 					// Using serialize
    520 					// Faster but uses more memory
    521 					case 'serialize':
    522 						$cache = unserialize(file_get_contents($cache_file));
    523 						if( $cache ){
    524 							touch($cache_file);
    525 							$this->UnsetInput();
    526 							return $cache;
    527 						}
    528 					break;
    529 
    530 
    531 					// Using generated php code
    532 					case 'var_export':
    533 					case 'php':
    534 					$this->UnsetInput();
    535 					return include($cache_file);
    536 				}
    537 			}
    538 		}
    539 
    540 		$rules = $this->parsePrimary();
    541 
    542 		if( $this->pos < $this->input_len ){
    543 			throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
    544 		}
    545 
    546 		$this->UnsetInput();
    547 
    548 
    549 		//save the cache
    550 		if( $cache_file ){
    551 			if( Less_Parser::$options['cache_method'] == 'callback' ){
    552 				if( is_callable(Less_Parser::$options['cache_callback_set']) ){
    553 					call_user_func_array(
    554 						Less_Parser::$options['cache_callback_set'],
    555 						array($this, $file_path, $cache_file, $rules)
    556 					);
    557 				}
    558 
    559 			}else{
    560 				//msg('write cache file');
    561 				switch(Less_Parser::$options['cache_method']){
    562 					case 'serialize':
    563 						file_put_contents( $cache_file, serialize($rules) );
    564 					break;
    565 					case 'php':
    566 						file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
    567 					break;
    568 					case 'var_export':
    569 						//Requires __set_state()
    570 						file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
    571 					break;
    572 				}
    573 
    574 				Less_Cache::CleanCache();
    575 			}
    576 		}
    577 
    578 		return $rules;
    579 	}
    580 
    581 
    582 	/**
    583 	 * Set up the input buffer
    584 	 *
    585 	 */
    586 	public function SetInput( $file_path ){
    587 
    588 		if( $file_path ){
    589 			$this->input = file_get_contents( $file_path );
    590 		}
    591 
    592 		$this->pos = $this->furthest = 0;
    593 
    594 		// Remove potential UTF Byte Order Mark
    595 		$this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
    596 		$this->input_len = strlen($this->input);
    597 
    598 
    599 		if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
    600 			$uri = $this->env->currentFileInfo['currentUri'];
    601 			Less_Parser::$contentsMap[$uri] = $this->input;
    602 		}
    603 
    604 	}
    605 
    606 
    607 	/**
    608 	 * Free up some memory
    609 	 *
    610 	 */
    611 	public function UnsetInput(){
    612 		unset($this->input, $this->pos, $this->input_len, $this->furthest);
    613 		$this->saveStack = array();
    614 	}
    615 
    616 
    617 	public function CacheFile( $file_path ){
    618 
    619 		if( $file_path && $this->CacheEnabled() ){
    620 
    621 			$env = get_object_vars($this->env);
    622 			unset($env['frames']);
    623 
    624 			$parts = array();
    625 			$parts[] = $file_path;
    626 			$parts[] = filesize( $file_path );
    627 			$parts[] = filemtime( $file_path );
    628 			$parts[] = $env;
    629 			$parts[] = Less_Version::cache_version;
    630 			$parts[] = Less_Parser::$options['cache_method'];
    631 			return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache';
    632 		}
    633 	}
    634 
    635 
    636 	static function AddParsedFile($file){
    637 		self::$imports[] = $file;
    638 	}
    639 
    640 	static function AllParsedFiles(){
    641 		return self::$imports;
    642 	}
    643 
    644 	/**
    645 	 * @param string $file
    646 	 */
    647 	static function FileParsed($file){
    648 		return in_array($file,self::$imports);
    649 	}
    650 
    651 
    652 	function save() {
    653 		$this->saveStack[] = $this->pos;
    654 	}
    655 
    656 	private function restore() {
    657 		$this->pos = array_pop($this->saveStack);
    658 	}
    659 
    660 	private function forget(){
    661 		array_pop($this->saveStack);
    662 	}
    663 
    664 
    665 	private function isWhitespace($offset = 0) {
    666 		return preg_match('/\s/',$this->input[ $this->pos + $offset]);
    667 	}
    668 
    669 	/**
    670 	 * Parse from a token, regexp or string, and move forward if match
    671 	 *
    672 	 * @param array $toks
    673 	 * @return array
    674 	 */
    675 	private function match($toks){
    676 
    677 		// The match is confirmed, add the match length to `this::pos`,
    678 		// and consume any extra white-space characters (' ' || '\n')
    679 		// which come after that. The reason for this is that LeSS's
    680 		// grammar is mostly white-space insensitive.
    681 		//
    682 
    683 		foreach($toks as $tok){
    684 
    685 			$char = $tok[0];
    686 
    687 			if( $char === '/' ){
    688 				$match = $this->MatchReg($tok);
    689 
    690 				if( $match ){
    691 					return count($match) === 1 ? $match[0] : $match;
    692 				}
    693 
    694 			}elseif( $char === '#' ){
    695 				$match = $this->MatchChar($tok[1]);
    696 
    697 			}else{
    698 				// Non-terminal, match using a function call
    699 				$match = $this->$tok();
    700 
    701 			}
    702 
    703 			if( $match ){
    704 				return $match;
    705 			}
    706 		}
    707 	}
    708 
    709 	/**
    710 	 * @param string[] $toks
    711 	 *
    712 	 * @return string
    713 	 */
    714 	private function MatchFuncs($toks){
    715 
    716 		if( $this->pos < $this->input_len ){
    717 			foreach($toks as $tok){
    718 				$match = $this->$tok();
    719 				if( $match ){
    720 					return $match;
    721 				}
    722 			}
    723 		}
    724 
    725 	}
    726 
    727 	// Match a single character in the input,
    728 	private function MatchChar($tok){
    729 		if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
    730 			$this->skipWhitespace(1);
    731 			return $tok;
    732 		}
    733 	}
    734 
    735 	// Match a regexp from the current start point
    736 	private function MatchReg($tok){
    737 
    738 		if( preg_match($tok, $this->input, $match, 0, $this->pos) ){
    739 			$this->skipWhitespace(strlen($match[0]));
    740 			return $match;
    741 		}
    742 	}
    743 
    744 
    745 	/**
    746 	 * Same as match(), but don't change the state of the parser,
    747 	 * just return the match.
    748 	 *
    749 	 * @param string $tok
    750 	 * @return integer
    751 	 */
    752 	public function PeekReg($tok){
    753 		return preg_match($tok, $this->input, $match, 0, $this->pos);
    754 	}
    755 
    756 	/**
    757 	 * @param string $tok
    758 	 */
    759 	public function PeekChar($tok){
    760 		//return ($this->input[$this->pos] === $tok );
    761 		return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
    762 	}
    763 
    764 
    765 	/**
    766 	 * @param integer $length
    767 	 */
    768 	public function skipWhitespace($length){
    769 
    770 		$this->pos += $length;
    771 
    772 		for(; $this->pos < $this->input_len; $this->pos++ ){
    773 			$c = $this->input[$this->pos];
    774 
    775 			if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
    776 				break;
    777 			}
    778 		}
    779 	}
    780 
    781 
    782 	/**
    783 	 * @param string $tok
    784 	 * @param string|null $msg
    785 	 */
    786 	public function expect($tok, $msg = NULL) {
    787 		$result = $this->match( array($tok) );
    788 		if (!$result) {
    789 			$this->Error( $msg	? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
    790 		} else {
    791 			return $result;
    792 		}
    793 	}
    794 
    795 	/**
    796 	 * @param string $tok
    797 	 */
    798 	public function expectChar($tok, $msg = null ){
    799 		$result = $this->MatchChar($tok);
    800 		if( !$result ){
    801 			$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
    802 		}else{
    803 			return $result;
    804 		}
    805 	}
    806 
    807 	//
    808 	// Here in, the parsing rules/functions
    809 	//
    810 	// The basic structure of the syntax tree generated is as follows:
    811 	//
    812 	//   Ruleset ->  Rule -> Value -> Expression -> Entity
    813 	//
    814 	// Here's some LESS code:
    815 	//
    816 	//	.class {
    817 	//	  color: #fff;
    818 	//	  border: 1px solid #000;
    819 	//	  width: @w + 4px;
    820 	//	  > .child {...}
    821 	//	}
    822 	//
    823 	// And here's what the parse tree might look like:
    824 	//
    825 	//	 Ruleset (Selector '.class', [
    826 	//		 Rule ("color",  Value ([Expression [Color #fff]]))
    827 	//		 Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
    828 	//		 Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
    829 	//		 Ruleset (Selector [Element '>', '.child'], [...])
    830 	//	 ])
    831 	//
    832 	//  In general, most rules will try to parse a token with the `$()` function, and if the return
    833 	//  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
    834 	//  first, before parsing, that's when we use `peek()`.
    835 	//
    836 
    837 	//
    838 	// The `primary` rule is the *entry* and *exit* point of the parser.
    839 	// The rules here can appear at any level of the parse tree.
    840 	//
    841 	// The recursive nature of the grammar is an interplay between the `block`
    842 	// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
    843 	// as represented by this simplified grammar:
    844 	//
    845 	//	 primary  →  (ruleset | rule)+
    846 	//	 ruleset  →  selector+ block
    847 	//	 block	→  '{' primary '}'
    848 	//
    849 	// Only at one point is the primary rule not called from the
    850 	// block rule: at the root level.
    851 	//
    852 	private function parsePrimary(){
    853 		$root = array();
    854 
    855 		while( true ){
    856 
    857 			if( $this->pos >= $this->input_len ){
    858 				break;
    859 			}
    860 
    861 			$node = $this->parseExtend(true);
    862 			if( $node ){
    863 				$root = array_merge($root,$node);
    864 				continue;
    865 			}
    866 
    867 			//$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
    868 			$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));
    869 
    870 			if( $node ){
    871 				$root[] = $node;
    872 			}elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){
    873 				break;
    874 			}
    875 
    876             if( $this->PeekChar('}') ){
    877 				break;
    878 			}
    879 		}
    880 
    881 		return $root;
    882 	}
    883 
    884 
    885 
    886 	// We create a Comment node for CSS comments `/* */`,
    887 	// but keep the LeSS comments `//` silent, by just skipping
    888 	// over them.
    889 	private function parseComment(){
    890 
    891 		if( $this->input[$this->pos] !== '/' ){
    892 			return;
    893 		}
    894 
    895 		if( $this->input[$this->pos+1] === '/' ){
    896 			$match = $this->MatchReg('/\\G\/\/.*/');
    897 			return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
    898 		}
    899 
    900 		//$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
    901 		$comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
    902 		if( $comment ){
    903 			return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
    904 		}
    905 	}
    906 
    907 	private function parseComments(){
    908 		$comments = array();
    909 
    910 		while( $this->pos < $this->input_len ){
    911 			$comment = $this->parseComment();
    912 			if( !$comment ){
    913 				break;
    914 			}
    915 
    916 			$comments[] = $comment;
    917 		}
    918 
    919 		return $comments;
    920 	}
    921 
    922 
    923 
    924 	//
    925 	// A string, which supports escaping " and '
    926 	//
    927 	//	 "milky way" 'he\'s the one!'
    928 	//
    929 	private function parseEntitiesQuoted() {
    930 		$j = $this->pos;
    931 		$e = false;
    932 		$index = $this->pos;
    933 
    934 		if( $this->input[$this->pos] === '~' ){
    935 			$j++;
    936 			$e = true; // Escaped strings
    937 		}
    938 
    939 		if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){
    940 			return;
    941 		}
    942 
    943 		if ($e) {
    944 			$this->MatchChar('~');
    945 		}
    946 
    947                 // Fix for #124: match escaped newlines
    948                 //$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/');
    949 		$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/');
    950 
    951 		if( $str ){
    952 			$result = $str[0][0] == '"' ? $str[1] : $str[2];
    953 			return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) );
    954 		}
    955 		return;
    956 	}
    957 
    958 
    959 	//
    960 	// A catch-all word, such as:
    961 	//
    962 	//	 black border-collapse
    963 	//
    964 	private function parseEntitiesKeyword(){
    965 
    966 		//$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
    967 		$k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
    968 		if( $k ){
    969 			$k = $k[0];
    970 			$color = $this->fromKeyword($k);
    971 			if( $color ){
    972 				return $color;
    973 			}
    974 			return $this->NewObj1('Less_Tree_Keyword',$k);
    975 		}
    976 	}
    977 
    978 	// duplicate of Less_Tree_Color::FromKeyword
    979 	private function FromKeyword( $keyword ){
    980 		$keyword = strtolower($keyword);
    981 
    982 		if( Less_Colors::hasOwnProperty($keyword) ){
    983 			// detect named color
    984 			return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
    985 		}
    986 
    987 		if( $keyword === 'transparent' ){
    988 			return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
    989 		}
    990 	}
    991 
    992 	//
    993 	// A function call
    994 	//
    995 	//	 rgb(255, 0, 255)
    996 	//
    997 	// We also try to catch IE's `alpha()`, but let the `alpha` parser
    998 	// deal with the details.
    999 	//
   1000 	// The arguments are parsed with the `entities.arguments` parser.
   1001 	//
   1002 	private function parseEntitiesCall(){
   1003 		$index = $this->pos;
   1004 
   1005 		if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
   1006 			return;
   1007 		}
   1008 		$name = $name[1];
   1009 		$nameLC = strtolower($name);
   1010 
   1011 		if ($nameLC === 'url') {
   1012 			return null;
   1013 		}
   1014 
   1015 		$this->pos += strlen($name);
   1016 
   1017 		if( $nameLC === 'alpha' ){
   1018 			$alpha_ret = $this->parseAlpha();
   1019 			if( $alpha_ret ){
   1020 				return $alpha_ret;
   1021 			}
   1022 		}
   1023 
   1024 		$this->MatchChar('('); // Parse the '(' and consume whitespace.
   1025 
   1026 		$args = $this->parseEntitiesArguments();
   1027 
   1028 		if( !$this->MatchChar(')') ){
   1029 			return;
   1030 		}
   1031 
   1032 		if ($name) {
   1033 			return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
   1034 		}
   1035 	}
   1036 
   1037 	/**
   1038 	 * Parse a list of arguments
   1039 	 *
   1040 	 * @return array
   1041 	 */
   1042 	private function parseEntitiesArguments(){
   1043 
   1044 		$args = array();
   1045 		while( true ){
   1046 			$arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
   1047 			if( !$arg ){
   1048 				break;
   1049 			}
   1050 
   1051 			$args[] = $arg;
   1052 			if( !$this->MatchChar(',') ){
   1053 				break;
   1054 			}
   1055 		}
   1056 		return $args;
   1057 	}
   1058 
   1059 	private function parseEntitiesLiteral(){
   1060 		return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
   1061 	}
   1062 
   1063 	// Assignments are argument entities for calls.
   1064 	// They are present in ie filter properties as shown below.
   1065 	//
   1066 	//	 filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
   1067 	//
   1068 	private function parseEntitiesAssignment() {
   1069 
   1070 		$key = $this->MatchReg('/\\G\w+(?=\s?=)/');
   1071 		if( !$key ){
   1072 			return;
   1073 		}
   1074 
   1075 		if( !$this->MatchChar('=') ){
   1076 			return;
   1077 		}
   1078 
   1079 		$value = $this->parseEntity();
   1080 		if( $value ){
   1081 			return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
   1082 		}
   1083 	}
   1084 
   1085 	//
   1086 	// Parse url() tokens
   1087 	//
   1088 	// We use a specific rule for urls, because they don't really behave like
   1089 	// standard function calls. The difference is that the argument doesn't have
   1090 	// to be enclosed within a string, so it can't be parsed as an Expression.
   1091 	//
   1092 	private function parseEntitiesUrl(){
   1093 
   1094 
   1095 		if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
   1096 			return;
   1097 		}
   1098 
   1099 		$value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
   1100 		if( !$value ){
   1101 			$value = '';
   1102 		}
   1103 
   1104 
   1105 		$this->expectChar(')');
   1106 
   1107 
   1108 		if( isset($value->value) || $value instanceof Less_Tree_Variable ){
   1109 			return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
   1110 		}
   1111 
   1112 		return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
   1113 	}
   1114 
   1115 
   1116 	//
   1117 	// A Variable entity, such as `@fink`, in
   1118 	//
   1119 	//	 width: @fink + 2px
   1120 	//
   1121 	// We use a different parser for variable definitions,
   1122 	// see `parsers.variable`.
   1123 	//
   1124 	private function parseEntitiesVariable(){
   1125 		$index = $this->pos;
   1126 		if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
   1127 			return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
   1128 		}
   1129 	}
   1130 
   1131 
   1132 	// A variable entity useing the protective {} e.g. @{var}
   1133 	private function parseEntitiesVariableCurly() {
   1134 		$index = $this->pos;
   1135 
   1136 		if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
   1137 			return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
   1138 		}
   1139 	}
   1140 
   1141 	//
   1142 	// A Hexadecimal color
   1143 	//
   1144 	//	 #4F3C2F
   1145 	//
   1146 	// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
   1147 	//
   1148 	private function parseEntitiesColor(){
   1149 		if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
   1150 			return $this->NewObj1('Less_Tree_Color',$rgb[1]);
   1151 		}
   1152 	}
   1153 
   1154 	//
   1155 	// A Dimension, that is, a number and a unit
   1156 	//
   1157 	//	 0.5em 95%
   1158 	//
   1159 	private function parseEntitiesDimension(){
   1160 
   1161 		$c = @ord($this->input[$this->pos]);
   1162 
   1163 		//Is the first char of the dimension 0-9, '.', '+' or '-'
   1164 		if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
   1165 			return;
   1166 		}
   1167 
   1168 		$value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
   1169 		if( $value ){
   1170 
   1171 			if( isset($value[2]) ){
   1172 				return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
   1173 			}
   1174 			return $this->NewObj1('Less_Tree_Dimension',$value[1]);
   1175 		}
   1176 	}
   1177 
   1178 
   1179 	//
   1180 	// A unicode descriptor, as is used in unicode-range
   1181 	//
   1182 	// U+0?? or U+00A1-00A9
   1183 	//
   1184 	function parseUnicodeDescriptor() {
   1185 		$ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
   1186 		if( $ud ){
   1187 			return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
   1188 		}
   1189 	}
   1190 
   1191 
   1192 	//
   1193 	// JavaScript code to be evaluated
   1194 	//
   1195 	//	 `window.location.href`
   1196 	//
   1197 	private function parseEntitiesJavascript(){
   1198 		$e = false;
   1199 		$j = $this->pos;
   1200 		if( $this->input[$j] === '~' ){
   1201 			$j++;
   1202 			$e = true;
   1203 		}
   1204 		if( $this->input[$j] !== '`' ){
   1205 			return;
   1206 		}
   1207 		if( $e ){
   1208 			$this->MatchChar('~');
   1209 		}
   1210 		$str = $this->MatchReg('/\\G`([^`]*)`/');
   1211 		if( $str ){
   1212 			return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
   1213 		}
   1214 	}
   1215 
   1216 
   1217 	//
   1218 	// The variable part of a variable definition. Used in the `rule` parser
   1219 	//
   1220 	//	 @fink:
   1221 	//
   1222 	private function parseVariable(){
   1223 		if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
   1224 			return $name[1];
   1225 		}
   1226 	}
   1227 
   1228 
   1229 	//
   1230 	// The variable part of a variable definition. Used in the `rule` parser
   1231 	//
   1232 	// @fink();
   1233 	//
   1234 	private function parseRulesetCall(){
   1235 
   1236 		if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
   1237 			return $this->NewObj1('Less_Tree_RulesetCall', $name[1] );
   1238 		}
   1239 	}
   1240 
   1241 
   1242 	//
   1243 	// extend syntax - used to extend selectors
   1244 	//
   1245 	function parseExtend($isRule = false){
   1246 
   1247 		$index = $this->pos;
   1248 		$extendList = array();
   1249 
   1250 
   1251 		if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }
   1252 
   1253 		do{
   1254 			$option = null;
   1255 			$elements = array();
   1256 			while( true ){
   1257 				$option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
   1258 				if( $option ){ break; }
   1259 				$e = $this->parseElement();
   1260 				if( !$e ){ break; }
   1261 				$elements[] = $e;
   1262 			}
   1263 
   1264 			if( $option ){
   1265 				$option = $option[1];
   1266 			}
   1267 
   1268 			$extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));
   1269 
   1270 		}while( $this->MatchChar(",") );
   1271 
   1272 		$this->expect('/\\G\)/');
   1273 
   1274 		if( $isRule ){
   1275 			$this->expect('/\\G;/');
   1276 		}
   1277 
   1278 		return $extendList;
   1279 	}
   1280 
   1281 
   1282 	//
   1283 	// A Mixin call, with an optional argument list
   1284 	//
   1285 	//	 #mixins > .square(#fff);
   1286 	//	 .rounded(4px, black);
   1287 	//	 .button;
   1288 	//
   1289 	// The `while` loop is there because mixins can be
   1290 	// namespaced, but we only support the child and descendant
   1291 	// selector for now.
   1292 	//
   1293 	private function parseMixinCall(){
   1294 
   1295 		$char = $this->input[$this->pos];
   1296 		if( $char !== '.' && $char !== '#' ){
   1297 			return;
   1298 		}
   1299 
   1300 		$index = $this->pos;
   1301 		$this->save(); // stop us absorbing part of an invalid selector
   1302 
   1303 		$elements = $this->parseMixinCallElements();
   1304 
   1305 		if( $elements ){
   1306 
   1307 			if( $this->MatchChar('(') ){
   1308 				$returned = $this->parseMixinArgs(true);
   1309 				$args = $returned['args'];
   1310 				$this->expectChar(')');
   1311 			}else{
   1312 				$args = array();
   1313 			}
   1314 
   1315 			$important = $this->parseImportant();
   1316 
   1317 			if( $this->parseEnd() ){
   1318 				$this->forget();
   1319 				return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
   1320 			}
   1321 		}
   1322 
   1323 		$this->restore();
   1324 	}
   1325 
   1326 
   1327 	private function parseMixinCallElements(){
   1328 		$elements = array();
   1329 		$c = null;
   1330 
   1331 		while( true ){
   1332 			$elemIndex = $this->pos;
   1333 			$e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
   1334 			if( !$e ){
   1335 				break;
   1336 			}
   1337 			$elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
   1338 			$c = $this->MatchChar('>');
   1339 		}
   1340 
   1341 		return $elements;
   1342 	}
   1343 
   1344 
   1345 
   1346 	/**
   1347 	 * @param boolean $isCall
   1348 	 */
   1349 	private function parseMixinArgs( $isCall ){
   1350 		$expressions = array();
   1351 		$argsSemiColon = array();
   1352 		$isSemiColonSeperated = null;
   1353 		$argsComma = array();
   1354 		$expressionContainsNamed = null;
   1355 		$name = null;
   1356 		$returner = array('args'=>array(), 'variadic'=> false);
   1357 
   1358 		$this->save();
   1359 
   1360 		while( true ){
   1361 			if( $isCall ){
   1362 				$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
   1363 			} else {
   1364 				$this->parseComments();
   1365 				if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
   1366 					$returner['variadic'] = true;
   1367 					if( $this->MatchChar(";") && !$isSemiColonSeperated ){
   1368 						$isSemiColonSeperated = true;
   1369 					}
   1370 
   1371 					if( $isSemiColonSeperated ){
   1372 						$argsSemiColon[] = array('variadic'=>true);
   1373 					}else{
   1374 						$argsComma[] = array('variadic'=>true);
   1375 					}
   1376 					break;
   1377 				}
   1378 				$arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
   1379 			}
   1380 
   1381 			if( !$arg ){
   1382 				break;
   1383 			}
   1384 
   1385 
   1386 			$nameLoop = null;
   1387 			if( $arg instanceof Less_Tree_Expression ){
   1388 				$arg->throwAwayComments();
   1389 			}
   1390 			$value = $arg;
   1391 			$val = null;
   1392 
   1393 			if( $isCall ){
   1394 				// Variable
   1395 				if( property_exists($arg,'value') && count($arg->value) == 1 ){
   1396 					$val = $arg->value[0];
   1397 				}
   1398 			} else {
   1399 				$val = $arg;
   1400 			}
   1401 
   1402 
   1403 			if( $val instanceof Less_Tree_Variable ){
   1404 
   1405 				if( $this->MatchChar(':') ){
   1406 					if( $expressions ){
   1407 						if( $isSemiColonSeperated ){
   1408 							$this->Error('Cannot mix ; and , as delimiter types');
   1409 						}
   1410 						$expressionContainsNamed = true;
   1411 					}
   1412 
   1413 					// we do not support setting a ruleset as a default variable - it doesn't make sense
   1414 					// However if we do want to add it, there is nothing blocking it, just don't error
   1415 					// and remove isCall dependency below
   1416 					$value = null;
   1417 					if( $isCall ){
   1418 						$value = $this->parseDetachedRuleset();
   1419 					}
   1420 					if( !$value ){
   1421 						$value = $this->parseExpression();
   1422 					}
   1423 
   1424 					if( !$value ){
   1425 						if( $isCall ){
   1426 							$this->Error('could not understand value for named argument');
   1427 						} else {
   1428 							$this->restore();
   1429 							$returner['args'] = array();
   1430 							return $returner;
   1431 						}
   1432 					}
   1433 
   1434 					$nameLoop = ($name = $val->name);
   1435 				}elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){
   1436 					$returner['variadic'] = true;
   1437 					if( $this->MatchChar(";") && !$isSemiColonSeperated ){
   1438 						$isSemiColonSeperated = true;
   1439 					}
   1440 					if( $isSemiColonSeperated ){
   1441 						$argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
   1442 					}else{
   1443 						$argsComma[] = array('name'=> $arg->name, 'variadic' => true);
   1444 					}
   1445 					break;
   1446 				}elseif( !$isCall ){
   1447 					$name = $nameLoop = $val->name;
   1448 					$value = null;
   1449 				}
   1450 			}
   1451 
   1452 			if( $value ){
   1453 				$expressions[] = $value;
   1454 			}
   1455 
   1456 			$argsComma[] = array('name'=>$nameLoop, 'value'=>$value );
   1457 
   1458 			if( $this->MatchChar(',') ){
   1459 				continue;
   1460 			}
   1461 
   1462 			if( $this->MatchChar(';') || $isSemiColonSeperated ){
   1463 
   1464 				if( $expressionContainsNamed ){
   1465 					$this->Error('Cannot mix ; and , as delimiter types');
   1466 				}
   1467 
   1468 				$isSemiColonSeperated = true;
   1469 
   1470 				if( count($expressions) > 1 ){
   1471 					$value = $this->NewObj1('Less_Tree_Value', $expressions);
   1472 				}
   1473 				$argsSemiColon[] = array('name'=>$name, 'value'=>$value );
   1474 
   1475 				$name = null;
   1476 				$expressions = array();
   1477 				$expressionContainsNamed = false;
   1478 			}
   1479 		}
   1480 
   1481 		$this->forget();
   1482 		$returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
   1483 		return $returner;
   1484 	}
   1485 
   1486 
   1487 
   1488 	//
   1489 	// A Mixin definition, with a list of parameters
   1490 	//
   1491 	//	 .rounded (@radius: 2px, @color) {
   1492 	//		...
   1493 	//	 }
   1494 	//
   1495 	// Until we have a finer grained state-machine, we have to
   1496 	// do a look-ahead, to make sure we don't have a mixin call.
   1497 	// See the `rule` function for more information.
   1498 	//
   1499 	// We start by matching `.rounded (`, and then proceed on to
   1500 	// the argument list, which has optional default values.
   1501 	// We store the parameters in `params`, with a `value` key,
   1502 	// if there is a value, such as in the case of `@radius`.
   1503 	//
   1504 	// Once we've got our params list, and a closing `)`, we parse
   1505 	// the `{...}` block.
   1506 	//
   1507 	private function parseMixinDefinition(){
   1508 		$cond = null;
   1509 
   1510 		$char = $this->input[$this->pos];
   1511 		if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
   1512 			return;
   1513 		}
   1514 
   1515 		$this->save();
   1516 
   1517 		$match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
   1518 		if( $match ){
   1519 			$name = $match[1];
   1520 
   1521 			$argInfo = $this->parseMixinArgs( false );
   1522 			$params = $argInfo['args'];
   1523 			$variadic = $argInfo['variadic'];
   1524 
   1525 
   1526 			// .mixincall("@{a}");
   1527 			// looks a bit like a mixin definition..
   1528 			// also
   1529 			// .mixincall(@a: {rule: set;});
   1530 			// so we have to be nice and restore
   1531 			if( !$this->MatchChar(')') ){
   1532 				$this->furthest = $this->pos;
   1533 				$this->restore();
   1534 				return;
   1535 			}
   1536 
   1537 
   1538 			$this->parseComments();
   1539 
   1540 			if ($this->MatchReg('/\\Gwhen/')) { // Guard
   1541 				$cond = $this->expect('parseConditions', 'Expected conditions');
   1542 			}
   1543 
   1544 			$ruleset = $this->parseBlock();
   1545 
   1546 			if( is_array($ruleset) ){
   1547 				$this->forget();
   1548 				return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
   1549 			}
   1550 
   1551 			$this->restore();
   1552 		}else{
   1553 			$this->forget();
   1554 		}
   1555 	}
   1556 
   1557 	//
   1558 	// Entities are the smallest recognized token,
   1559 	// and can be found inside a rule's value.
   1560 	//
   1561 	private function parseEntity(){
   1562 
   1563 		return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
   1564 	}
   1565 
   1566 	//
   1567 	// A Rule terminator. Note that we use `peek()` to check for '}',
   1568 	// because the `block` rule will be expecting it, but we still need to make sure
   1569 	// it's there, if ';' was ommitted.
   1570 	//
   1571 	private function parseEnd(){
   1572 		return $this->MatchChar(';') || $this->PeekChar('}');
   1573 	}
   1574 
   1575 	//
   1576 	// IE's alpha function
   1577 	//
   1578 	//	 alpha(opacity=88)
   1579 	//
   1580 	private function parseAlpha(){
   1581 
   1582 		if ( ! $this->MatchReg('/\\G\(opacity=/i')) {
   1583 			return;
   1584 		}
   1585 
   1586 		$value = $this->MatchReg('/\\G[0-9]+/');
   1587 		if( $value ){
   1588 			$value = $value[0];
   1589 		}else{
   1590 			$value = $this->parseEntitiesVariable();
   1591 			if( !$value ){
   1592 				return;
   1593 			}
   1594 		}
   1595 
   1596 		$this->expectChar(')');
   1597 		return $this->NewObj1('Less_Tree_Alpha',$value);
   1598 	}
   1599 
   1600 
   1601 	//
   1602 	// A Selector Element
   1603 	//
   1604 	//	 div
   1605 	//	 + h1
   1606 	//	 #socks
   1607 	//	 input[type="text"]
   1608 	//
   1609 	// Elements are the building blocks for Selectors,
   1610 	// they are made out of a `Combinator` (see combinator rule),
   1611 	// and an element name, such as a tag a class, or `*`.
   1612 	//
   1613 	private function parseElement(){
   1614 		$c = $this->parseCombinator();
   1615 		$index = $this->pos;
   1616 
   1617 		$e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
   1618 			'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );
   1619 
   1620 		if( is_null($e) ){
   1621 			$this->save();
   1622 			if( $this->MatchChar('(') ){
   1623 				if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){
   1624 					$e = $this->NewObj1('Less_Tree_Paren',$v);
   1625 					$this->forget();
   1626 				}else{
   1627 					$this->restore();
   1628 				}
   1629 			}else{
   1630 				$this->forget();
   1631 			}
   1632 		}
   1633 
   1634 		if( !is_null($e) ){
   1635 			return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
   1636 		}
   1637 	}
   1638 
   1639 	//
   1640 	// Combinators combine elements together, in a Selector.
   1641 	//
   1642 	// Because our parser isn't white-space sensitive, special care
   1643 	// has to be taken, when parsing the descendant combinator, ` `,
   1644 	// as it's an empty space. We have to check the previous character
   1645 	// in the input, to see if it's a ` ` character.
   1646 	//
   1647 	private function parseCombinator(){
   1648 		if( $this->pos < $this->input_len ){
   1649 			$c = $this->input[$this->pos];
   1650 			if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){
   1651 
   1652 				$this->pos++;
   1653 				if( $this->input[$this->pos] === '^' ){
   1654 					$c = '^^';
   1655 					$this->pos++;
   1656 				}
   1657 
   1658 				$this->skipWhitespace(0);
   1659 
   1660 				return $c;
   1661 			}
   1662 
   1663 			if( $this->pos > 0 && $this->isWhitespace(-1) ){
   1664 				return ' ';
   1665 			}
   1666 		}
   1667 	}
   1668 
   1669 	//
   1670 	// A CSS selector (see selector below)
   1671 	// with less extensions e.g. the ability to extend and guard
   1672 	//
   1673 	private function parseLessSelector(){
   1674 		return $this->parseSelector(true);
   1675 	}
   1676 
   1677 	//
   1678 	// A CSS Selector
   1679 	//
   1680 	//	 .class > div + h1
   1681 	//	 li a:hover
   1682 	//
   1683 	// Selectors are made out of one or more Elements, see above.
   1684 	//
   1685 	private function parseSelector( $isLess = false ){
   1686 		$elements = array();
   1687 		$extendList = array();
   1688 		$condition = null;
   1689 		$when = false;
   1690 		$extend = false;
   1691 		$e = null;
   1692 		$c = null;
   1693 		$index = $this->pos;
   1694 
   1695 		while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
   1696 			if( $when ){
   1697 				$condition = $this->expect('parseConditions', 'expected condition');
   1698 			}elseif( $condition ){
   1699 				//error("CSS guard can only be used at the end of selector");
   1700 			}elseif( $extend ){
   1701 				$extendList = array_merge($extendList,$extend);
   1702 			}else{
   1703 				//if( count($extendList) ){
   1704 					//error("Extend can only be used at the end of selector");
   1705 				//}
   1706 				if( $this->pos < $this->input_len ){
   1707 					$c = $this->input[ $this->pos ];
   1708 				}
   1709 				$elements[] = $e;
   1710 				$e = null;
   1711 			}
   1712 
   1713 			if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
   1714 		}
   1715 
   1716 		if( $elements ){
   1717 			return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
   1718 		}
   1719 		if( $extendList ) {
   1720 			$this->Error('Extend must be used to extend a selector, it cannot be used on its own');
   1721 		}
   1722 	}
   1723 
   1724 	private function parseTag(){
   1725 		return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
   1726 	}
   1727 
   1728 	private function parseAttribute(){
   1729 
   1730 		$val = null;
   1731 
   1732 		if( !$this->MatchChar('[') ){
   1733 			return;
   1734 		}
   1735 
   1736 		$key = $this->parseEntitiesVariableCurly();
   1737 		if( !$key ){
   1738 			$key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
   1739 		}
   1740 
   1741 		$op = $this->MatchReg('/\\G[|~*$^]?=/');
   1742 		if( $op ){
   1743 			$val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
   1744 		}
   1745 
   1746 		$this->expectChar(']');
   1747 
   1748 		return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val));
   1749 	}
   1750 
   1751 	//
   1752 	// The `block` rule is used by `ruleset` and `mixin.definition`.
   1753 	// It's a wrapper around the `primary` rule, with added `{}`.
   1754 	//
   1755 	private function parseBlock(){
   1756 		if( $this->MatchChar('{') ){
   1757 			$content = $this->parsePrimary();
   1758 			if( $this->MatchChar('}') ){
   1759 				return $content;
   1760 			}
   1761 		}
   1762 	}
   1763 
   1764 	private function parseBlockRuleset(){
   1765 		$block = $this->parseBlock();
   1766 
   1767 		if( $block ){
   1768 			$block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
   1769 		}
   1770 
   1771 		return $block;
   1772 	}
   1773 
   1774 	private function parseDetachedRuleset(){
   1775 		$blockRuleset = $this->parseBlockRuleset();
   1776 		if( $blockRuleset ){
   1777 			return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
   1778 		}
   1779 	}
   1780 
   1781 	//
   1782 	// div, .class, body > p {...}
   1783 	//
   1784 	private function parseRuleset(){
   1785 		$selectors = array();
   1786 
   1787 		$this->save();
   1788 
   1789 		while( true ){
   1790 			$s = $this->parseLessSelector();
   1791 			if( !$s ){
   1792 				break;
   1793 			}
   1794 			$selectors[] = $s;
   1795 			$this->parseComments();
   1796 
   1797 			if( $s->condition && count($selectors) > 1 ){
   1798 				$this->Error('Guards are only currently allowed on a single selector.');
   1799 			}
   1800 
   1801 			if( !$this->MatchChar(',') ){
   1802 				break;
   1803 			}
   1804 			if( $s->condition ){
   1805 				$this->Error('Guards are only currently allowed on a single selector.');
   1806 			}
   1807 			$this->parseComments();
   1808 		}
   1809 
   1810 
   1811 		if( $selectors ){
   1812 			$rules = $this->parseBlock();
   1813 			if( is_array($rules) ){
   1814 				$this->forget();
   1815 				return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
   1816 			}
   1817 		}
   1818 
   1819 		// Backtrack
   1820 		$this->furthest = $this->pos;
   1821 		$this->restore();
   1822 	}
   1823 
   1824 	/**
   1825 	 * Custom less.php parse function for finding simple name-value css pairs
   1826 	 * ex: width:100px;
   1827 	 *
   1828 	 */
   1829 	private function parseNameValue(){
   1830 
   1831 		$index = $this->pos;
   1832 		$this->save();
   1833 
   1834 
   1835 		//$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
   1836 		$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
   1837 		if( $match ){
   1838 
   1839 			if( $match[4] == '}' ){
   1840 				$this->pos = $index + strlen($match[0])-1;
   1841 			}
   1842 
   1843 			if( $match[3] ){
   1844 				$match[2] .= ' !important';
   1845 			}
   1846 
   1847 			return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
   1848 		}
   1849 
   1850 		$this->restore();
   1851 	}
   1852 
   1853 
   1854 	private function parseRule( $tryAnonymous = null ){
   1855 
   1856 		$merge = false;
   1857 		$startOfRule = $this->pos;
   1858 
   1859 		$c = $this->input[$this->pos];
   1860 		if( $c === '.' || $c === '#' || $c === '&' ){
   1861 			return;
   1862 		}
   1863 
   1864 		$this->save();
   1865 		$name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));
   1866 
   1867 		if( $name ){
   1868 
   1869 			$isVariable = is_string($name);
   1870 
   1871 			$value = null;
   1872 			if( $isVariable ){
   1873 				$value = $this->parseDetachedRuleset();
   1874 			}
   1875 
   1876 			$important = null;
   1877 			if( !$value ){
   1878 
   1879 				// prefer to try to parse first if its a variable or we are compressing
   1880 				// but always fallback on the other one
   1881 				//if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
   1882 				if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
   1883 					$value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
   1884 				}else{
   1885 					$value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
   1886 				}
   1887 
   1888 				$important = $this->parseImportant();
   1889 
   1890 				// a name returned by this.ruleProperty() is always an array of the form:
   1891 				// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
   1892 				// where each item is a tree.Keyword or tree.Variable
   1893 				if( !$isVariable && is_array($name) ){
   1894 					$nm = array_pop($name);
   1895 					if( $nm->value ){
   1896 						$merge = $nm->value;
   1897 					}
   1898 				}
   1899 			}
   1900 
   1901 
   1902 			if( $value && $this->parseEnd() ){
   1903 				$this->forget();
   1904 				return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
   1905 			}else{
   1906 				$this->furthest = $this->pos;
   1907 				$this->restore();
   1908 				if( $value && !$tryAnonymous ){
   1909 					return $this->parseRule(true);
   1910 				}
   1911 			}
   1912 		}else{
   1913 			$this->forget();
   1914 		}
   1915 	}
   1916 
   1917 	function parseAnonymousValue(){
   1918 
   1919 		if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
   1920 			$this->pos += strlen($match[1]);
   1921 			return $this->NewObj1('Less_Tree_Anonymous',$match[1]);
   1922 		}
   1923 	}
   1924 
   1925 	//
   1926 	// An @import directive
   1927 	//
   1928 	//	 @import "lib";
   1929 	//
   1930 	// Depending on our environment, importing is done differently:
   1931 	// In the browser, it's an XHR request, in Node, it would be a
   1932 	// file-system operation. The function used for importing is
   1933 	// stored in `import`, which we pass to the Import constructor.
   1934 	//
   1935 	private function parseImport(){
   1936 
   1937 		$this->save();
   1938 
   1939 		$dir = $this->MatchReg('/\\G@import?\s+/');
   1940 
   1941 		if( $dir ){
   1942 			$options = $this->parseImportOptions();
   1943 			$path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));
   1944 
   1945 			if( $path ){
   1946 				$features = $this->parseMediaFeatures();
   1947 				if( $this->MatchChar(';') ){
   1948 					if( $features ){
   1949 						$features = $this->NewObj1('Less_Tree_Value',$features);
   1950 					}
   1951 
   1952 					$this->forget();
   1953 					return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
   1954 				}
   1955 			}
   1956 		}
   1957 
   1958 		$this->restore();
   1959 	}
   1960 
   1961 	private function parseImportOptions(){
   1962 
   1963 		$options = array();
   1964 
   1965 		// list of options, surrounded by parens
   1966 		if( !$this->MatchChar('(') ){
   1967 			return $options;
   1968 		}
   1969 		do{
   1970 			$optionName = $this->parseImportOption();
   1971 			if( $optionName ){
   1972 				$value = true;
   1973 				switch( $optionName ){
   1974 					case "css":
   1975 						$optionName = "less";
   1976 						$value = false;
   1977 					break;
   1978 					case "once":
   1979 						$optionName = "multiple";
   1980 						$value = false;
   1981 					break;
   1982 				}
   1983 				$options[$optionName] = $value;
   1984 				if( !$this->MatchChar(',') ){ break; }
   1985 			}
   1986 		}while( $optionName );
   1987 		$this->expectChar(')');
   1988 		return $options;
   1989 	}
   1990 
   1991 	private function parseImportOption(){
   1992 		$opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference|optional)/');
   1993 		if( $opt ){
   1994 			return $opt[1];
   1995 		}
   1996 	}
   1997 
   1998 	private function parseMediaFeature() {
   1999 		$nodes = array();
   2000 
   2001 		do{
   2002 			$e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
   2003 			if( $e ){
   2004 				$nodes[] = $e;
   2005 			} elseif ($this->MatchChar('(')) {
   2006 				$p = $this->parseProperty();
   2007 				$e = $this->parseValue();
   2008 				if ($this->MatchChar(')')) {
   2009 					if ($p && $e) {
   2010 						$r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
   2011 						$nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
   2012 					} elseif ($e) {
   2013 						$nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
   2014 					} else {
   2015 						return null;
   2016 					}
   2017 				} else
   2018 					return null;
   2019 			}
   2020 		} while ($e);
   2021 
   2022 		if ($nodes) {
   2023 			return $this->NewObj1('Less_Tree_Expression',$nodes);
   2024 		}
   2025 	}
   2026 
   2027 	private function parseMediaFeatures() {
   2028 		$features = array();
   2029 
   2030 		do{
   2031 			$e = $this->parseMediaFeature();
   2032 			if( $e ){
   2033 				$features[] = $e;
   2034 				if (!$this->MatchChar(',')) break;
   2035 			}else{
   2036 				$e = $this->parseEntitiesVariable();
   2037 				if( $e ){
   2038 					$features[] = $e;
   2039 					if (!$this->MatchChar(',')) break;
   2040 				}
   2041 			}
   2042 		} while ($e);
   2043 
   2044 		return $features ? $features : null;
   2045 	}
   2046 
   2047 	private function parseMedia() {
   2048 		if( $this->MatchReg('/\\G@media/') ){
   2049 			$features = $this->parseMediaFeatures();
   2050 			$rules = $this->parseBlock();
   2051 
   2052 			if( is_array($rules) ){
   2053 				return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
   2054 			}
   2055 		}
   2056 	}
   2057 
   2058 
   2059 	//
   2060 	// A CSS Directive
   2061 	//
   2062 	// @charset "utf-8";
   2063 	//
   2064 	private function parseDirective(){
   2065 
   2066 		if( !$this->PeekChar('@') ){
   2067 			return;
   2068 		}
   2069 
   2070 		$rules = null;
   2071 		$index = $this->pos;
   2072 		$hasBlock = true;
   2073 		$hasIdentifier = false;
   2074 		$hasExpression = false;
   2075 		$hasUnknown = false;
   2076 
   2077 
   2078 		$value = $this->MatchFuncs(array('parseImport','parseMedia'));
   2079 		if( $value ){
   2080 			return $value;
   2081 		}
   2082 
   2083 		$this->save();
   2084 
   2085 		$name = $this->MatchReg('/\\G@[a-z-]+/');
   2086 
   2087 		if( !$name ) return;
   2088 		$name = $name[0];
   2089 
   2090 
   2091 		$nonVendorSpecificName = $name;
   2092 		$pos = strpos($name,'-', 2);
   2093 		if( $name[1] == '-' && $pos > 0 ){
   2094 			$nonVendorSpecificName = "@" . substr($name, $pos + 1);
   2095 		}
   2096 
   2097 
   2098 		switch( $nonVendorSpecificName ){
   2099 			/*
   2100 			case "@font-face":
   2101 			case "@viewport":
   2102 			case "@top-left":
   2103 			case "@top-left-corner":
   2104 			case "@top-center":
   2105 			case "@top-right":
   2106 			case "@top-right-corner":
   2107 			case "@bottom-left":
   2108 			case "@bottom-left-corner":
   2109 			case "@bottom-center":
   2110 			case "@bottom-right":
   2111 			case "@bottom-right-corner":
   2112 			case "@left-top":
   2113 			case "@left-middle":
   2114 			case "@left-bottom":
   2115 			case "@right-top":
   2116 			case "@right-middle":
   2117 			case "@right-bottom":
   2118 			hasBlock = true;
   2119 			break;
   2120 			*/
   2121 			case "@charset":
   2122 				$hasIdentifier = true;
   2123 				$hasBlock = false;
   2124 				break;
   2125 			case "@namespace":
   2126 				$hasExpression = true;
   2127 				$hasBlock = false;
   2128 				break;
   2129 			case "@keyframes":
   2130 				$hasIdentifier = true;
   2131 				break;
   2132 			case "@host":
   2133 			case "@page":
   2134 			case "@document":
   2135 			case "@supports":
   2136 				$hasUnknown = true;
   2137 				break;
   2138 		}
   2139 
   2140 		if( $hasIdentifier ){
   2141 			$value = $this->parseEntity();
   2142 			if( !$value ){
   2143 				$this->error("expected " . $name . " identifier");
   2144 			}
   2145 		} else if( $hasExpression ){
   2146 			$value = $this->parseExpression();
   2147 			if( !$value ){
   2148 				$this->error("expected " . $name. " expression");
   2149 			}
   2150 		} else if ($hasUnknown) {
   2151 
   2152 			$value = $this->MatchReg('/\\G[^{;]+/');
   2153 			if( $value ){
   2154 				$value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
   2155 			}
   2156 		}
   2157 
   2158 		if( $hasBlock ){
   2159 			$rules = $this->parseBlockRuleset();
   2160 		}
   2161 
   2162 		if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
   2163 			$this->forget();
   2164 			return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
   2165 		}
   2166 
   2167 		$this->restore();
   2168 	}
   2169 
   2170 
   2171 	//
   2172 	// A Value is a comma-delimited list of Expressions
   2173 	//
   2174 	//	 font-family: Baskerville, Georgia, serif;
   2175 	//
   2176 	// In a Rule, a Value represents everything after the `:`,
   2177 	// and before the `;`.
   2178 	//
   2179 	private function parseValue(){
   2180 		$expressions = array();
   2181 
   2182 		do{
   2183 			$e = $this->parseExpression();
   2184 			if( $e ){
   2185 				$expressions[] = $e;
   2186 				if (! $this->MatchChar(',')) {
   2187 					break;
   2188 				}
   2189 			}
   2190 		}while($e);
   2191 
   2192 		if( $expressions ){
   2193 			return $this->NewObj1('Less_Tree_Value',$expressions);
   2194 		}
   2195 	}
   2196 
   2197 	private function parseImportant (){
   2198 		if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
   2199 			return ' !important';
   2200 		}
   2201 	}
   2202 
   2203 	private function parseSub (){
   2204 
   2205 		if( $this->MatchChar('(') ){
   2206 			$a = $this->parseAddition();
   2207 			if( $a ){
   2208 				$this->expectChar(')');
   2209 				return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
   2210 			}
   2211 		}
   2212 	}
   2213 
   2214 
   2215 	/**
   2216 	 * Parses multiplication operation
   2217 	 *
   2218 	 * @return Less_Tree_Operation|null
   2219 	 */
   2220 	function parseMultiplication(){
   2221 
   2222 		$return = $m = $this->parseOperand();
   2223 		if( $return ){
   2224 			while( true ){
   2225 
   2226 				$isSpaced = $this->isWhitespace( -1 );
   2227 
   2228 				if( $this->PeekReg('/\\G\/[*\/]/') ){
   2229 					break;
   2230 				}
   2231 
   2232 				$op = $this->MatchChar('/');
   2233 				if( !$op ){
   2234 					$op = $this->MatchChar('*');
   2235 					if( !$op ){
   2236 						break;
   2237 					}
   2238 				}
   2239 
   2240 				$a = $this->parseOperand();
   2241 
   2242 				if(!$a) { break; }
   2243 
   2244 				$m->parensInOp = true;
   2245 				$a->parensInOp = true;
   2246 				$return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
   2247 			}
   2248 		}
   2249 		return $return;
   2250 
   2251 	}
   2252 
   2253 
   2254 	/**
   2255 	 * Parses an addition operation
   2256 	 *
   2257 	 * @return Less_Tree_Operation|null
   2258 	 */
   2259 	private function parseAddition (){
   2260 
   2261 		$return = $m = $this->parseMultiplication();
   2262 		if( $return ){
   2263 			while( true ){
   2264 
   2265 				$isSpaced = $this->isWhitespace( -1 );
   2266 
   2267 				$op = $this->MatchReg('/\\G[-+]\s+/');
   2268 				if( $op ){
   2269 					$op = $op[0];
   2270 				}else{
   2271 					if( !$isSpaced ){
   2272 						$op = $this->match(array('#+','#-'));
   2273 					}
   2274 					if( !$op ){
   2275 						break;
   2276 					}
   2277 				}
   2278 
   2279 				$a = $this->parseMultiplication();
   2280 				if( !$a ){
   2281 					break;
   2282 				}
   2283 
   2284 				$m->parensInOp = true;
   2285 				$a->parensInOp = true;
   2286 				$return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
   2287 			}
   2288 		}
   2289 
   2290 		return $return;
   2291 	}
   2292 
   2293 
   2294 	/**
   2295 	 * Parses the conditions
   2296 	 *
   2297 	 * @return Less_Tree_Condition|null
   2298 	 */
   2299 	private function parseConditions() {
   2300 		$index = $this->pos;
   2301 		$return = $a = $this->parseCondition();
   2302 		if( $a ){
   2303 			while( true ){
   2304 				if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') ||  !$this->MatchChar(',') ){
   2305 					break;
   2306 				}
   2307 				$b = $this->parseCondition();
   2308 				if( !$b ){
   2309 					break;
   2310 				}
   2311 
   2312 				$return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
   2313 			}
   2314 			return $return;
   2315 		}
   2316 	}
   2317 
   2318 	private function parseCondition() {
   2319 		$index = $this->pos;
   2320 		$negate = false;
   2321 		$c = null;
   2322 
   2323 		if ($this->MatchReg('/\\Gnot/')) $negate = true;
   2324 		$this->expectChar('(');
   2325 		$a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
   2326 
   2327 		if( $a ){
   2328 			$op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
   2329 			if( $op ){
   2330 				$b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
   2331 				if( $b ){
   2332 					$c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
   2333 				} else {
   2334 					$this->Error('Unexpected expression');
   2335 				}
   2336 			} else {
   2337 				$k = $this->NewObj1('Less_Tree_Keyword','true');
   2338 				$c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
   2339 			}
   2340 			$this->expectChar(')');
   2341 			return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
   2342 		}
   2343 	}
   2344 
   2345 	/**
   2346 	 * An operand is anything that can be part of an operation,
   2347 	 * such as a Color, or a Variable
   2348 	 *
   2349 	 */
   2350 	private function parseOperand (){
   2351 
   2352 		$negate = false;
   2353 		$offset = $this->pos+1;
   2354 		if( $offset >= $this->input_len ){
   2355 			return;
   2356 		}
   2357 		$char = $this->input[$offset];
   2358 		if( $char === '@' || $char === '(' ){
   2359 			$negate = $this->MatchChar('-');
   2360 		}
   2361 
   2362 		$o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));
   2363 
   2364 		if( $negate ){
   2365 			$o->parensInOp = true;
   2366 			$o = $this->NewObj1('Less_Tree_Negative',$o);
   2367 		}
   2368 
   2369 		return $o;
   2370 	}
   2371 
   2372 
   2373 	/**
   2374 	 * Expressions either represent mathematical operations,
   2375 	 * or white-space delimited Entities.
   2376 	 *
   2377 	 *	 1px solid black
   2378 	 *	 @var * 2
   2379 	 *
   2380 	 * @return Less_Tree_Expression|null
   2381 	 */
   2382 	private function parseExpression (){
   2383 		$entities = array();
   2384 
   2385 		do{
   2386 			$e = $this->MatchFuncs(array('parseAddition','parseEntity'));
   2387 			if( $e ){
   2388 				$entities[] = $e;
   2389 				// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
   2390 				if( !$this->PeekReg('/\\G\/[\/*]/') ){
   2391 					$delim = $this->MatchChar('/');
   2392 					if( $delim ){
   2393 						$entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
   2394 					}
   2395 				}
   2396 			}
   2397 		}while($e);
   2398 
   2399 		if( $entities ){
   2400 			return $this->NewObj1('Less_Tree_Expression',$entities);
   2401 		}
   2402 	}
   2403 
   2404 
   2405 	/**
   2406 	 * Parse a property
   2407 	 * eg: 'min-width', 'orientation', etc
   2408 	 *
   2409 	 * @return string
   2410 	 */
   2411 	private function parseProperty (){
   2412 		$name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
   2413 		if( $name ){
   2414 			return $name[1];
   2415 		}
   2416 	}
   2417 
   2418 
   2419 	/**
   2420 	 * Parse a rule property
   2421 	 * eg: 'color', 'width', 'height', etc
   2422 	 *
   2423 	 * @return string
   2424 	 */
   2425 	private function parseRuleProperty(){
   2426 		$offset = $this->pos;
   2427 		$name = array();
   2428 		$index = array();
   2429 		$length = 0;
   2430 
   2431 
   2432 		$this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
   2433 		while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !
   2434 
   2435 		if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
   2436 			// at last, we have the complete match now. move forward,
   2437 			// convert name particles to tree objects and return:
   2438 			$this->skipWhitespace($length);
   2439 
   2440 			if( $name[0] === '' ){
   2441 				array_shift($name);
   2442 				array_shift($index);
   2443 			}
   2444 			foreach($name as $k => $s ){
   2445 				if( !$s || $s[0] !== '@' ){
   2446 					$name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
   2447 				}else{
   2448 					$name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
   2449 				}
   2450 			}
   2451 			return $name;
   2452 		}
   2453 
   2454 
   2455 	}
   2456 
   2457 	private function rulePropertyMatch( $re, &$offset, &$length,  &$index, &$name ){
   2458 		preg_match($re, $this->input, $a, 0, $offset);
   2459 		if( $a ){
   2460 			$index[] = $this->pos + $length;
   2461 			$length += strlen($a[0]);
   2462 			$offset += strlen($a[0]);
   2463 			$name[] = $a[1];
   2464 			return true;
   2465 		}
   2466 	}
   2467 
   2468 	public static function serializeVars( $vars ){
   2469 		$s = '';
   2470 
   2471 		foreach($vars as $name => $value){
   2472 			$s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
   2473 		}
   2474 
   2475 		return $s;
   2476 	}
   2477 
   2478 
   2479 	/**
   2480 	 * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
   2481 	 *
   2482 	 * @param string $b
   2483 	 */
   2484 	public static function is_method($a,$b){
   2485 		return is_object($a) && method_exists($a,$b);
   2486 	}
   2487 
   2488 
   2489 	/**
   2490 	 * Round numbers similarly to javascript
   2491 	 * eg: 1.499999 to 1 instead of 2
   2492 	 *
   2493 	 */
   2494 	public static function round($i, $precision = 0){
   2495 
   2496 		$precision = pow(10,$precision);
   2497 		$i = $i*$precision;
   2498 
   2499 		$ceil = ceil($i);
   2500 		$floor = floor($i);
   2501 		if( ($ceil - $i) <= ($i - $floor) ){
   2502 			return $ceil/$precision;
   2503 		}else{
   2504 			return $floor/$precision;
   2505 		}
   2506 	}
   2507 
   2508 
   2509 	/**
   2510 	 * Create Less_Tree_* objects and optionally generate a cache string
   2511 	 *
   2512 	 * @return mixed
   2513 	 */
   2514 	public function NewObj0($class){
   2515 		$obj = new $class();
   2516 		if( $this->CacheEnabled() ){
   2517 			$obj->cache_string = ' new '.$class.'()';
   2518 		}
   2519 		return $obj;
   2520 	}
   2521 
   2522 	public function NewObj1($class, $arg){
   2523 		$obj = new $class( $arg );
   2524 		if( $this->CacheEnabled() ){
   2525 			$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
   2526 		}
   2527 		return $obj;
   2528 	}
   2529 
   2530 	public function NewObj2($class, $args){
   2531 		$obj = new $class( $args[0], $args[1] );
   2532 		if( $this->CacheEnabled() ){
   2533 			$this->ObjCache( $obj, $class, $args);
   2534 		}
   2535 		return $obj;
   2536 	}
   2537 
   2538 	public function NewObj3($class, $args){
   2539 		$obj = new $class( $args[0], $args[1], $args[2] );
   2540 		if( $this->CacheEnabled() ){
   2541 			$this->ObjCache( $obj, $class, $args);
   2542 		}
   2543 		return $obj;
   2544 	}
   2545 
   2546 	public function NewObj4($class, $args){
   2547 		$obj = new $class( $args[0], $args[1], $args[2], $args[3] );
   2548 		if( $this->CacheEnabled() ){
   2549 			$this->ObjCache( $obj, $class, $args);
   2550 		}
   2551 		return $obj;
   2552 	}
   2553 
   2554 	public function NewObj5($class, $args){
   2555 		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
   2556 		if( $this->CacheEnabled() ){
   2557 			$this->ObjCache( $obj, $class, $args);
   2558 		}
   2559 		return $obj;
   2560 	}
   2561 
   2562 	public function NewObj6($class, $args){
   2563 		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
   2564 		if( $this->CacheEnabled() ){
   2565 			$this->ObjCache( $obj, $class, $args);
   2566 		}
   2567 		return $obj;
   2568 	}
   2569 
   2570 	public function NewObj7($class, $args){
   2571 		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
   2572 		if( $this->CacheEnabled() ){
   2573 			$this->ObjCache( $obj, $class, $args);
   2574 		}
   2575 		return $obj;
   2576 	}
   2577 
   2578 	//caching
   2579 	public function ObjCache($obj, $class, $args=array()){
   2580 		$obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
   2581 	}
   2582 
   2583 	public function ArgCache($args){
   2584 		return implode(',',array_map( array('Less_Parser','ArgString'),$args));
   2585 	}
   2586 
   2587 
   2588 	/**
   2589 	 * Convert an argument to a string for use in the parser cache
   2590 	 *
   2591 	 * @return string
   2592 	 */
   2593 	public static function ArgString($arg){
   2594 
   2595 		$type = gettype($arg);
   2596 
   2597 		if( $type === 'object'){
   2598 			$string = $arg->cache_string;
   2599 			unset($arg->cache_string);
   2600 			return $string;
   2601 
   2602 		}elseif( $type === 'array' ){
   2603 			$string = ' Array(';
   2604 			foreach($arg as $k => $a){
   2605 				$string .= var_export($k,true).' => '.self::ArgString($a).',';
   2606 			}
   2607 			return $string . ')';
   2608 		}
   2609 
   2610 		return var_export($arg,true);
   2611 	}
   2612 
   2613 	public function Error($msg){
   2614 		throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
   2615 	}
   2616 
   2617 	public static function WinPath($path){
   2618 		return str_replace('\\', '/', $path);
   2619 	}
   2620 
   2621 	public function CacheEnabled(){
   2622 	    return false;
   2623 		//return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback')));
   2624 	}
   2625 
   2626 }
   2627  
   2628 
   2629 /**
   2630  * Utility for css colors
   2631  *
   2632  * @package Less
   2633  * @subpackage color
   2634  */
   2635 class Less_Colors {
   2636 
   2637 	public static $colors = array(
   2638 			'aliceblue'=>'#f0f8ff',
   2639 			'antiquewhite'=>'#faebd7',
   2640 			'aqua'=>'#00ffff',
   2641 			'aquamarine'=>'#7fffd4',
   2642 			'azure'=>'#f0ffff',
   2643 			'beige'=>'#f5f5dc',
   2644 			'bisque'=>'#ffe4c4',
   2645 			'black'=>'#000000',
   2646 			'blanchedalmond'=>'#ffebcd',
   2647 			'blue'=>'#0000ff',
   2648 			'blueviolet'=>'#8a2be2',
   2649 			'brown'=>'#a52a2a',
   2650 			'burlywood'=>'#deb887',
   2651 			'cadetblue'=>'#5f9ea0',
   2652 			'chartreuse'=>'#7fff00',
   2653 			'chocolate'=>'#d2691e',
   2654 			'coral'=>'#ff7f50',
   2655 			'cornflowerblue'=>'#6495ed',
   2656 			'cornsilk'=>'#fff8dc',
   2657 			'crimson'=>'#dc143c',
   2658 			'cyan'=>'#00ffff',
   2659 			'darkblue'=>'#00008b',
   2660 			'darkcyan'=>'#008b8b',
   2661 			'darkgoldenrod'=>'#b8860b',
   2662 			'darkgray'=>'#a9a9a9',
   2663 			'darkgrey'=>'#a9a9a9',
   2664 			'darkgreen'=>'#006400',
   2665 			'darkkhaki'=>'#bdb76b',
   2666 			'darkmagenta'=>'#8b008b',
   2667 			'darkolivegreen'=>'#556b2f',
   2668 			'darkorange'=>'#ff8c00',
   2669 			'darkorchid'=>'#9932cc',
   2670 			'darkred'=>'#8b0000',
   2671 			'darksalmon'=>'#e9967a',
   2672 			'darkseagreen'=>'#8fbc8f',
   2673 			'darkslateblue'=>'#483d8b',
   2674 			'darkslategray'=>'#2f4f4f',
   2675 			'darkslategrey'=>'#2f4f4f',
   2676 			'darkturquoise'=>'#00ced1',
   2677 			'darkviolet'=>'#9400d3',
   2678 			'deeppink'=>'#ff1493',
   2679 			'deepskyblue'=>'#00bfff',
   2680 			'dimgray'=>'#696969',
   2681 			'dimgrey'=>'#696969',
   2682 			'dodgerblue'=>'#1e90ff',
   2683 			'firebrick'=>'#b22222',
   2684 			'floralwhite'=>'#fffaf0',
   2685 			'forestgreen'=>'#228b22',
   2686 			'fuchsia'=>'#ff00ff',
   2687 			'gainsboro'=>'#dcdcdc',
   2688 			'ghostwhite'=>'#f8f8ff',
   2689 			'gold'=>'#ffd700',
   2690 			'goldenrod'=>'#daa520',
   2691 			'gray'=>'#808080',
   2692 			'grey'=>'#808080',
   2693 			'green'=>'#008000',
   2694 			'greenyellow'=>'#adff2f',
   2695 			'honeydew'=>'#f0fff0',
   2696 			'hotpink'=>'#ff69b4',
   2697 			'indianred'=>'#cd5c5c',
   2698 			'indigo'=>'#4b0082',
   2699 			'ivory'=>'#fffff0',
   2700 			'khaki'=>'#f0e68c',
   2701 			'lavender'=>'#e6e6fa',
   2702 			'lavenderblush'=>'#fff0f5',
   2703 			'lawngreen'=>'#7cfc00',
   2704 			'lemonchiffon'=>'#fffacd',
   2705 			'lightblue'=>'#add8e6',
   2706 			'lightcoral'=>'#f08080',
   2707 			'lightcyan'=>'#e0ffff',
   2708 			'lightgoldenrodyellow'=>'#fafad2',
   2709 			'lightgray'=>'#d3d3d3',
   2710 			'lightgrey'=>'#d3d3d3',
   2711 			'lightgreen'=>'#90ee90',
   2712 			'lightpink'=>'#ffb6c1',
   2713 			'lightsalmon'=>'#ffa07a',
   2714 			'lightseagreen'=>'#20b2aa',
   2715 			'lightskyblue'=>'#87cefa',
   2716 			'lightslategray'=>'#778899',
   2717 			'lightslategrey'=>'#778899',
   2718 			'lightsteelblue'=>'#b0c4de',
   2719 			'lightyellow'=>'#ffffe0',
   2720 			'lime'=>'#00ff00',
   2721 			'limegreen'=>'#32cd32',
   2722 			'linen'=>'#faf0e6',
   2723 			'magenta'=>'#ff00ff',
   2724 			'maroon'=>'#800000',
   2725 			'mediumaquamarine'=>'#66cdaa',
   2726 			'mediumblue'=>'#0000cd',
   2727 			'mediumorchid'=>'#ba55d3',
   2728 			'mediumpurple'=>'#9370d8',
   2729 			'mediumseagreen'=>'#3cb371',
   2730 			'mediumslateblue'=>'#7b68ee',
   2731 			'mediumspringgreen'=>'#00fa9a',
   2732 			'mediumturquoise'=>'#48d1cc',
   2733 			'mediumvioletred'=>'#c71585',
   2734 			'midnightblue'=>'#191970',
   2735 			'mintcream'=>'#f5fffa',
   2736 			'mistyrose'=>'#ffe4e1',
   2737 			'moccasin'=>'#ffe4b5',
   2738 			'navajowhite'=>'#ffdead',
   2739 			'navy'=>'#000080',
   2740 			'oldlace'=>'#fdf5e6',
   2741 			'olive'=>'#808000',
   2742 			'olivedrab'=>'#6b8e23',
   2743 			'orange'=>'#ffa500',
   2744 			'orangered'=>'#ff4500',
   2745 			'orchid'=>'#da70d6',
   2746 			'palegoldenrod'=>'#eee8aa',
   2747 			'palegreen'=>'#98fb98',
   2748 			'paleturquoise'=>'#afeeee',
   2749 			'palevioletred'=>'#d87093',
   2750 			'papayawhip'=>'#ffefd5',
   2751 			'peachpuff'=>'#ffdab9',
   2752 			'peru'=>'#cd853f',
   2753 			'pink'=>'#ffc0cb',
   2754 			'plum'=>'#dda0dd',
   2755 			'powderblue'=>'#b0e0e6',
   2756 			'purple'=>'#800080',
   2757 			'red'=>'#ff0000',
   2758 			'rosybrown'=>'#bc8f8f',
   2759 			'royalblue'=>'#4169e1',
   2760 			'saddlebrown'=>'#8b4513',
   2761 			'salmon'=>'#fa8072',
   2762 			'sandybrown'=>'#f4a460',
   2763 			'seagreen'=>'#2e8b57',
   2764 			'seashell'=>'#fff5ee',
   2765 			'sienna'=>'#a0522d',
   2766 			'silver'=>'#c0c0c0',
   2767 			'skyblue'=>'#87ceeb',
   2768 			'slateblue'=>'#6a5acd',
   2769 			'slategray'=>'#708090',
   2770 			'slategrey'=>'#708090',
   2771 			'snow'=>'#fffafa',
   2772 			'springgreen'=>'#00ff7f',
   2773 			'steelblue'=>'#4682b4',
   2774 			'tan'=>'#d2b48c',
   2775 			'teal'=>'#008080',
   2776 			'thistle'=>'#d8bfd8',
   2777 			'tomato'=>'#ff6347',
   2778 			'turquoise'=>'#40e0d0',
   2779 			'violet'=>'#ee82ee',
   2780 			'wheat'=>'#f5deb3',
   2781 			'white'=>'#ffffff',
   2782 			'whitesmoke'=>'#f5f5f5',
   2783 			'yellow'=>'#ffff00',
   2784 			'yellowgreen'=>'#9acd32'
   2785 		);
   2786 
   2787 	public static function hasOwnProperty($color) {
   2788 		return isset(self::$colors[$color]);
   2789 	}
   2790 
   2791 
   2792 	public static function color($color) {
   2793 		return self::$colors[$color];
   2794 	}
   2795 
   2796 }
   2797  
   2798 
   2799 
   2800 /**
   2801  * Environment
   2802  *
   2803  * @package Less
   2804  * @subpackage environment
   2805  */
   2806 class Less_Environment{
   2807 
   2808 	//public $paths = array();				// option - unmodified - paths to search for imports on
   2809 	//public static $files = array();		// list of files that have been imported, used for import-once
   2810 	//public $rootpath;						// option - rootpath to append to URL's
   2811 	//public static $strictImports = null;	// option -
   2812 	//public $insecure;						// option - whether to allow imports from insecure ssl hosts
   2813 	//public $processImports;				// option - whether to process imports. if false then imports will not be imported
   2814 	//public $javascriptEnabled;			// option - whether JavaScript is enabled. if undefined, defaults to true
   2815 	//public $useFileCache;					// browser only - whether to use the per file session cache
   2816 	public $currentFileInfo;				// information about the current file - for error reporting and importing and making urls relative etc.
   2817 
   2818 	public $importMultiple = false; 		// whether we are currently importing multiple copies
   2819 
   2820 
   2821 	/**
   2822 	 * @var array
   2823 	 */
   2824 	public $frames = array();
   2825 
   2826 	/**
   2827 	 * @var array
   2828 	 */
   2829 	public $mediaBlocks = array();
   2830 
   2831 	/**
   2832 	 * @var array
   2833 	 */
   2834 	public $mediaPath = array();
   2835 
   2836 	public static $parensStack = 0;
   2837 
   2838 	public static $tabLevel = 0;
   2839 
   2840 	public static $lastRule = false;
   2841 
   2842 	public static $_outputMap;
   2843 
   2844 	public static $mixin_stack = 0;
   2845 
   2846 	/**
   2847 	 * @var array
   2848 	 */
   2849 	public $functions = array();
   2850 
   2851 
   2852 	public function Init(){
   2853 
   2854 		self::$parensStack = 0;
   2855 		self::$tabLevel = 0;
   2856 		self::$lastRule = false;
   2857 		self::$mixin_stack = 0;
   2858 
   2859 		if( Less_Parser::$options['compress'] ){
   2860 
   2861 			Less_Environment::$_outputMap = array(
   2862 				','	=> ',',
   2863 				': ' => ':',
   2864 				''  => '',
   2865 				' ' => ' ',
   2866 				':' => ' :',
   2867 				'+' => '+',
   2868 				'~' => '~',
   2869 				'>' => '>',
   2870 				'|' => '|',
   2871 		        '^' => '^',
   2872 		        '^^' => '^^'
   2873 			);
   2874 
   2875 		}else{
   2876 
   2877 			Less_Environment::$_outputMap = array(
   2878 				','	=> ', ',
   2879 				': ' => ': ',
   2880 				''  => '',
   2881 				' ' => ' ',
   2882 				':' => ' :',
   2883 				'+' => ' + ',
   2884 				'~' => ' ~ ',
   2885 				'>' => ' > ',
   2886 				'|' => '|',
   2887 		        '^' => ' ^ ',
   2888 		        '^^' => ' ^^ '
   2889 			);
   2890 
   2891 		}
   2892 	}
   2893 
   2894 
   2895 	public function copyEvalEnv($frames = array() ){
   2896 		$new_env = new Less_Environment();
   2897 		$new_env->frames = $frames;
   2898 		return $new_env;
   2899 	}
   2900 
   2901 
   2902 	public static function isMathOn(){
   2903 		return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
   2904 	}
   2905 
   2906 	public static function isPathRelative($path){
   2907 		return !preg_match('/^(?:[a-z-]+:|\/)/',$path);
   2908 	}
   2909 
   2910 
   2911 	/**
   2912 	 * Canonicalize a path by resolving references to '/./', '/../'
   2913 	 * Does not remove leading "../"
   2914 	 * @param string path or url
   2915 	 * @return string Canonicalized path
   2916 	 *
   2917 	 */
   2918 	public static function normalizePath($path){
   2919 
   2920 		$segments = explode('/',$path);
   2921 		$segments = array_reverse($segments);
   2922 
   2923 		$path = array();
   2924 		$path_len = 0;
   2925 
   2926 		while( $segments ){
   2927 			$segment = array_pop($segments);
   2928 			switch( $segment ) {
   2929 
   2930 				case '.':
   2931 				break;
   2932 
   2933 				case '..':
   2934 					if( !$path_len || ( $path[$path_len-1] === '..') ){
   2935 						$path[] = $segment;
   2936 						$path_len++;
   2937 					}else{
   2938 						array_pop($path);
   2939 						$path_len--;
   2940 					}
   2941 				break;
   2942 
   2943 				default:
   2944 					$path[] = $segment;
   2945 					$path_len++;
   2946 				break;
   2947 			}
   2948 		}
   2949 
   2950 		return implode('/',$path);
   2951 	}
   2952 
   2953 
   2954 	public function unshiftFrame($frame){
   2955 		array_unshift($this->frames, $frame);
   2956 	}
   2957 
   2958 	public function shiftFrame(){
   2959 		return array_shift($this->frames);
   2960 	}
   2961 
   2962 }
   2963  
   2964 
   2965 /**
   2966  * Builtin functions
   2967  *
   2968  * @package Less
   2969  * @subpackage function
   2970  * @see http://lesscss.org/functions/
   2971  */
   2972 class Less_Functions{
   2973 
   2974 	public $env;
   2975 	public $currentFileInfo;
   2976 
   2977 	function __construct($env, $currentFileInfo = null ){
   2978 		$this->env = $env;
   2979 		$this->currentFileInfo = $currentFileInfo;
   2980 	}
   2981 
   2982 	/**
   2983 	 * @param string $op
   2984 	 */
   2985 	public static function operate( $op, $a, $b ){
   2986 		switch ($op) {
   2987 			case '+': return $a + $b;
   2988 			case '-': return $a - $b;
   2989 			case '*': return $a * $b;
   2990 			case '/': return $a / $b;
   2991 		}
   2992 	}
   2993 
   2994 	public static function clamp($val, $max = 1){
   2995 		return min( max($val, 0), $max);
   2996 	}
   2997 
   2998 	public static function fround( $value ){
   2999 
   3000 		if( $value === 0 ){
   3001 			return $value;
   3002 		}
   3003 
   3004 		if( Less_Parser::$options['numPrecision'] ){
   3005 			$p = pow(10, Less_Parser::$options['numPrecision']);
   3006 			return round( $value * $p) / $p;
   3007 		}
   3008 		return $value;
   3009 	}
   3010 
   3011 	public static function number($n){
   3012 
   3013 		if ($n instanceof Less_Tree_Dimension) {
   3014 			return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value);
   3015 		} else if (is_numeric($n)) {
   3016 			return $n;
   3017 		} else {
   3018 			throw new Less_Exception_Compiler("color functions take numbers as parameters");
   3019 		}
   3020 	}
   3021 
   3022 	public static function scaled($n, $size = 255 ){
   3023 		if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){
   3024 			return (float)$n->value * $size / 100;
   3025 		} else {
   3026 			return Less_Functions::number($n);
   3027 		}
   3028 	}
   3029 
   3030 	public function rgb ($r = null, $g = null, $b = null){
   3031 		if (is_null($r) || is_null($g) || is_null($b)) {
   3032 			throw new Less_Exception_Compiler("rgb expects three parameters");
   3033 		}
   3034 		return $this->rgba($r, $g, $b, 1.0);
   3035 	}
   3036 
   3037 	public function rgba($r = null, $g = null, $b = null, $a = null){
   3038 		$rgb = array($r, $g, $b);
   3039 		$rgb = array_map(array('Less_Functions','scaled'),$rgb);
   3040 
   3041 		$a = self::number($a);
   3042 		return new Less_Tree_Color($rgb, $a);
   3043 	}
   3044 
   3045 	public function hsl($h, $s, $l){
   3046 		return $this->hsla($h, $s, $l, 1.0);
   3047 	}
   3048 
   3049 	public function hsla($h, $s, $l, $a){
   3050 
   3051 		$h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int
   3052 		$s = self::clamp(self::number($s));
   3053 		$l = self::clamp(self::number($l));
   3054 		$a = self::clamp(self::number($a));
   3055 
   3056 		$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
   3057 
   3058 		$m1 = $l * 2 - $m2;
   3059 
   3060 		return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255,
   3061 							self::hsla_hue($h, $m1, $m2) * 255,
   3062 							self::hsla_hue($h - 1/3, $m1, $m2) * 255,
   3063 							$a);
   3064 	}
   3065 
   3066 	/**
   3067 	 * @param double $h
   3068 	 */
   3069 	public function hsla_hue($h, $m1, $m2){
   3070 		$h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h);
   3071 		if	  ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
   3072 		else if ($h * 2 < 1) return $m2;
   3073 		else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
   3074 		else				 return $m1;
   3075 	}
   3076 
   3077 	public function hsv($h, $s, $v) {
   3078 		return $this->hsva($h, $s, $v, 1.0);
   3079 	}
   3080 
   3081 	/**
   3082 	 * @param double $a
   3083 	 */
   3084 	public function hsva($h, $s, $v, $a) {
   3085 		$h = ((Less_Functions::number($h) % 360) / 360 ) * 360;
   3086 		$s = Less_Functions::number($s);
   3087 		$v = Less_Functions::number($v);
   3088 		$a = Less_Functions::number($a);
   3089 
   3090 		$i = floor(($h / 60) % 6);
   3091 		$f = ($h / 60) - $i;
   3092 
   3093 		$vs = array( $v,
   3094 				  $v * (1 - $s),
   3095 				  $v * (1 - $f * $s),
   3096 				  $v * (1 - (1 - $f) * $s));
   3097 
   3098 		$perm = array(array(0, 3, 1),
   3099 					array(2, 0, 1),
   3100 					array(1, 0, 3),
   3101 					array(1, 2, 0),
   3102 					array(3, 1, 0),
   3103 					array(0, 1, 2));
   3104 
   3105 		return $this->rgba($vs[$perm[$i][0]] * 255,
   3106 						 $vs[$perm[$i][1]] * 255,
   3107 						 $vs[$perm[$i][2]] * 255,
   3108 						 $a);
   3109 	}
   3110 
   3111 	public function hue($color = null){
   3112 		if (!$color instanceof Less_Tree_Color) {
   3113 			throw new Less_Exception_Compiler('The first argument to hue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3114 		}
   3115 
   3116 		$c = $color->toHSL();
   3117 		return new Less_Tree_Dimension(Less_Parser::round($c['h']));
   3118 	}
   3119 
   3120 	public function saturation($color = null){
   3121 		if (!$color instanceof Less_Tree_Color) {
   3122 			throw new Less_Exception_Compiler('The first argument to saturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3123 		}
   3124 
   3125 		$c = $color->toHSL();
   3126 		return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%');
   3127 	}
   3128 
   3129 	public function lightness($color = null){
   3130 		if (!$color instanceof Less_Tree_Color) {
   3131 			throw new Less_Exception_Compiler('The first argument to lightness must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3132 		}
   3133 
   3134 		$c = $color->toHSL();
   3135 		return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%');
   3136 	}
   3137 
   3138 	public function hsvhue( $color = null ){
   3139 		if (!$color instanceof Less_Tree_Color) {
   3140 			throw new Less_Exception_Compiler('The first argument to hsvhue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3141 		}
   3142 
   3143 		$hsv = $color->toHSV();
   3144 		return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) );
   3145 	}
   3146 
   3147 
   3148 	public function hsvsaturation( $color = null ){
   3149 		if (!$color instanceof Less_Tree_Color) {
   3150 			throw new Less_Exception_Compiler('The first argument to hsvsaturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3151 		}
   3152 
   3153 		$hsv = $color->toHSV();
   3154 		return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' );
   3155 	}
   3156 
   3157 	public function hsvvalue( $color = null ){
   3158 		if (!$color instanceof Less_Tree_Color) {
   3159 			throw new Less_Exception_Compiler('The first argument to hsvvalue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3160 		}
   3161 
   3162 		$hsv = $color->toHSV();
   3163 		return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' );
   3164 	}
   3165 
   3166 	public function red($color = null) {
   3167 		if (!$color instanceof Less_Tree_Color) {
   3168 			throw new Less_Exception_Compiler('The first argument to red must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3169 		}
   3170 
   3171 		return new Less_Tree_Dimension( $color->rgb[0] );
   3172 	}
   3173 
   3174 	public function green($color = null) {
   3175 		if (!$color instanceof Less_Tree_Color) {
   3176 			throw new Less_Exception_Compiler('The first argument to green must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3177 		}
   3178 
   3179 		return new Less_Tree_Dimension( $color->rgb[1] );
   3180 	}
   3181 
   3182 	public function blue($color = null) {
   3183 		if (!$color instanceof Less_Tree_Color) {
   3184 			throw new Less_Exception_Compiler('The first argument to blue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3185 		}
   3186 
   3187 		return new Less_Tree_Dimension( $color->rgb[2] );
   3188 	}
   3189 
   3190 	public function alpha($color = null){
   3191 		if (!$color instanceof Less_Tree_Color) {
   3192 			throw new Less_Exception_Compiler('The first argument to alpha must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3193 		}
   3194 
   3195 		$c = $color->toHSL();
   3196 		return new Less_Tree_Dimension($c['a']);
   3197 	}
   3198 
   3199 	public function luma ($color = null) {
   3200 		if (!$color instanceof Less_Tree_Color) {
   3201 			throw new Less_Exception_Compiler('The first argument to luma must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3202 		}
   3203 
   3204 		return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%');
   3205 	}
   3206 
   3207 	public function luminance( $color = null ){
   3208 		if (!$color instanceof Less_Tree_Color) {
   3209 			throw new Less_Exception_Compiler('The first argument to luminance must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3210 		}
   3211 
   3212 		$luminance =
   3213 			(0.2126 * $color->rgb[0] / 255)
   3214 		  + (0.7152 * $color->rgb[1] / 255)
   3215 		  + (0.0722 * $color->rgb[2] / 255);
   3216 
   3217 		return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%');
   3218 	}
   3219 
   3220 	public function saturate($color = null, $amount = null){
   3221 		// filter: saturate(3.2);
   3222 		// should be kept as is, so check for color
   3223 		if ($color instanceof Less_Tree_Dimension) {
   3224 			return null;
   3225 		}
   3226 
   3227 		if (!$color instanceof Less_Tree_Color) {
   3228 			throw new Less_Exception_Compiler('The first argument to saturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3229 		}
   3230 		if (!$amount instanceof Less_Tree_Dimension) {
   3231 			throw new Less_Exception_Compiler('The second argument to saturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3232 		}
   3233 
   3234 		$hsl = $color->toHSL();
   3235 
   3236 		$hsl['s'] += $amount->value / 100;
   3237 		$hsl['s'] = self::clamp($hsl['s']);
   3238 
   3239 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3240 	}
   3241 
   3242 	/**
   3243 	 * @param Less_Tree_Dimension $amount
   3244 	 */
   3245 	public function desaturate($color = null, $amount = null){
   3246 		if (!$color instanceof Less_Tree_Color) {
   3247 			throw new Less_Exception_Compiler('The first argument to desaturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3248 		}
   3249 		if (!$amount instanceof Less_Tree_Dimension) {
   3250 			throw new Less_Exception_Compiler('The second argument to desaturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3251 		}
   3252 
   3253 		$hsl = $color->toHSL();
   3254 
   3255 		$hsl['s'] -= $amount->value / 100;
   3256 		$hsl['s'] = self::clamp($hsl['s']);
   3257 
   3258 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3259 	}
   3260 
   3261 
   3262 
   3263 	public function lighten($color = null, $amount=null){
   3264 		if (!$color instanceof Less_Tree_Color) {
   3265 			throw new Less_Exception_Compiler('The first argument to lighten must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3266 		}
   3267 		if (!$amount instanceof Less_Tree_Dimension) {
   3268 			throw new Less_Exception_Compiler('The second argument to lighten must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3269 		}
   3270 
   3271 		$hsl = $color->toHSL();
   3272 
   3273 		$hsl['l'] += $amount->value / 100;
   3274 		$hsl['l'] = self::clamp($hsl['l']);
   3275 
   3276 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3277 	}
   3278 
   3279 	public function darken($color = null, $amount = null){
   3280 		if (!$color instanceof Less_Tree_Color) {
   3281 			throw new Less_Exception_Compiler('The first argument to darken must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3282 		}
   3283 		if (!$amount instanceof Less_Tree_Dimension) {
   3284 			throw new Less_Exception_Compiler('The second argument to darken must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3285 		}
   3286 
   3287 		$hsl = $color->toHSL();
   3288 		$hsl['l'] -= $amount->value / 100;
   3289 		$hsl['l'] = self::clamp($hsl['l']);
   3290 
   3291 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3292 	}
   3293 
   3294 	public function fadein($color = null, $amount = null){
   3295 		if (!$color instanceof Less_Tree_Color) {
   3296 			throw new Less_Exception_Compiler('The first argument to fadein must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3297 		}
   3298 		if (!$amount instanceof Less_Tree_Dimension) {
   3299 			throw new Less_Exception_Compiler('The second argument to fadein must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3300 		}
   3301 
   3302 		$hsl = $color->toHSL();
   3303 		$hsl['a'] += $amount->value / 100;
   3304 		$hsl['a'] = self::clamp($hsl['a']);
   3305 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3306 	}
   3307 
   3308 	public function fadeout($color = null, $amount = null){
   3309 		if (!$color instanceof Less_Tree_Color) {
   3310 			throw new Less_Exception_Compiler('The first argument to fadeout must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3311 		}
   3312 		if (!$amount instanceof Less_Tree_Dimension) {
   3313 			throw new Less_Exception_Compiler('The second argument to fadeout must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3314 		}
   3315 
   3316 		$hsl = $color->toHSL();
   3317 		$hsl['a'] -= $amount->value / 100;
   3318 		$hsl['a'] = self::clamp($hsl['a']);
   3319 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3320 	}
   3321 
   3322 	public function fade($color = null, $amount = null){
   3323 		if (!$color instanceof Less_Tree_Color) {
   3324 			throw new Less_Exception_Compiler('The first argument to fade must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3325 		}
   3326 		if (!$amount instanceof Less_Tree_Dimension) {
   3327 			throw new Less_Exception_Compiler('The second argument to fade must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3328 		}
   3329 
   3330 		$hsl = $color->toHSL();
   3331 
   3332 		$hsl['a'] = $amount->value / 100;
   3333 		$hsl['a'] = self::clamp($hsl['a']);
   3334 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3335 	}
   3336 
   3337 
   3338 
   3339 	public function spin($color = null, $amount = null){
   3340 		if (!$color instanceof Less_Tree_Color) {
   3341 			throw new Less_Exception_Compiler('The first argument to spin must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3342 		}
   3343 		if (!$amount instanceof Less_Tree_Dimension) {
   3344 			throw new Less_Exception_Compiler('The second argument to spin must be a number' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3345 		}
   3346 
   3347 		$hsl = $color->toHSL();
   3348 		$hue = fmod($hsl['h'] + $amount->value, 360);
   3349 
   3350 		$hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
   3351 
   3352 		return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']);
   3353 	}
   3354 
   3355 	//
   3356 	// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
   3357 	// http://sass-lang.com
   3358 	//
   3359 
   3360 	/**
   3361 	 * @param Less_Tree_Color $color1
   3362 	 */
   3363 	public function mix($color1 = null, $color2 = null, $weight = null){
   3364 		if (!$color1 instanceof Less_Tree_Color) {
   3365 			throw new Less_Exception_Compiler('The first argument to mix must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3366 		}
   3367 		if (!$color2 instanceof Less_Tree_Color) {
   3368 			throw new Less_Exception_Compiler('The second argument to mix must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3369 		}
   3370 		if (!$weight) {
   3371 			$weight = new Less_Tree_Dimension('50', '%');
   3372 		}
   3373 		if (!$weight instanceof Less_Tree_Dimension) {
   3374 			throw new Less_Exception_Compiler('The third argument to contrast must be a percentage' . ($weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3375 		}
   3376 
   3377 		$p = $weight->value / 100.0;
   3378 		$w = $p * 2 - 1;
   3379 		$hsl1 = $color1->toHSL();
   3380 		$hsl2 = $color2->toHSL();
   3381 		$a = $hsl1['a'] - $hsl2['a'];
   3382 
   3383 		$w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2;
   3384 		$w2 = 1 - $w1;
   3385 
   3386 		$rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
   3387 					 $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
   3388 					 $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2);
   3389 
   3390 		$alpha = $color1->alpha * $p + $color2->alpha * (1 - $p);
   3391 
   3392 		return new Less_Tree_Color($rgb, $alpha);
   3393 	}
   3394 
   3395 	public function greyscale($color){
   3396 		return $this->desaturate($color, new Less_Tree_Dimension(100,'%'));
   3397 	}
   3398 
   3399 
   3400 	public function contrast( $color, $dark = null, $light = null, $threshold = null){
   3401 		// filter: contrast(3.2);
   3402 		// should be kept as is, so check for color
   3403 		if (!$color instanceof Less_Tree_Color) {
   3404 			return null;
   3405 		}
   3406 		if( !$light ){
   3407 			$light = $this->rgba(255, 255, 255, 1.0);
   3408 		}
   3409 		if( !$dark ){
   3410 			$dark = $this->rgba(0, 0, 0, 1.0);
   3411 		}
   3412 
   3413 		if (!$dark instanceof Less_Tree_Color) {
   3414 			throw new Less_Exception_Compiler('The second argument to contrast must be a color' . ($dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3415 		}
   3416 		if (!$light instanceof Less_Tree_Color) {
   3417 			throw new Less_Exception_Compiler('The third argument to contrast must be a color' . ($light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3418 		}
   3419 
   3420 		//Figure out which is actually light and dark!
   3421 		if( $dark->luma() > $light->luma() ){
   3422 			$t = $light;
   3423 			$light = $dark;
   3424 			$dark = $t;
   3425 		}
   3426 		if( !$threshold ){
   3427 			$threshold = 0.43;
   3428 		} else {
   3429 			$threshold = Less_Functions::number($threshold);
   3430 		}
   3431 
   3432 		if( $color->luma() < $threshold ){
   3433 			return $light;
   3434 		} else {
   3435 			return $dark;
   3436 		}
   3437 	}
   3438 
   3439 	public function e ($str){
   3440 		if( is_string($str) ){
   3441 			return new Less_Tree_Anonymous($str);
   3442 		}
   3443 		return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value);
   3444 	}
   3445 
   3446 	public function escape ($str){
   3447 
   3448 		$revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$');
   3449 
   3450 		return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert));
   3451 	}
   3452 
   3453 
   3454 	/**
   3455 	 * todo: This function will need some additional work to make it work the same as less.js
   3456 	 *
   3457 	 */
   3458 	public function replace( $string, $pattern, $replacement, $flags = null ){
   3459 		$result = $string->value;
   3460 
   3461 		$expr = '/'.str_replace('/','\\/',$pattern->value).'/';
   3462 		if( $flags && $flags->value){
   3463 			$expr .= self::replace_flags($flags->value);
   3464 		}
   3465 
   3466 		$result = preg_replace($expr,$replacement->value,$result);
   3467 
   3468 
   3469 		if( property_exists($string,'quote') ){
   3470 			return new Less_Tree_Quoted( $string->quote, $result, $string->escaped);
   3471 		}
   3472 		return new Less_Tree_Quoted( '', $result );
   3473 	}
   3474 
   3475 	public static function replace_flags($flags){
   3476 		$flags = str_split($flags,1);
   3477 		$new_flags = '';
   3478 
   3479 		foreach($flags as $flag){
   3480 			switch($flag){
   3481 				case 'e':
   3482 				case 'g':
   3483 				break;
   3484 
   3485 				default:
   3486 				$new_flags .= $flag;
   3487 				break;
   3488 			}
   3489 		}
   3490 
   3491 		return $new_flags;
   3492 	}
   3493 
   3494 	public function _percent(){
   3495 		$string = func_get_arg(0);
   3496 
   3497 		$args = func_get_args();
   3498 		array_shift($args);
   3499 		$result = $string->value;
   3500 
   3501 		foreach($args as $arg){
   3502 			if( preg_match('/%[sda]/i',$result, $token) ){
   3503 				$token = $token[0];
   3504 				$value = stristr($token, 's') ? $arg->value : $arg->toCSS();
   3505 				$value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value;
   3506 				$result = preg_replace('/%[sda]/i',$value, $result, 1);
   3507 			}
   3508 		}
   3509 		$result = str_replace('%%', '%', $result);
   3510 
   3511 		return new Less_Tree_Quoted( $string->quote , $result, $string->escaped);
   3512 	}
   3513 
   3514 	public function unit( $val, $unit = null) {
   3515 		if( !($val instanceof Less_Tree_Dimension) ){
   3516 			throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') );
   3517 		}
   3518 
   3519 		if( $unit ){
   3520 			if( $unit instanceof Less_Tree_Keyword ){
   3521 				$unit = $unit->value;
   3522 			} else {
   3523 				$unit = $unit->toCSS();
   3524 			}
   3525 		} else {
   3526 			$unit = "";
   3527 		}
   3528 		return new Less_Tree_Dimension($val->value, $unit );
   3529 	}
   3530 
   3531 	public function convert($val, $unit){
   3532 		return $val->convertTo($unit->value);
   3533 	}
   3534 
   3535 	public function round($n, $f = false) {
   3536 
   3537 		$fraction = 0;
   3538 		if( $f !== false ){
   3539 			$fraction = $f->value;
   3540 		}
   3541 
   3542 		return $this->_math('Less_Parser::round',null, $n, $fraction);
   3543 	}
   3544 
   3545 	public function pi(){
   3546 		return new Less_Tree_Dimension(M_PI);
   3547 	}
   3548 
   3549 	public function mod($a, $b) {
   3550 		return new Less_Tree_Dimension( $a->value % $b->value, $a->unit);
   3551 	}
   3552 
   3553 
   3554 
   3555 	public function pow($x, $y) {
   3556 		if( is_numeric($x) && is_numeric($y) ){
   3557 			$x = new Less_Tree_Dimension($x);
   3558 			$y = new Less_Tree_Dimension($y);
   3559 		}elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){
   3560 			throw new Less_Exception_Compiler('Arguments must be numbers');
   3561 		}
   3562 
   3563 		return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit );
   3564 	}
   3565 
   3566 	// var mathFunctions = [{name:"ce ...
   3567 	public function ceil( $n ){		return $this->_math('ceil', null, $n); }
   3568 	public function floor( $n ){	return $this->_math('floor', null, $n); }
   3569 	public function sqrt( $n ){		return $this->_math('sqrt', null, $n); }
   3570 	public function abs( $n ){		return $this->_math('abs', null, $n); }
   3571 
   3572 	public function tan( $n ){		return $this->_math('tan', '', $n);	}
   3573 	public function sin( $n ){		return $this->_math('sin', '', $n);	}
   3574 	public function cos( $n ){		return $this->_math('cos', '', $n);	}
   3575 
   3576 	public function atan( $n ){		return $this->_math('atan', 'rad', $n);	}
   3577 	public function asin( $n ){		return $this->_math('asin', 'rad', $n);	}
   3578 	public function acos( $n ){		return $this->_math('acos', 'rad', $n);	}
   3579 
   3580 	private function _math() {
   3581 		$args = func_get_args();
   3582 		$fn = array_shift($args);
   3583 		$unit = array_shift($args);
   3584 
   3585 		if ($args[0] instanceof Less_Tree_Dimension) {
   3586 
   3587 			if( $unit === null ){
   3588 				$unit = $args[0]->unit;
   3589 			}else{
   3590 				$args[0] = $args[0]->unify();
   3591 			}
   3592 			$args[0] = (float)$args[0]->value;
   3593 			return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit);
   3594 		} else if (is_numeric($args[0])) {
   3595 			return call_user_func_array($fn,$args);
   3596 		} else {
   3597 			throw new Less_Exception_Compiler("math functions take numbers as parameters");
   3598 		}
   3599 	}
   3600 
   3601 	/**
   3602 	 * @param boolean $isMin
   3603 	 */
   3604 	private function _minmax( $isMin, $args ){
   3605 
   3606 		$arg_count = count($args);
   3607 
   3608 		if( $arg_count < 1 ){
   3609 			throw new Less_Exception_Compiler( 'one or more arguments required');
   3610 		}
   3611 
   3612 		$j = null;
   3613 		$unitClone = null;
   3614 		$unitStatic = null;
   3615 
   3616 
   3617 		$order = array();	// elems only contains original argument values.
   3618 		$values = array();	// key is the unit.toString() for unified tree.Dimension values,
   3619 							// value is the index into the order array.
   3620 
   3621 
   3622 		for( $i = 0; $i < $arg_count; $i++ ){
   3623 			$current = $args[$i];
   3624 			if( !($current instanceof Less_Tree_Dimension) ){
   3625 				if( is_array($args[$i]->value) ){
   3626 					$args[] = $args[$i]->value;
   3627 				}
   3628 				continue;
   3629 			}
   3630 
   3631 			if( $current->unit->toString() === '' && !$unitClone ){
   3632 				$temp = new Less_Tree_Dimension($current->value, $unitClone);
   3633 				$currentUnified = $temp->unify();
   3634 			}else{
   3635 				$currentUnified = $current->unify();
   3636 			}
   3637 
   3638 			if( $currentUnified->unit->toString() === "" && !$unitStatic ){
   3639 				$unit = $unitStatic;
   3640 			}else{
   3641 				$unit = $currentUnified->unit->toString();
   3642 			}
   3643 
   3644 			if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){
   3645 				$unitStatic = $unit;
   3646 			}
   3647 
   3648 			if( $unit != '' && !$unitClone ){
   3649 				$unitClone = $current->unit->toString();
   3650 			}
   3651 
   3652 			if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){
   3653 				$j = $values[''];
   3654 			}elseif( isset($values[$unit]) ){
   3655 				$j = $values[$unit];
   3656 			}else{
   3657 
   3658 				if( $unitStatic && $unit !== $unitStatic ){
   3659 					throw new Less_Exception_Compiler( 'incompatible types');
   3660 				}
   3661 				$values[$unit] = count($order);
   3662 				$order[] = $current;
   3663 				continue;
   3664 			}
   3665 
   3666 
   3667 			if( $order[$j]->unit->toString() === "" && $unitClone ){
   3668 				$temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone);
   3669 				$referenceUnified = $temp->unify();
   3670 			}else{
   3671 				$referenceUnified = $order[$j]->unify();
   3672 			}
   3673 			if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){
   3674 				$order[$j] = $current;
   3675 			}
   3676 		}
   3677 
   3678 		if( count($order) == 1 ){
   3679 			return $order[0];
   3680 		}
   3681 		$args = array();
   3682 		foreach($order as $a){
   3683 			$args[] = $a->toCSS($this->env);
   3684 		}
   3685 		return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')');
   3686 	}
   3687 
   3688 	public function min(){
   3689 		$args = func_get_args();
   3690 		return $this->_minmax( true, $args );
   3691 	}
   3692 
   3693 	public function max(){
   3694 		$args = func_get_args();
   3695 		return $this->_minmax( false, $args );
   3696 	}
   3697 
   3698 	public function getunit($n){
   3699 		return new Less_Tree_Anonymous($n->unit);
   3700 	}
   3701 
   3702 	public function argb($color) {
   3703 		if (!$color instanceof Less_Tree_Color) {
   3704 			throw new Less_Exception_Compiler('The first argument to argb must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   3705 		}
   3706 
   3707 		return new Less_Tree_Anonymous($color->toARGB());
   3708 	}
   3709 
   3710 	public function percentage($n) {
   3711 		return new Less_Tree_Dimension($n->value * 100, '%');
   3712 	}
   3713 
   3714 	public function color($n) {
   3715 
   3716 		if( $n instanceof Less_Tree_Quoted ){
   3717 			$colorCandidate = $n->value;
   3718 			$returnColor = Less_Tree_Color::fromKeyword($colorCandidate);
   3719 			if( $returnColor ){
   3720 				return $returnColor;
   3721 			}
   3722 			if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){
   3723 				return new Less_Tree_Color(substr($colorCandidate, 1));
   3724 			}
   3725 			throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF");
   3726 		} else {
   3727 			throw new Less_Exception_Compiler("argument must be a string");
   3728 		}
   3729 	}
   3730 
   3731 
   3732 	public function iscolor($n) {
   3733 		return $this->_isa($n, 'Less_Tree_Color');
   3734 	}
   3735 
   3736 	public function isnumber($n) {
   3737 		return $this->_isa($n, 'Less_Tree_Dimension');
   3738 	}
   3739 
   3740 	public function isstring($n) {
   3741 		return $this->_isa($n, 'Less_Tree_Quoted');
   3742 	}
   3743 
   3744 	public function iskeyword($n) {
   3745 		return $this->_isa($n, 'Less_Tree_Keyword');
   3746 	}
   3747 
   3748 	public function isurl($n) {
   3749 		return $this->_isa($n, 'Less_Tree_Url');
   3750 	}
   3751 
   3752 	public function ispixel($n) {
   3753 		return $this->isunit($n, 'px');
   3754 	}
   3755 
   3756 	public function ispercentage($n) {
   3757 		return $this->isunit($n, '%');
   3758 	}
   3759 
   3760 	public function isem($n) {
   3761 		return $this->isunit($n, 'em');
   3762 	}
   3763 
   3764 	/**
   3765 	 * @param string $unit
   3766 	 */
   3767 	public function isunit( $n, $unit ){
   3768 		return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
   3769 	}
   3770 
   3771 	/**
   3772 	 * @param string $type
   3773 	 */
   3774 	private function _isa($n, $type) {
   3775 		return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
   3776 	}
   3777 
   3778 	public function tint($color, $amount) {
   3779 		return $this->mix( $this->rgb(255,255,255), $color, $amount);
   3780 	}
   3781 
   3782 	public function shade($color, $amount) {
   3783 		return $this->mix($this->rgb(0, 0, 0), $color, $amount);
   3784 	}
   3785 
   3786 	public function extract($values, $index ){
   3787 		$index = (int)$index->value - 1; // (1-based index)
   3788 		// handle non-array values as an array of length 1
   3789 		// return 'undefined' if index is invalid
   3790 		if( property_exists($values,'value') && is_array($values->value) ){
   3791 			if( isset($values->value[$index]) ){
   3792 				return $values->value[$index];
   3793 			}
   3794 			return null;
   3795 
   3796 		}elseif( (int)$index === 0 ){
   3797 			return $values;
   3798 		}
   3799 
   3800 		return null;
   3801 	}
   3802 
   3803 	public function length($values){
   3804 		$n = (property_exists($values,'value') && is_array($values->value)) ? count($values->value) : 1;
   3805 		return new Less_Tree_Dimension($n);
   3806 	}
   3807 
   3808 	public function datauri($mimetypeNode, $filePathNode = null ) {
   3809 
   3810 		$filePath = ( $filePathNode ? $filePathNode->value : null );
   3811 		$mimetype = $mimetypeNode->value;
   3812 
   3813 		$args = 2;
   3814 		if( !$filePath ){
   3815 			$filePath = $mimetype;
   3816 			$args = 1;
   3817 		}
   3818 
   3819 		$filePath = str_replace('\\','/',$filePath);
   3820 		if( Less_Environment::isPathRelative($filePath) ){
   3821 
   3822 			if( Less_Parser::$options['relativeUrls'] ){
   3823 				$temp = $this->currentFileInfo['currentDirectory'];
   3824 			} else {
   3825 				$temp = $this->currentFileInfo['entryPath'];
   3826 			}
   3827 
   3828 			if( !empty($temp) ){
   3829 				$filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath);
   3830 			}
   3831 
   3832 		}
   3833 
   3834 
   3835 		// detect the mimetype if not given
   3836 		if( $args < 2 ){
   3837 
   3838 			/* incomplete
   3839 			$mime = require('mime');
   3840 			mimetype = mime.lookup(path);
   3841 
   3842 			// use base 64 unless it's an ASCII or UTF-8 format
   3843 			var charset = mime.charsets.lookup(mimetype);
   3844 			useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
   3845 			if (useBase64) mimetype += ';base64';
   3846 			*/
   3847 
   3848 			$mimetype = Less_Mime::lookup($filePath);
   3849 
   3850 			$charset = Less_Mime::charsets_lookup($mimetype);
   3851 			$useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8'));
   3852 			if( $useBase64 ){ $mimetype .= ';base64'; }
   3853 
   3854 		}else{
   3855 			$useBase64 = preg_match('/;base64$/',$mimetype);
   3856 		}
   3857 
   3858 
   3859 		if( file_exists($filePath) ){
   3860 			$buf = @file_get_contents($filePath);
   3861 		}else{
   3862 			$buf = false;
   3863 		}
   3864 
   3865 
   3866 		// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
   3867 		// and the --ieCompat flag is enabled, return a normal url() instead.
   3868 		$DATA_URI_MAX_KB = 32;
   3869 		$fileSizeInKB = round( strlen($buf) / 1024 );
   3870 		if( $fileSizeInKB >= $DATA_URI_MAX_KB ){
   3871 			$url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo);
   3872 			return $url->compile($this);
   3873 		}
   3874 
   3875 		if( $buf ){
   3876 			$buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf);
   3877 			$filePath = '"data:' . $mimetype . ',' . $buf . '"';
   3878 		}
   3879 
   3880 		return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) );
   3881 	}
   3882 
   3883 	//svg-gradient
   3884 	public function svggradient( $direction ){
   3885 
   3886 		$throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
   3887 		$arguments = func_get_args();
   3888 
   3889 		if( count($arguments) < 3 ){
   3890 			throw new Less_Exception_Compiler( $throw_message );
   3891 		}
   3892 
   3893 		$stops = array_slice($arguments,1);
   3894 		$gradientType = 'linear';
   3895 		$rectangleDimension = 'x="0" y="0" width="1" height="1"';
   3896 		$useBase64 = true;
   3897 		$directionValue = $direction->toCSS();
   3898 
   3899 
   3900 		switch( $directionValue ){
   3901 			case "to bottom":
   3902 				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
   3903 				break;
   3904 			case "to right":
   3905 				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
   3906 				break;
   3907 			case "to bottom right":
   3908 				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
   3909 				break;
   3910 			case "to top right":
   3911 				$gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
   3912 				break;
   3913 			case "ellipse":
   3914 			case "ellipse at center":
   3915 				$gradientType = "radial";
   3916 				$gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
   3917 				$rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
   3918 				break;
   3919 			default:
   3920 				throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
   3921 		}
   3922 
   3923 		$returner = '<?xml version="1.0" ?>' .
   3924 			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
   3925 			'<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
   3926 
   3927 		for( $i = 0; $i < count($stops); $i++ ){
   3928 			if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){
   3929 				$color = $stops[$i]->value[0];
   3930 				$position = $stops[$i]->value[1];
   3931 			}else{
   3932 				$color = $stops[$i];
   3933 				$position = null;
   3934 			}
   3935 
   3936 			if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){
   3937 				throw new Less_Exception_Compiler( $throw_message );
   3938 			}
   3939 			if( $position ){
   3940 				$positionValue = $position->toCSS();
   3941 			}elseif( $i === 0 ){
   3942 				$positionValue = '0%';
   3943 			}else{
   3944 				$positionValue = '100%';
   3945 			}
   3946 			$alpha = $color->alpha;
   3947 			$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ($alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '') . '/>';
   3948 		}
   3949 
   3950 		$returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
   3951 
   3952 
   3953 		if( $useBase64 ){
   3954 			$returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'";
   3955 		}else{
   3956 			$returner = "'data:image/svg+xml,".$returner."'";
   3957 		}
   3958 
   3959 		return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
   3960 	}
   3961 
   3962 
   3963 	/**
   3964 	 * Php version of javascript's `encodeURIComponent` function
   3965 	 *
   3966 	 * @param string $string The string to encode
   3967 	 * @return string The encoded string
   3968 	 */
   3969 	public static function encodeURIComponent($string){
   3970 		$revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
   3971 		return strtr(rawurlencode($string), $revert);
   3972 	}
   3973 
   3974 
   3975 	// Color Blending
   3976 	// ref: http://www.w3.org/TR/compositing-1
   3977 
   3978 	public function colorBlend( $mode, $color1, $color2 ){
   3979 		$ab = $color1->alpha;	// backdrop
   3980 		$as = $color2->alpha;	// source
   3981 		$r = array();			// result
   3982 
   3983 		$ar = $as + $ab * (1 - $as);
   3984 		for( $i = 0; $i < 3; $i++ ){
   3985 			$cb = $color1->rgb[$i] / 255;
   3986 			$cs = $color2->rgb[$i] / 255;
   3987 			$cr = call_user_func( $mode, $cb, $cs );
   3988 			if( $ar ){
   3989 				$cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar;
   3990 			}
   3991 			$r[$i] = $cr * 255;
   3992 		}
   3993 
   3994 		return new Less_Tree_Color($r, $ar);
   3995 	}
   3996 
   3997 	public function multiply($color1 = null, $color2 = null ){
   3998 		if (!$color1 instanceof Less_Tree_Color) {
   3999 			throw new Less_Exception_Compiler('The first argument to multiply must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4000 		}
   4001 		if (!$color2 instanceof Less_Tree_Color) {
   4002 			throw new Less_Exception_Compiler('The second argument to multiply must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4003 		}
   4004 
   4005 		return $this->colorBlend( array($this,'colorBlendMultiply'),  $color1, $color2 );
   4006 	}
   4007 
   4008 	private function colorBlendMultiply($cb, $cs){
   4009 		return $cb * $cs;
   4010 	}
   4011 
   4012 	public function screen($color1 = null, $color2 = null ){
   4013 		if (!$color1 instanceof Less_Tree_Color) {
   4014 			throw new Less_Exception_Compiler('The first argument to screen must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4015 		}
   4016 		if (!$color2 instanceof Less_Tree_Color) {
   4017 			throw new Less_Exception_Compiler('The second argument to screen must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4018 		}
   4019 
   4020 		return $this->colorBlend( array($this,'colorBlendScreen'),  $color1, $color2 );
   4021 	}
   4022 
   4023 	private function colorBlendScreen( $cb, $cs){
   4024 		return $cb + $cs - $cb * $cs;
   4025 	}
   4026 
   4027 	public function overlay($color1 = null, $color2 = null){
   4028 		if (!$color1 instanceof Less_Tree_Color) {
   4029 			throw new Less_Exception_Compiler('The first argument to overlay must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4030 		}
   4031 		if (!$color2 instanceof Less_Tree_Color) {
   4032 			throw new Less_Exception_Compiler('The second argument to overlay must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4033 		}
   4034 
   4035 		return $this->colorBlend( array($this,'colorBlendOverlay'),  $color1, $color2 );
   4036 	}
   4037 
   4038 	private function colorBlendOverlay($cb, $cs ){
   4039 		$cb *= 2;
   4040 		return ($cb <= 1)
   4041 			? $this->colorBlendMultiply($cb, $cs)
   4042 			: $this->colorBlendScreen($cb - 1, $cs);
   4043 	}
   4044 
   4045 	public function softlight($color1 = null, $color2 = null){
   4046 		if (!$color1 instanceof Less_Tree_Color) {
   4047 			throw new Less_Exception_Compiler('The first argument to softlight must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4048 		}
   4049 		if (!$color2 instanceof Less_Tree_Color) {
   4050 			throw new Less_Exception_Compiler('The second argument to softlight must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4051 		}
   4052 
   4053 		return $this->colorBlend( array($this,'colorBlendSoftlight'),  $color1, $color2 );
   4054 	}
   4055 
   4056 	private function colorBlendSoftlight($cb, $cs ){
   4057 		$d = 1;
   4058 		$e = $cb;
   4059 		if( $cs > 0.5 ){
   4060 			$e = 1;
   4061 			$d = ($cb > 0.25) ? sqrt($cb)
   4062 				: ((16 * $cb - 12) * $cb + 4) * $cb;
   4063 		}
   4064 		return $cb - (1 - 2 * $cs) * $e * ($d - $cb);
   4065 	}
   4066 
   4067 	public function hardlight($color1 = null, $color2 = null){
   4068 		if (!$color1 instanceof Less_Tree_Color) {
   4069 			throw new Less_Exception_Compiler('The first argument to hardlight must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4070 		}
   4071 		if (!$color2 instanceof Less_Tree_Color) {
   4072 			throw new Less_Exception_Compiler('The second argument to hardlight must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4073 		}
   4074 
   4075 		return $this->colorBlend( array($this,'colorBlendHardlight'),  $color1, $color2 );
   4076 	}
   4077 
   4078 	private function colorBlendHardlight( $cb, $cs ){
   4079 		return $this->colorBlendOverlay($cs, $cb);
   4080 	}
   4081 
   4082 	public function difference($color1 = null, $color2 = null) {
   4083 		if (!$color1 instanceof Less_Tree_Color) {
   4084 			throw new Less_Exception_Compiler('The first argument to difference must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4085 		}
   4086 		if (!$color2 instanceof Less_Tree_Color) {
   4087 			throw new Less_Exception_Compiler('The second argument to difference must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4088 		}
   4089 
   4090 		return $this->colorBlend( array($this,'colorBlendDifference'),  $color1, $color2 );
   4091 	}
   4092 
   4093 	private function colorBlendDifference( $cb, $cs ){
   4094 		return abs($cb - $cs);
   4095 	}
   4096 
   4097 	public function exclusion( $color1 = null, $color2 = null ){
   4098 		if (!$color1 instanceof Less_Tree_Color) {
   4099 			throw new Less_Exception_Compiler('The first argument to exclusion must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4100 		}
   4101 		if (!$color2 instanceof Less_Tree_Color) {
   4102 			throw new Less_Exception_Compiler('The second argument to exclusion must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4103 		}
   4104 
   4105 		return $this->colorBlend( array($this,'colorBlendExclusion'),  $color1, $color2 );
   4106 	}
   4107 
   4108 	private function colorBlendExclusion( $cb, $cs ){
   4109 		return $cb + $cs - 2 * $cb * $cs;
   4110 	}
   4111 
   4112 	public function average($color1 = null, $color2 = null){
   4113 		if (!$color1 instanceof Less_Tree_Color) {
   4114 			throw new Less_Exception_Compiler('The first argument to average must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4115 		}
   4116 		if (!$color2 instanceof Less_Tree_Color) {
   4117 			throw new Less_Exception_Compiler('The second argument to average must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4118 		}
   4119 
   4120 		return $this->colorBlend( array($this,'colorBlendAverage'),  $color1, $color2 );
   4121 	}
   4122 
   4123 	// non-w3c functions:
   4124 	public function colorBlendAverage($cb, $cs ){
   4125 		return ($cb + $cs) / 2;
   4126 	}
   4127 
   4128 	public function negation($color1 = null, $color2 = null ){
   4129 		if (!$color1 instanceof Less_Tree_Color) {
   4130 			throw new Less_Exception_Compiler('The first argument to negation must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4131 		}
   4132 		if (!$color2 instanceof Less_Tree_Color) {
   4133 			throw new Less_Exception_Compiler('The second argument to negation must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') );
   4134 		}
   4135 
   4136 		return $this->colorBlend( array($this,'colorBlendNegation'),  $color1, $color2 );
   4137 	}
   4138 
   4139 	public function colorBlendNegation($cb, $cs){
   4140 		return 1 - abs($cb + $cs - 1);
   4141 	}
   4142 
   4143 	// ~ End of Color Blending
   4144 
   4145 }
   4146  
   4147 
   4148 /**
   4149  * Mime lookup
   4150  *
   4151  * @package Less
   4152  * @subpackage node
   4153  */
   4154 class Less_Mime{
   4155 
   4156 	// this map is intentionally incomplete
   4157 	// if you want more, install 'mime' dep
   4158 	static $_types = array(
   4159 	        '.htm' => 'text/html',
   4160 	        '.html'=> 'text/html',
   4161 	        '.gif' => 'image/gif',
   4162 	        '.jpg' => 'image/jpeg',
   4163 	        '.jpeg'=> 'image/jpeg',
   4164 	        '.png' => 'image/png',
   4165 	        '.ttf' => 'application/x-font-ttf',
   4166 	        '.otf' => 'application/x-font-otf',
   4167 	        '.eot' => 'application/vnd.ms-fontobject',
   4168 	        '.woff' => 'application/x-font-woff',
   4169 	        '.svg' => 'image/svg+xml',
   4170 	        );
   4171 
   4172 	public static function lookup( $filepath ){
   4173 		$parts = explode('.',$filepath);
   4174 		$ext = '.'.strtolower(array_pop($parts));
   4175 
   4176 		if( !isset(self::$_types[$ext]) ){
   4177 			return null;
   4178 		}
   4179 		return self::$_types[$ext];
   4180 	}
   4181 
   4182 	public static function charsets_lookup( $type = null ){
   4183 		// assumes all text types are UTF-8
   4184 		return $type && preg_match('/^text\//',$type) ? 'UTF-8' : '';
   4185 	}
   4186 }
   4187  
   4188 
   4189 /**
   4190  * Tree
   4191  *
   4192  * @package Less
   4193  * @subpackage tree
   4194  */
   4195 class Less_Tree{
   4196 
   4197 	public $cache_string;
   4198 
   4199 	public function toCSS(){
   4200 		$output = new Less_Output();
   4201 		$this->genCSS($output);
   4202 		return $output->toString();
   4203 	}
   4204 
   4205 
   4206     /**
   4207      * Generate CSS by adding it to the output object
   4208      *
   4209      * @param Less_Output $output The output
   4210      * @return void
   4211      */
   4212     public function genCSS($output){}
   4213 
   4214 
   4215 	/**
   4216 	 * @param Less_Tree_Ruleset[] $rules
   4217 	 */
   4218 	public static function outputRuleset( $output, $rules ){
   4219 
   4220 		$ruleCnt = count($rules);
   4221 		Less_Environment::$tabLevel++;
   4222 
   4223 
   4224 		// Compressed
   4225 		if( Less_Parser::$options['compress'] ){
   4226 			$output->add('{');
   4227 			for( $i = 0; $i < $ruleCnt; $i++ ){
   4228 				$rules[$i]->genCSS( $output );
   4229 			}
   4230 
   4231 			$output->add( '}' );
   4232 			Less_Environment::$tabLevel--;
   4233 			return;
   4234 		}
   4235 
   4236 
   4237 		// Non-compressed
   4238 		$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel-1 );
   4239 		$tabRuleStr = $tabSetStr.Less_Parser::$options['indentation'];
   4240 
   4241 		$output->add( " {" );
   4242 		for($i = 0; $i < $ruleCnt; $i++ ){
   4243 			$output->add( $tabRuleStr );
   4244 			$rules[$i]->genCSS( $output );
   4245 		}
   4246 		Less_Environment::$tabLevel--;
   4247 		$output->add( $tabSetStr.'}' );
   4248 
   4249 	}
   4250 
   4251 	public function accept($visitor){}
   4252 
   4253 
   4254 	public static function ReferencedArray($rules){
   4255 		foreach($rules as $rule){
   4256 			if( method_exists($rule, 'markReferenced') ){
   4257 				$rule->markReferenced();
   4258 			}
   4259 		}
   4260 	}
   4261 
   4262 
   4263 	/**
   4264 	 * Requires php 5.3+
   4265 	 */
   4266 	public static function __set_state($args){
   4267 
   4268 		$class = get_called_class();
   4269 		$obj = new $class(null,null,null,null);
   4270 		foreach($args as $key => $val){
   4271 			$obj->$key = $val;
   4272 		}
   4273 		return $obj;
   4274 	}
   4275 
   4276 }
   4277  
   4278 
   4279 /**
   4280  * Parser output
   4281  *
   4282  * @package Less
   4283  * @subpackage output
   4284  */
   4285 class Less_Output{
   4286 
   4287 	/**
   4288 	 * Output holder
   4289 	 *
   4290 	 * @var string
   4291 	 */
   4292 	protected $strs = array();
   4293 
   4294 	/**
   4295 	 * Adds a chunk to the stack
   4296 	 *
   4297 	 * @param string $chunk The chunk to output
   4298 	 * @param Less_FileInfo $fileInfo The file information
   4299 	 * @param integer $index The index
   4300 	 * @param mixed $mapLines
   4301 	 */
   4302 	public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
   4303 		$this->strs[] = $chunk;
   4304 	}
   4305 
   4306 	/**
   4307 	 * Is the output empty?
   4308 	 *
   4309 	 * @return boolean
   4310 	 */
   4311 	public function isEmpty(){
   4312 		return count($this->strs) === 0;
   4313 	}
   4314 
   4315 
   4316 	/**
   4317 	 * Converts the output to string
   4318 	 *
   4319 	 * @return string
   4320 	 */
   4321 	public function toString(){
   4322 		return implode('',$this->strs);
   4323 	}
   4324 
   4325 } 
   4326 
   4327 /**
   4328  * Visitor
   4329  *
   4330  * @package Less
   4331  * @subpackage visitor
   4332  */
   4333 class Less_Visitor{
   4334 
   4335 	protected $methods = array();
   4336 	protected $_visitFnCache = array();
   4337 
   4338 	public function __construct(){
   4339 		$this->_visitFnCache = get_class_methods(get_class($this));
   4340 		$this->_visitFnCache = array_flip($this->_visitFnCache);
   4341 	}
   4342 
   4343 	public function visitObj( $node ){
   4344 
   4345 		$funcName = 'visit'.$node->type;
   4346 		if( isset($this->_visitFnCache[$funcName]) ){
   4347 
   4348 			$visitDeeper = true;
   4349 			$this->$funcName( $node, $visitDeeper );
   4350 
   4351 			if( $visitDeeper ){
   4352 				$node->accept($this);
   4353 			}
   4354 
   4355 			$funcName = $funcName . "Out";
   4356 			if( isset($this->_visitFnCache[$funcName]) ){
   4357 				$this->$funcName( $node );
   4358 			}
   4359 
   4360 		}else{
   4361 			$node->accept($this);
   4362 		}
   4363 
   4364 		return $node;
   4365 	}
   4366 
   4367 	public function visitArray( $nodes ){
   4368 
   4369 		array_map( array($this,'visitObj'), $nodes);
   4370 		return $nodes;
   4371 	}
   4372 }
   4373 
   4374  
   4375 
   4376 /**
   4377  * Replacing Visitor
   4378  *
   4379  * @package Less
   4380  * @subpackage visitor
   4381  */
   4382 class Less_VisitorReplacing extends Less_Visitor{
   4383 
   4384 	public function visitObj( $node ){
   4385 
   4386 		$funcName = 'visit'.$node->type;
   4387 		if( isset($this->_visitFnCache[$funcName]) ){
   4388 
   4389 			$visitDeeper = true;
   4390 			$node = $this->$funcName( $node, $visitDeeper );
   4391 
   4392 			if( $node ){
   4393 				if( $visitDeeper && is_object($node) ){
   4394 					$node->accept($this);
   4395 				}
   4396 
   4397 				$funcName = $funcName . "Out";
   4398 				if( isset($this->_visitFnCache[$funcName]) ){
   4399 					$this->$funcName( $node );
   4400 				}
   4401 			}
   4402 
   4403 		}else{
   4404 			$node->accept($this);
   4405 		}
   4406 
   4407 		return $node;
   4408 	}
   4409 
   4410 	public function visitArray( $nodes ){
   4411 
   4412 		$newNodes = array();
   4413 		foreach($nodes as $node){
   4414 			$evald = $this->visitObj($node);
   4415 			if( $evald ){
   4416 				if( is_array($evald) ){
   4417 					self::flatten($evald,$newNodes);
   4418 				}else{
   4419 					$newNodes[] = $evald;
   4420 				}
   4421 			}
   4422 		}
   4423 		return $newNodes;
   4424 	}
   4425 
   4426 	public function flatten( $arr, &$out ){
   4427 
   4428 		foreach($arr as $item){
   4429 			if( !is_array($item) ){
   4430 				$out[] = $item;
   4431 				continue;
   4432 			}
   4433 
   4434 			foreach($item as $nestedItem){
   4435 				if( is_array($nestedItem) ){
   4436 					self::flatten( $nestedItem, $out);
   4437 				}else{
   4438 					$out[] = $nestedItem;
   4439 				}
   4440 			}
   4441 		}
   4442 
   4443 		return $out;
   4444 	}
   4445 
   4446 }
   4447 
   4448 
   4449  
   4450 
   4451 /**
   4452  * Configurable
   4453  *
   4454  * @package Less
   4455  * @subpackage Core
   4456  */
   4457 abstract class Less_Configurable {
   4458 
   4459 	/**
   4460 	 * Array of options
   4461 	 *
   4462 	 * @var array
   4463 	 */
   4464 	protected $options = array();
   4465 
   4466 	/**
   4467 	 * Array of default options
   4468 	 *
   4469 	 * @var array
   4470 	 */
   4471 	protected $defaultOptions = array();
   4472 
   4473 
   4474 	/**
   4475 	 * Set options
   4476 	 *
   4477 	 * If $options is an object it will be converted into an array by called
   4478 	 * it's toArray method.
   4479 	 *
   4480 	 * @throws Exception
   4481 	 * @param array|object $options
   4482 	 *
   4483 	 */
   4484 	public function setOptions($options){
   4485 		$options = array_intersect_key($options,$this->defaultOptions);
   4486 		$this->options = array_merge($this->defaultOptions, $this->options, $options);
   4487 	}
   4488 
   4489 
   4490 	/**
   4491 	 * Get an option value by name
   4492 	 *
   4493 	 * If the option is empty or not set a NULL value will be returned.
   4494 	 *
   4495 	 * @param string $name
   4496 	 * @param mixed $default Default value if confiuration of $name is not present
   4497 	 * @return mixed
   4498 	 */
   4499 	public function getOption($name, $default = null){
   4500 		if(isset($this->options[$name])){
   4501 			return $this->options[$name];
   4502 		}
   4503 		return $default;
   4504 	}
   4505 
   4506 
   4507 	/**
   4508 	 * Set an option
   4509 	 *
   4510 	 * @param string $name
   4511 	 * @param mixed $value
   4512 	 */
   4513 	public function setOption($name, $value){
   4514 		$this->options[$name] = $value;
   4515 	}
   4516 
   4517 } 
   4518 
   4519 /**
   4520  * Alpha
   4521  *
   4522  * @package Less
   4523  * @subpackage tree
   4524  */
   4525 class Less_Tree_Alpha extends Less_Tree{
   4526 	public $value;
   4527 	public $type = 'Alpha';
   4528 
   4529 	public function __construct($val){
   4530 		$this->value = $val;
   4531 	}
   4532 
   4533 	//function accept( $visitor ){
   4534 	//	$this->value = $visitor->visit( $this->value );
   4535 	//}
   4536 
   4537 	public function compile($env){
   4538 
   4539 		if( is_object($this->value) ){
   4540 			$this->value = $this->value->compile($env);
   4541 		}
   4542 
   4543 		return $this;
   4544 	}
   4545 
   4546     /**
   4547      * @see Less_Tree::genCSS
   4548      */
   4549 	public function genCSS( $output ){
   4550 
   4551 		$output->add( "alpha(opacity=" );
   4552 
   4553 		if( is_string($this->value) ){
   4554 			$output->add( $this->value );
   4555 		}else{
   4556 			$this->value->genCSS( $output);
   4557 		}
   4558 
   4559 		$output->add( ')' );
   4560 	}
   4561 
   4562 	public function toCSS(){
   4563 		return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")";
   4564 	}
   4565 
   4566 
   4567 } 
   4568 
   4569 /**
   4570  * Anonymous
   4571  *
   4572  * @package Less
   4573  * @subpackage tree
   4574  */
   4575 class Less_Tree_Anonymous extends Less_Tree{
   4576 	public $value;
   4577 	public $quote;
   4578 	public $index;
   4579 	public $mapLines;
   4580 	public $currentFileInfo;
   4581 	public $type = 'Anonymous';
   4582 
   4583 	/**
   4584 	 * @param integer $index
   4585 	 * @param boolean $mapLines
   4586 	 */
   4587 	public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){
   4588 		$this->value = $value;
   4589 		$this->index = $index;
   4590 		$this->mapLines = $mapLines;
   4591 		$this->currentFileInfo = $currentFileInfo;
   4592 	}
   4593 
   4594 	public function compile(){
   4595 		return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines);
   4596 	}
   4597 
   4598     public function compare($x){
   4599 		if( !is_object($x) ){
   4600 			return -1;
   4601 		}
   4602 
   4603 		$left = $this->toCSS();
   4604 		$right = $x->toCSS();
   4605 
   4606 		if( $left === $right ){
   4607 			return 0;
   4608 		}
   4609 
   4610 		return $left < $right ? -1 : 1;
   4611 	}
   4612 
   4613     /**
   4614      * @see Less_Tree::genCSS
   4615      */
   4616 	public function genCSS( $output ){
   4617 		$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
   4618 	}
   4619 
   4620 	public function toCSS(){
   4621 		return $this->value;
   4622 	}
   4623 
   4624 }
   4625  
   4626 
   4627 /**
   4628  * Assignment
   4629  *
   4630  * @package Less
   4631  * @subpackage tree
   4632  */
   4633 class Less_Tree_Assignment extends Less_Tree{
   4634 
   4635 	public $key;
   4636 	public $value;
   4637 	public $type = 'Assignment';
   4638 
   4639     public function __construct($key, $val) {
   4640 		$this->key = $key;
   4641 		$this->value = $val;
   4642 	}
   4643 
   4644     public function accept( $visitor ){
   4645 		$this->value = $visitor->visitObj( $this->value );
   4646 	}
   4647 
   4648 	public function compile($env) {
   4649 		return new Less_Tree_Assignment( $this->key, $this->value->compile($env));
   4650 	}
   4651 
   4652     /**
   4653      * @see Less_Tree::genCSS
   4654      */
   4655 	public function genCSS( $output ){
   4656 		$output->add( $this->key . '=' );
   4657 		$this->value->genCSS( $output );
   4658 	}
   4659 
   4660 	public function toCss(){
   4661 		return $this->key . '=' . $this->value->toCSS();
   4662 	}
   4663 }
   4664  
   4665 
   4666 /**
   4667  * Attribute
   4668  *
   4669  * @package Less
   4670  * @subpackage tree
   4671  */
   4672 class Less_Tree_Attribute extends Less_Tree{
   4673 
   4674 	public $key;
   4675 	public $op;
   4676 	public $value;
   4677 	public $type = 'Attribute';
   4678 
   4679     public function __construct($key, $op, $value){
   4680 		$this->key = $key;
   4681 		$this->op = $op;
   4682 		$this->value = $value;
   4683 	}
   4684 
   4685     public function compile($env){
   4686 
   4687 		$key_obj = is_object($this->key);
   4688 		$val_obj = is_object($this->value);
   4689 
   4690 		if( !$key_obj && !$val_obj ){
   4691 			return $this;
   4692 		}
   4693 
   4694 		return new Less_Tree_Attribute(
   4695 			$key_obj ? $this->key->compile($env) : $this->key ,
   4696 			$this->op,
   4697 			$val_obj ? $this->value->compile($env) : $this->value);
   4698 	}
   4699 
   4700     /**
   4701      * @see Less_Tree::genCSS
   4702      */
   4703     public function genCSS( $output ){
   4704 		$output->add( $this->toCSS() );
   4705 	}
   4706 
   4707     public function toCSS(){
   4708 		$value = $this->key;
   4709 
   4710 		if( $this->op ){
   4711 			$value .= $this->op;
   4712 			$value .= (is_object($this->value) ? $this->value->toCSS() : $this->value);
   4713 		}
   4714 
   4715 		return '[' . $value . ']';
   4716 	}
   4717 } 
   4718 
   4719 
   4720 /**
   4721  * Call
   4722  *
   4723  * @package Less
   4724  * @subpackage tree
   4725  */
   4726 class Less_Tree_Call extends Less_Tree{
   4727     public $value;
   4728 
   4729     protected $name;
   4730     protected $args;
   4731     protected $index;
   4732     protected $currentFileInfo;
   4733     public $type = 'Call';
   4734 
   4735 	public function __construct($name, $args, $index, $currentFileInfo = null ){
   4736 		$this->name = $name;
   4737 		$this->args = $args;
   4738 		$this->index = $index;
   4739 		$this->currentFileInfo = $currentFileInfo;
   4740 	}
   4741 
   4742     public function accept( $visitor ){
   4743 		$this->args = $visitor->visitArray( $this->args );
   4744 	}
   4745 
   4746     //
   4747     // When evaluating a function call,
   4748     // we either find the function in `tree.functions` [1],
   4749     // in which case we call it, passing the  evaluated arguments,
   4750     // or we simply print it out as it appeared originally [2].
   4751     //
   4752     // The *functions.js* file contains the built-in functions.
   4753     //
   4754     // The reason why we evaluate the arguments, is in the case where
   4755     // we try to pass a variable to a function, like: `saturate(@color)`.
   4756     // The function should receive the value, not the variable.
   4757     //
   4758     public function compile($env=null){
   4759 		$args = array();
   4760 		foreach($this->args as $a){
   4761 			$args[] = $a->compile($env);
   4762 		}
   4763 
   4764 		$nameLC = strtolower($this->name);
   4765 		switch($nameLC){
   4766 			case '%':
   4767 			$nameLC = '_percent';
   4768 			break;
   4769 
   4770 			case 'get-unit':
   4771 			$nameLC = 'getunit';
   4772 			break;
   4773 
   4774 			case 'data-uri':
   4775 			$nameLC = 'datauri';
   4776 			break;
   4777 
   4778 			case 'svg-gradient':
   4779 			$nameLC = 'svggradient';
   4780 			break;
   4781 		}
   4782 
   4783 		$result = null;
   4784 		if( $nameLC === 'default' ){
   4785 			$result = Less_Tree_DefaultFunc::compile();
   4786 
   4787 		}else{
   4788 
   4789 			if( method_exists('Less_Functions',$nameLC) ){ // 1.
   4790 				try {
   4791 
   4792 					$func = new Less_Functions($env, $this->currentFileInfo);
   4793 					$result = call_user_func_array( array($func,$nameLC),$args);
   4794 
   4795 				} catch (Exception $e) {
   4796 					throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
   4797 				}
   4798 			} elseif( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
   4799 				try {
   4800 					$result = call_user_func_array( $env->functions[$nameLC], $args );
   4801 				} catch (Exception $e) {
   4802 					throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index);
   4803 				}
   4804 			}
   4805 		}
   4806 
   4807 		if( $result !== null ){
   4808 			return $result;
   4809 		}
   4810 
   4811 
   4812 		return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
   4813     }
   4814 
   4815     /**
   4816      * @see Less_Tree::genCSS
   4817      */
   4818 	public function genCSS( $output ){
   4819 
   4820 		$output->add( $this->name . '(', $this->currentFileInfo, $this->index );
   4821 		$args_len = count($this->args);
   4822 		for($i = 0; $i < $args_len; $i++ ){
   4823 			$this->args[$i]->genCSS( $output );
   4824 			if( $i + 1 < $args_len ){
   4825 				$output->add( ', ' );
   4826 			}
   4827 		}
   4828 
   4829 		$output->add( ')' );
   4830 	}
   4831 
   4832 
   4833     //public function toCSS(){
   4834     //    return $this->compile()->toCSS();
   4835     //}
   4836 
   4837 }
   4838  
   4839 
   4840 /**
   4841  * Color
   4842  *
   4843  * @package Less
   4844  * @subpackage tree
   4845  */
   4846 class Less_Tree_Color extends Less_Tree{
   4847 	public $rgb;
   4848 	public $alpha;
   4849 	public $isTransparentKeyword;
   4850 	public $type = 'Color';
   4851 
   4852 	public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){
   4853 
   4854 		if( $isTransparentKeyword ){
   4855 			$this->rgb = $rgb;
   4856 			$this->alpha = $a;
   4857 			$this->isTransparentKeyword = true;
   4858 			return;
   4859 		}
   4860 
   4861 		$this->rgb = array();
   4862 		if( is_array($rgb) ){
   4863 			$this->rgb = $rgb;
   4864 		}else if( strlen($rgb) == 6 ){
   4865 			foreach(str_split($rgb, 2) as $c){
   4866 				$this->rgb[] = hexdec($c);
   4867 			}
   4868 		}else{
   4869 			foreach(str_split($rgb, 1) as $c){
   4870 				$this->rgb[] = hexdec($c.$c);
   4871 			}
   4872 		}
   4873 		$this->alpha = is_numeric($a) ? $a : 1;
   4874 	}
   4875 
   4876 	public function compile(){
   4877 		return $this;
   4878 	}
   4879 
   4880 	public function luma(){
   4881 		$r = $this->rgb[0] / 255;
   4882 		$g = $this->rgb[1] / 255;
   4883 		$b = $this->rgb[2] / 255;
   4884 
   4885 		$r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4);
   4886 		$g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4);
   4887 		$b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4);
   4888 
   4889 		return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
   4890 	}
   4891 
   4892 	/**
   4893 	 * @see Less_Tree::genCSS
   4894 	 */
   4895 	public function genCSS( $output ){
   4896 		$output->add( $this->toCSS() );
   4897 	}
   4898 
   4899 	public function toCSS( $doNotCompress = false ){
   4900 		$compress = Less_Parser::$options['compress'] && !$doNotCompress;
   4901 		$alpha = Less_Functions::fround( $this->alpha );
   4902 
   4903 
   4904 		//
   4905 		// If we have some transparency, the only way to represent it
   4906 		// is via `rgba`. Otherwise, we use the hex representation,
   4907 		// which has better compatibility with older browsers.
   4908 		// Values are capped between `0` and `255`, rounded and zero-padded.
   4909 		//
   4910 		if( $alpha < 1 ){
   4911 			if( ( $alpha === 0 || $alpha === 0.0 ) && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){
   4912 				return 'transparent';
   4913 			}
   4914 
   4915 			$values = array();
   4916 			foreach($this->rgb as $c){
   4917 				$values[] = Less_Functions::clamp( round($c), 255);
   4918 			}
   4919 			$values[] = $alpha;
   4920 
   4921 			$glue = ($compress ? ',' : ', ');
   4922 			return "rgba(" . implode($glue, $values) . ")";
   4923 		}else{
   4924 
   4925 			$color = $this->toRGB();
   4926 
   4927 			if( $compress ){
   4928 
   4929 				// Convert color to short format
   4930 				if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) {
   4931 					$color = '#'.$color[1] . $color[3] . $color[5];
   4932 				}
   4933 			}
   4934 
   4935 			return $color;
   4936 		}
   4937 	}
   4938 
   4939 	//
   4940 	// Operations have to be done per-channel, if not,
   4941 	// channels will spill onto each other. Once we have
   4942 	// our result, in the form of an integer triplet,
   4943 	// we create a new Color node to hold the result.
   4944 	//
   4945 
   4946 	/**
   4947 	 * @param string $op
   4948 	 */
   4949 	public function operate( $op, $other) {
   4950 		$rgb = array();
   4951 		$alpha = $this->alpha * (1 - $other->alpha) + $other->alpha;
   4952 		for ($c = 0; $c < 3; $c++) {
   4953 			$rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]);
   4954 		}
   4955 		return new Less_Tree_Color($rgb, $alpha);
   4956 	}
   4957 
   4958 	public function toRGB(){
   4959 		return $this->toHex($this->rgb);
   4960 	}
   4961 
   4962 	public function toHSL(){
   4963 		$r = $this->rgb[0] / 255;
   4964 		$g = $this->rgb[1] / 255;
   4965 		$b = $this->rgb[2] / 255;
   4966 		$a = $this->alpha;
   4967 
   4968 		$max = max($r, $g, $b);
   4969 		$min = min($r, $g, $b);
   4970 		$l = ($max + $min) / 2;
   4971 		$d = $max - $min;
   4972 
   4973 		$h = $s = 0;
   4974 		if( $max !== $min ){
   4975 			$s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
   4976 
   4977 			switch ($max) {
   4978 				case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
   4979 				case $g: $h = ($b - $r) / $d + 2;				 break;
   4980 				case $b: $h = ($r - $g) / $d + 4;				 break;
   4981 			}
   4982 			$h /= 6;
   4983 		}
   4984 		return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
   4985 	}
   4986 
   4987 	//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
   4988     public function toHSV() {
   4989 		$r = $this->rgb[0] / 255;
   4990 		$g = $this->rgb[1] / 255;
   4991 		$b = $this->rgb[2] / 255;
   4992 		$a = $this->alpha;
   4993 
   4994 		$max = max($r, $g, $b);
   4995 		$min = min($r, $g, $b);
   4996 
   4997 		$v = $max;
   4998 
   4999 		$d = $max - $min;
   5000 		if ($max === 0) {
   5001 			$s = 0;
   5002 		} else {
   5003 			$s = $d / $max;
   5004 		}
   5005 
   5006 		$h = 0;
   5007 		if( $max !== $min ){
   5008 			switch($max){
   5009 				case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break;
   5010 				case $g: $h = ($b - $r) / $d + 2; break;
   5011 				case $b: $h = ($r - $g) / $d + 4; break;
   5012 			}
   5013 			$h /= 6;
   5014 		}
   5015 		return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a );
   5016 	}
   5017 
   5018 	public function toARGB(){
   5019 		$argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb);
   5020 		return $this->toHex( $argb );
   5021 	}
   5022 
   5023 	public function compare($x){
   5024 
   5025 		if( !property_exists( $x, 'rgb' ) ){
   5026 			return -1;
   5027 		}
   5028 
   5029 
   5030 		return ($x->rgb[0] === $this->rgb[0] &&
   5031 			$x->rgb[1] === $this->rgb[1] &&
   5032 			$x->rgb[2] === $this->rgb[2] &&
   5033 			$x->alpha === $this->alpha) ? 0 : -1;
   5034 	}
   5035 
   5036     public function toHex( $v ){
   5037 
   5038 		$ret = '#';
   5039 		foreach($v as $c){
   5040 			$c = Less_Functions::clamp( Less_Parser::round($c), 255);
   5041 			if( $c < 16 ){
   5042 				$ret .= '0';
   5043 			}
   5044 			$ret .= dechex($c);
   5045 		}
   5046 
   5047 		return $ret;
   5048 	}
   5049 
   5050 
   5051 	/**
   5052 	 * @param string $keyword
   5053 	 */
   5054 	public static function fromKeyword( $keyword ){
   5055 		$keyword = strtolower($keyword);
   5056 
   5057 		if( Less_Colors::hasOwnProperty($keyword) ){
   5058 			// detect named color
   5059 			return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1));
   5060 		}
   5061 
   5062 		if( $keyword === 'transparent' ){
   5063 			return new Less_Tree_Color( array(0, 0, 0), 0, true);
   5064 		}
   5065 	}
   5066 
   5067 }
   5068  
   5069 
   5070 /**
   5071  * Comment
   5072  *
   5073  * @package Less
   5074  * @subpackage tree
   5075  */
   5076 class Less_Tree_Comment extends Less_Tree{
   5077 
   5078 	public $value;
   5079 	public $silent;
   5080 	public $isReferenced;
   5081 	public $currentFileInfo;
   5082 	public $type = 'Comment';
   5083 
   5084 	public function __construct($value, $silent, $index = null, $currentFileInfo = null ){
   5085 		$this->value = $value;
   5086 		$this->silent = !! $silent;
   5087 		$this->currentFileInfo = $currentFileInfo;
   5088 	}
   5089 
   5090     /**
   5091      * @see Less_Tree::genCSS
   5092      */
   5093 	public function genCSS( $output ){
   5094 		//if( $this->debugInfo ){
   5095 			//$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
   5096 		//}
   5097 		$output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n
   5098 	}
   5099 
   5100 	public function toCSS(){
   5101 		return Less_Parser::$options['compress'] ? '' : $this->value;
   5102 	}
   5103 
   5104 	public function isSilent(){
   5105 		$isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) );
   5106 		$isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value);
   5107 		return $this->silent || $isReference || $isCompressed;
   5108 	}
   5109 
   5110 	public function compile(){
   5111 		return $this;
   5112 	}
   5113 
   5114 	public function markReferenced(){
   5115 		$this->isReferenced = true;
   5116 	}
   5117 
   5118 }
   5119  
   5120 
   5121 /**
   5122  * Condition
   5123  *
   5124  * @package Less
   5125  * @subpackage tree
   5126  */
   5127 class Less_Tree_Condition extends Less_Tree{
   5128 
   5129 	public $op;
   5130 	public $lvalue;
   5131 	public $rvalue;
   5132 	public $index;
   5133 	public $negate;
   5134 	public $type = 'Condition';
   5135 
   5136 	public function __construct($op, $l, $r, $i = 0, $negate = false) {
   5137 		$this->op = trim($op);
   5138 		$this->lvalue = $l;
   5139 		$this->rvalue = $r;
   5140 		$this->index = $i;
   5141 		$this->negate = $negate;
   5142 	}
   5143 
   5144 	public function accept($visitor){
   5145 		$this->lvalue = $visitor->visitObj( $this->lvalue );
   5146 		$this->rvalue = $visitor->visitObj( $this->rvalue );
   5147 	}
   5148 
   5149     public function compile($env) {
   5150 		$a = $this->lvalue->compile($env);
   5151 		$b = $this->rvalue->compile($env);
   5152 
   5153 		switch( $this->op ){
   5154 			case 'and':
   5155 				$result = $a && $b;
   5156 			break;
   5157 
   5158 			case 'or':
   5159 				$result = $a || $b;
   5160 			break;
   5161 
   5162 			default:
   5163 				if( Less_Parser::is_method($a, 'compare') ){
   5164 					$result = $a->compare($b);
   5165 				}elseif( Less_Parser::is_method($b, 'compare') ){
   5166 					$result = $b->compare($a);
   5167 				}else{
   5168 					throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index);
   5169 				}
   5170 
   5171 				switch ($result) {
   5172 					case -1:
   5173 					$result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
   5174 					break;
   5175 
   5176 					case  0:
   5177 					$result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
   5178 					break;
   5179 
   5180 					case  1:
   5181 					$result = $this->op === '>' || $this->op === '>=';
   5182 					break;
   5183 				}
   5184 			break;
   5185 		}
   5186 
   5187 		return $this->negate ? !$result : $result;
   5188     }
   5189 
   5190 }
   5191  
   5192 
   5193 /**
   5194  * DefaultFunc
   5195  *
   5196  * @package Less
   5197  * @subpackage tree
   5198  */
   5199 class Less_Tree_DefaultFunc{
   5200 
   5201 	static $error_;
   5202 	static $value_;
   5203 
   5204     public static function compile(){
   5205 		if( self::$error_ ){
   5206 			throw new Exception(self::$error_);
   5207 		}
   5208 		if( self::$value_ !== null ){
   5209 			return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false');
   5210 		}
   5211 	}
   5212 
   5213     public static function value( $v ){
   5214 		self::$value_ = $v;
   5215 	}
   5216 
   5217     public static function error( $e ){
   5218 		self::$error_ = $e;
   5219 	}
   5220 
   5221     public static function reset(){
   5222 		self::$value_ = self::$error_ = null;
   5223 	}
   5224 } 
   5225 
   5226 /**
   5227  * DetachedRuleset
   5228  *
   5229  * @package Less
   5230  * @subpackage tree
   5231  */
   5232 class Less_Tree_DetachedRuleset extends Less_Tree{
   5233 
   5234 	public $ruleset;
   5235 	public $frames;
   5236 	public $type = 'DetachedRuleset';
   5237 
   5238     public function __construct( $ruleset, $frames = null ){
   5239 		$this->ruleset = $ruleset;
   5240 		$this->frames = $frames;
   5241 	}
   5242 
   5243     public function accept($visitor) {
   5244 		$this->ruleset = $visitor->visitObj($this->ruleset);
   5245 	}
   5246 
   5247     public function compile($env){
   5248 		if( $this->frames ){
   5249 			$frames = $this->frames;
   5250 		}else{
   5251 			$frames = $env->frames;
   5252 		}
   5253 		return new Less_Tree_DetachedRuleset($this->ruleset, $frames);
   5254 	}
   5255 
   5256     public function callEval($env) {
   5257 		if( $this->frames ){
   5258 			return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) );
   5259 		}
   5260 		return $this->ruleset->compile( $env );
   5261 	}
   5262 }
   5263 
   5264  
   5265 
   5266 /**
   5267  * Dimension
   5268  *
   5269  * @package Less
   5270  * @subpackage tree
   5271  */
   5272 class Less_Tree_Dimension extends Less_Tree{
   5273 
   5274 	public $value;
   5275 	public $unit;
   5276 	public $type = 'Dimension';
   5277 
   5278     public function __construct($value, $unit = null){
   5279         $this->value = floatval($value);
   5280 
   5281 		if( $unit && ($unit instanceof Less_Tree_Unit) ){
   5282 			$this->unit = $unit;
   5283 		}elseif( $unit ){
   5284 			$this->unit = new Less_Tree_Unit( array($unit) );
   5285 		}else{
   5286 			$this->unit = new Less_Tree_Unit( );
   5287 		}
   5288     }
   5289 
   5290     public function accept( $visitor ){
   5291 		$this->unit = $visitor->visitObj( $this->unit );
   5292 	}
   5293 
   5294     public function compile(){
   5295         return $this;
   5296     }
   5297 
   5298     public function toColor() {
   5299         return new Less_Tree_Color(array($this->value, $this->value, $this->value));
   5300     }
   5301 
   5302     /**
   5303      * @see Less_Tree::genCSS
   5304      */
   5305 	public function genCSS( $output ){
   5306 
   5307 		if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){
   5308 			throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString());
   5309 		}
   5310 
   5311 		$value = Less_Functions::fround( $this->value );
   5312 		$strValue = (string)$value;
   5313 
   5314 		if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){
   5315 			// would be output 1e-6 etc.
   5316 			$strValue = number_format($strValue,10);
   5317 			$strValue = preg_replace('/\.?0+$/','', $strValue);
   5318 		}
   5319 
   5320 		if( Less_Parser::$options['compress'] ){
   5321 			// Zero values doesn't need a unit
   5322 			if( $value === 0 && $this->unit->isLength() ){
   5323 				$output->add( $strValue );
   5324 				return $strValue;
   5325 			}
   5326 
   5327 			// Float values doesn't need a leading zero
   5328 			if( $value > 0 && $value < 1 && $strValue[0] === '0' ){
   5329 				$strValue = substr($strValue,1);
   5330 			}
   5331 		}
   5332 
   5333 		$output->add( $strValue );
   5334 		$this->unit->genCSS( $output );
   5335 	}
   5336 
   5337     public function __toString(){
   5338         return $this->toCSS();
   5339     }
   5340 
   5341     // In an operation between two Dimensions,
   5342     // we default to the first Dimension's unit,
   5343     // so `1px + 2em` will yield `3px`.
   5344 
   5345     /**
   5346      * @param string $op
   5347      */
   5348     public function operate( $op, $other){
   5349 
   5350 		$value = Less_Functions::operate( $op, $this->value, $other->value);
   5351 		$unit = clone $this->unit;
   5352 
   5353 		if( $op === '+' || $op === '-' ){
   5354 
   5355 			if( !$unit->numerator && !$unit->denominator ){
   5356 				$unit->numerator = $other->unit->numerator;
   5357 				$unit->denominator = $other->unit->denominator;
   5358 			}elseif( !$other->unit->numerator && !$other->unit->denominator ){
   5359 				// do nothing
   5360 			}else{
   5361 				$other = $other->convertTo( $this->unit->usedUnits());
   5362 
   5363 				if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){
   5364 					throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'.");
   5365 				}
   5366 
   5367 				$value = Less_Functions::operate( $op, $this->value, $other->value);
   5368 			}
   5369 		}elseif( $op === '*' ){
   5370 			$unit->numerator = array_merge($unit->numerator, $other->unit->numerator);
   5371 			$unit->denominator = array_merge($unit->denominator, $other->unit->denominator);
   5372 			sort($unit->numerator);
   5373 			sort($unit->denominator);
   5374 			$unit->cancel();
   5375 		}elseif( $op === '/' ){
   5376 			$unit->numerator = array_merge($unit->numerator, $other->unit->denominator);
   5377 			$unit->denominator = array_merge($unit->denominator, $other->unit->numerator);
   5378 			sort($unit->numerator);
   5379 			sort($unit->denominator);
   5380 			$unit->cancel();
   5381 		}
   5382 		return new Less_Tree_Dimension( $value, $unit);
   5383     }
   5384 
   5385 	public function compare($other) {
   5386 		if ($other instanceof Less_Tree_Dimension) {
   5387 
   5388 			if( $this->unit->isEmpty() || $other->unit->isEmpty() ){
   5389 				$a = $this;
   5390 				$b = $other;
   5391 			} else {
   5392 				$a = $this->unify();
   5393 				$b = $other->unify();
   5394 				if( $a->unit->compare($b->unit) !== 0 ){
   5395 					return -1;
   5396 				}
   5397 			}
   5398 			$aValue = $a->value;
   5399 			$bValue = $b->value;
   5400 
   5401 			if ($bValue > $aValue) {
   5402 				return -1;
   5403 			} elseif ($bValue < $aValue) {
   5404 				return 1;
   5405 			} else {
   5406 				return 0;
   5407 			}
   5408 		} else {
   5409 			return -1;
   5410 		}
   5411 	}
   5412 
   5413     public function unify() {
   5414 		return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' ));
   5415 	}
   5416 
   5417     public function convertTo($conversions) {
   5418 		$value = $this->value;
   5419 		$unit = clone $this->unit;
   5420 
   5421 		if( is_string($conversions) ){
   5422 			$derivedConversions = array();
   5423 			foreach( Less_Tree_UnitConversions::$groups as $i ){
   5424 				if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){
   5425 					$derivedConversions = array( $i => $conversions);
   5426 				}
   5427 			}
   5428 			$conversions = $derivedConversions;
   5429 		}
   5430 
   5431 
   5432 		foreach($conversions as $groupName => $targetUnit){
   5433 			$group = Less_Tree_UnitConversions::${$groupName};
   5434 
   5435 			//numerator
   5436 			foreach($unit->numerator as $i => $atomicUnit){
   5437 				$atomicUnit = $unit->numerator[$i];
   5438 				if( !isset($group[$atomicUnit]) ){
   5439 					continue;
   5440 				}
   5441 
   5442 				$value = $value * ($group[$atomicUnit] / $group[$targetUnit]);
   5443 
   5444 				$unit->numerator[$i] = $targetUnit;
   5445 			}
   5446 
   5447 			//denominator
   5448 			foreach($unit->denominator as $i => $atomicUnit){
   5449 				$atomicUnit = $unit->denominator[$i];
   5450 				if( !isset($group[$atomicUnit]) ){
   5451 					continue;
   5452 				}
   5453 
   5454 				$value = $value / ($group[$atomicUnit] / $group[$targetUnit]);
   5455 
   5456 				$unit->denominator[$i] = $targetUnit;
   5457 			}
   5458 		}
   5459 
   5460 		$unit->cancel();
   5461 
   5462 		return new Less_Tree_Dimension( $value, $unit);
   5463     }
   5464 }
   5465  
   5466 
   5467 /**
   5468  * Directive
   5469  *
   5470  * @package Less
   5471  * @subpackage tree
   5472  */
   5473 class Less_Tree_Directive extends Less_Tree{
   5474 
   5475 	public $name;
   5476 	public $value;
   5477 	public $rules;
   5478 	public $index;
   5479 	public $isReferenced;
   5480 	public $currentFileInfo;
   5481 	public $debugInfo;
   5482 	public $type = 'Directive';
   5483 
   5484 	public function __construct($name, $value = null, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){
   5485 		$this->name = $name;
   5486 		$this->value = $value;
   5487 		if( $rules ){
   5488 			$this->rules = $rules;
   5489 			$this->rules->allowImports = true;
   5490 		}
   5491 
   5492 		$this->index = $index;
   5493 		$this->currentFileInfo = $currentFileInfo;
   5494 		$this->debugInfo = $debugInfo;
   5495 	}
   5496 
   5497 
   5498     public function accept( $visitor ){
   5499 		if( $this->rules ){
   5500 			$this->rules = $visitor->visitObj( $this->rules );
   5501 		}
   5502 		if( $this->value ){
   5503 			$this->value = $visitor->visitObj( $this->value );
   5504 		}
   5505 	}
   5506 
   5507 
   5508     /**
   5509      * @see Less_Tree::genCSS
   5510      */
   5511     public function genCSS( $output ){
   5512 		$value = $this->value;
   5513 		$rules = $this->rules;
   5514 		$output->add( $this->name, $this->currentFileInfo, $this->index );
   5515 		if( $this->value ){
   5516 			$output->add(' ');
   5517 			$this->value->genCSS($output);
   5518 		}
   5519 		if( $this->rules ){
   5520 			Less_Tree::outputRuleset( $output, array($this->rules));
   5521 		} else {
   5522 			$output->add(';');
   5523 		}
   5524 	}
   5525 
   5526 	public function compile($env){
   5527 
   5528 		$value = $this->value;
   5529 		$rules = $this->rules;
   5530 		if( $value ){
   5531 			$value = $value->compile($env);
   5532 		}
   5533 
   5534 		if( $rules ){
   5535 			$rules = $rules->compile($env);
   5536 			$rules->root = true;
   5537 		}
   5538 
   5539 		return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
   5540 	}
   5541 
   5542 
   5543 	public function variable($name){
   5544 		if( $this->rules ){
   5545 			return $this->rules->variable($name);
   5546 		}
   5547 	}
   5548 
   5549 	public function find($selector){
   5550 		if( $this->rules ){
   5551 			return $this->rules->find($selector, $this);
   5552 		}
   5553 	}
   5554 
   5555 	//rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
   5556 
   5557 	public function markReferenced(){
   5558 		$this->isReferenced = true;
   5559 		if( $this->rules ){
   5560 			Less_Tree::ReferencedArray($this->rules->rules);
   5561 		}
   5562 	}
   5563 
   5564 }
   5565  
   5566 
   5567 /**
   5568  * Element
   5569  *
   5570  * @package Less
   5571  * @subpackage tree
   5572  */
   5573 class Less_Tree_Element extends Less_Tree{
   5574 
   5575 	public $combinator = '';
   5576 	public $value = '';
   5577 	public $index;
   5578 	public $currentFileInfo;
   5579 	public $type = 'Element';
   5580 
   5581 	public $value_is_object = false;
   5582 
   5583 	public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){
   5584 
   5585 		$this->value = $value;
   5586 		$this->value_is_object = is_object($value);
   5587 
   5588 		if( $combinator ){
   5589 			$this->combinator = $combinator;
   5590 		}
   5591 
   5592 		$this->index = $index;
   5593 		$this->currentFileInfo = $currentFileInfo;
   5594 	}
   5595 
   5596     public function accept( $visitor ){
   5597 		if( $this->value_is_object ){ //object or string
   5598 			$this->value = $visitor->visitObj( $this->value );
   5599 		}
   5600 	}
   5601 
   5602 	public function compile($env){
   5603 
   5604 		if( Less_Environment::$mixin_stack ){
   5605 			return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo );
   5606 		}
   5607 
   5608 		if( $this->value_is_object ){
   5609 			$this->value = $this->value->compile($env);
   5610 		}
   5611 
   5612 		return $this;
   5613 	}
   5614 
   5615     /**
   5616      * @see Less_Tree::genCSS
   5617      */
   5618 	public function genCSS( $output ){
   5619 		$output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
   5620 	}
   5621 
   5622 	public function toCSS(){
   5623 
   5624 		if( $this->value_is_object ){
   5625 			$value = $this->value->toCSS();
   5626 		}else{
   5627 			$value = $this->value;
   5628 		}
   5629 
   5630 
   5631 		if( $value === '' && $this->combinator && $this->combinator === '&' ){
   5632 			return '';
   5633 		}
   5634 
   5635 
   5636 		return Less_Environment::$_outputMap[$this->combinator] . $value;
   5637 	}
   5638 
   5639 }
   5640  
   5641 
   5642 /**
   5643  * Expression
   5644  *
   5645  * @package Less
   5646  * @subpackage tree
   5647  */
   5648 class Less_Tree_Expression extends Less_Tree{
   5649 
   5650 	public $value = array();
   5651 	public $parens = false;
   5652 	public $parensInOp = false;
   5653 	public $type = 'Expression';
   5654 
   5655 	public function __construct( $value, $parens = null ){
   5656 		$this->value = $value;
   5657 		$this->parens = $parens;
   5658 	}
   5659 
   5660     public function accept( $visitor ){
   5661 		$this->value = $visitor->visitArray( $this->value );
   5662 	}
   5663 
   5664 	public function compile($env) {
   5665 
   5666 		$doubleParen = false;
   5667 
   5668 		if( $this->parens && !$this->parensInOp ){
   5669 			Less_Environment::$parensStack++;
   5670 		}
   5671 
   5672 		$returnValue = null;
   5673 		if( $this->value ){
   5674 
   5675 			$count = count($this->value);
   5676 
   5677 			if( $count > 1 ){
   5678 
   5679 				$ret = array();
   5680 				foreach($this->value as $e){
   5681 					$ret[] = $e->compile($env);
   5682 				}
   5683 				$returnValue = new Less_Tree_Expression($ret);
   5684 
   5685 			}else{
   5686 
   5687 				if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){
   5688 					$doubleParen = true;
   5689 				}
   5690 
   5691 				$returnValue = $this->value[0]->compile($env);
   5692 			}
   5693 
   5694 		} else {
   5695 			$returnValue = $this;
   5696 		}
   5697 
   5698 		if( $this->parens ){
   5699 			if( !$this->parensInOp ){
   5700 				Less_Environment::$parensStack--;
   5701 
   5702 			}elseif( !Less_Environment::isMathOn() && !$doubleParen ){
   5703 				$returnValue = new Less_Tree_Paren($returnValue);
   5704 
   5705 			}
   5706 		}
   5707 		return $returnValue;
   5708 	}
   5709 
   5710     /**
   5711      * @see Less_Tree::genCSS
   5712      */
   5713     public function genCSS( $output ){
   5714 		$val_len = count($this->value);
   5715 		for( $i = 0; $i < $val_len; $i++ ){
   5716 			$this->value[$i]->genCSS( $output );
   5717 			if( $i + 1 < $val_len ){
   5718 				$output->add( ' ' );
   5719 			}
   5720 		}
   5721 	}
   5722 
   5723     public function throwAwayComments() {
   5724 
   5725 		if( is_array($this->value) ){
   5726 			$new_value = array();
   5727 			foreach($this->value as $v){
   5728 				if( $v instanceof Less_Tree_Comment ){
   5729 					continue;
   5730 				}
   5731 				$new_value[] = $v;
   5732 			}
   5733 			$this->value = $new_value;
   5734 		}
   5735 	}
   5736 }
   5737  
   5738 
   5739 /**
   5740  * Extend
   5741  *
   5742  * @package Less
   5743  * @subpackage tree
   5744  */
   5745 class Less_Tree_Extend extends Less_Tree{
   5746 
   5747 	public $selector;
   5748 	public $option;
   5749 	public $index;
   5750 	public $selfSelectors = array();
   5751 	public $allowBefore;
   5752 	public $allowAfter;
   5753 	public $firstExtendOnThisSelectorPath;
   5754 	public $type = 'Extend';
   5755 	public $ruleset;
   5756 
   5757 
   5758 	public $object_id;
   5759 	public $parent_ids = array();
   5760 
   5761 	/**
   5762 	 * @param integer $index
   5763 	 */
   5764     public function __construct($selector, $option, $index){
   5765 		static $i = 0;
   5766 		$this->selector = $selector;
   5767 		$this->option = $option;
   5768 		$this->index = $index;
   5769 
   5770 		switch($option){
   5771 			case "all":
   5772 				$this->allowBefore = true;
   5773 				$this->allowAfter = true;
   5774 			break;
   5775 			default:
   5776 				$this->allowBefore = false;
   5777 				$this->allowAfter = false;
   5778 			break;
   5779 		}
   5780 
   5781 		$this->object_id = $i++;
   5782 		$this->parent_ids = array($this->object_id);
   5783 	}
   5784 
   5785     public function accept( $visitor ){
   5786 		$this->selector = $visitor->visitObj( $this->selector );
   5787 	}
   5788 
   5789     public function compile( $env ){
   5790 		Less_Parser::$has_extends = true;
   5791 		$this->selector = $this->selector->compile($env);
   5792 		return $this;
   5793 		//return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
   5794 	}
   5795 
   5796     public function findSelfSelectors( $selectors ){
   5797 		$selfElements = array();
   5798 
   5799 
   5800 		for( $i = 0, $selectors_len = count($selectors); $i < $selectors_len; $i++ ){
   5801 			$selectorElements = $selectors[$i]->elements;
   5802 			// duplicate the logic in genCSS function inside the selector node.
   5803 			// future TODO - move both logics into the selector joiner visitor
   5804 			if( $i && $selectorElements && $selectorElements[0]->combinator === "") {
   5805 				$selectorElements[0]->combinator = ' ';
   5806 			}
   5807 			$selfElements = array_merge( $selfElements, $selectors[$i]->elements );
   5808 		}
   5809 
   5810 		$this->selfSelectors = array(new Less_Tree_Selector($selfElements));
   5811 	}
   5812 
   5813 } 
   5814 
   5815 /**
   5816  * CSS @import node
   5817  *
   5818  * The general strategy here is that we don't want to wait
   5819  * for the parsing to be completed, before we start importing
   5820  * the file. That's because in the context of a browser,
   5821  * most of the time will be spent waiting for the server to respond.
   5822  *
   5823  * On creation, we push the import path to our import queue, though
   5824  * `import,push`, we also pass it a callback, which it'll call once
   5825  * the file has been fetched, and parsed.
   5826  *
   5827  * @package Less
   5828  * @subpackage tree
   5829  */
   5830 class Less_Tree_Import extends Less_Tree{
   5831 
   5832 	public $options;
   5833 	public $index;
   5834 	public $path;
   5835 	public $features;
   5836 	public $currentFileInfo;
   5837 	public $css;
   5838 	public $skip;
   5839 	public $root;
   5840 	public $type = 'Import';
   5841 
   5842     public function __construct($path, $features, $options, $index, $currentFileInfo = null ){
   5843 		$this->options = $options;
   5844 		$this->index = $index;
   5845 		$this->path = $path;
   5846 		$this->features = $features;
   5847 		$this->currentFileInfo = $currentFileInfo;
   5848 
   5849 		if( is_array($options) ){
   5850 			$this->options += array('inline'=>false);
   5851 
   5852 			if( isset($this->options['less']) || $this->options['inline'] ){
   5853 				$this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline'];
   5854 			} else {
   5855 				$pathValue = $this->getPath();
   5856 				if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){
   5857 					$this->css = true;
   5858 				}
   5859 			}
   5860 		}
   5861 	}
   5862 
   5863 //
   5864 // The actual import node doesn't return anything, when converted to CSS.
   5865 // The reason is that it's used at the evaluation stage, so that the rules
   5866 // it imports can be treated like any other rules.
   5867 //
   5868 // In `eval`, we make sure all Import nodes get evaluated, recursively, so
   5869 // we end up with a flat structure, which can easily be imported in the parent
   5870 // ruleset.
   5871 //
   5872 
   5873     public function accept($visitor){
   5874 
   5875 		if( $this->features ){
   5876 			$this->features = $visitor->visitObj($this->features);
   5877 		}
   5878 		$this->path = $visitor->visitObj($this->path);
   5879 
   5880 		if( !$this->options['inline'] && $this->root ){
   5881 			$this->root = $visitor->visit($this->root);
   5882 		}
   5883 	}
   5884 
   5885     /**
   5886      * @see Less_Tree::genCSS
   5887      */
   5888     public function genCSS( $output ){
   5889 		if( $this->css ){
   5890 
   5891 			$output->add( '@import ', $this->currentFileInfo, $this->index );
   5892 
   5893 			$this->path->genCSS( $output );
   5894 			if( $this->features ){
   5895 				$output->add( ' ' );
   5896 				$this->features->genCSS( $output );
   5897 			}
   5898 			$output->add( ';' );
   5899 		}
   5900 	}
   5901 
   5902     public function toCSS(){
   5903 		$features = $this->features ? ' ' . $this->features->toCSS() : '';
   5904 
   5905 		if ($this->css) {
   5906 			return "@import " . $this->path->toCSS() . $features . ";\n";
   5907 		} else {
   5908 			return "";
   5909 		}
   5910 	}
   5911 
   5912 	/**
   5913 	 * @return string
   5914 	 */
   5915     public function getPath(){
   5916 		if ($this->path instanceof Less_Tree_Quoted) {
   5917 			$path = $this->path->value;
   5918 			$path = ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less';
   5919 		} else if ($this->path instanceof Less_Tree_URL) {
   5920 			$path = $this->path->value->value;
   5921 		}else{
   5922 			return null;
   5923 		}
   5924 
   5925 		//remove query string and fragment
   5926 		return preg_replace('/[\?#][^\?]*$/','',$path);
   5927 	}
   5928 
   5929     public function compileForImport( $env ){
   5930 		return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo);
   5931 	}
   5932 
   5933     public function compilePath($env) {
   5934 		$path = $this->path->compile($env);
   5935 		$rootpath = '';
   5936 		if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){
   5937 			$rootpath = $this->currentFileInfo['rootpath'];
   5938 		}
   5939 
   5940 
   5941 		if( !($path instanceof Less_Tree_URL) ){
   5942 			if( $rootpath ){
   5943 				$pathValue = $path->value;
   5944 				// Add the base path if the import is relative
   5945 				if( $pathValue && Less_Environment::isPathRelative($pathValue) ){
   5946 					$path->value = $this->currentFileInfo['uri_root'].$pathValue;
   5947 				}
   5948 			}
   5949 			$path->value = Less_Environment::normalizePath($path->value);
   5950 		}
   5951 
   5952 
   5953 
   5954 		return $path;
   5955 	}
   5956 
   5957     public function compile( $env ){
   5958 
   5959 		$evald = $this->compileForImport($env);
   5960 
   5961 		//get path & uri
   5962 		$path_and_uri = null;
   5963 		if( is_callable(Less_Parser::$options['import_callback']) ){
   5964 			$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald);
   5965 		}
   5966 
   5967 		if( !$path_and_uri ){
   5968 			$path_and_uri = $evald->PathAndUri();
   5969 		}
   5970 
   5971 		if( $path_and_uri ){
   5972 			list($full_path, $uri) = $path_and_uri;
   5973 		}else{
   5974 			$full_path = $uri = $evald->getPath();
   5975 		}
   5976 
   5977 
   5978 		//import once
   5979 		if( $evald->skip( $full_path, $env) ){
   5980 			return array();
   5981 		}
   5982 
   5983 		if( $this->options['inline'] ){
   5984 			//todo needs to reference css file not import
   5985 			//$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
   5986 
   5987 			Less_Parser::AddParsedFile($full_path);
   5988 			$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
   5989 
   5990 			if( $this->features ){
   5991 				return new Less_Tree_Media( array($contents), $this->features->value );
   5992 			}
   5993 
   5994 			return array( $contents );
   5995 		}
   5996 
   5997 		// optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
   5998 		if( isset($this->options['optional']) && $this->options['optional'] && !file_exists($full_path) && (!$evald->css || !empty($this->currentFileInfo))) {
   5999 			return array();
   6000 		}
   6001 
   6002 
   6003 		// css ?
   6004 		if( $evald->css ){
   6005 			$features = ( $evald->features ? $evald->features->compile($env) : null );
   6006 			return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index);
   6007 		}
   6008 
   6009 
   6010 		return $this->ParseImport( $full_path, $uri, $env );
   6011 	}
   6012 
   6013 
   6014 	/**
   6015 	 * Using the import directories, get the full absolute path and uri of the import
   6016 	 *
   6017 	 * @param Less_Tree_Import $evald
   6018 	 */
   6019     public function PathAndUri(){
   6020 
   6021 		$evald_path = $this->getPath();
   6022 
   6023 		if( $evald_path ){
   6024 
   6025 			$import_dirs = array();
   6026 
   6027 			if( Less_Environment::isPathRelative($evald_path) ){
   6028 				//if the path is relative, the file should be in the current directory
   6029 				$import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
   6030 
   6031 			}else{
   6032 				//otherwise, the file should be relative to the server root
   6033 				$import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
   6034 
   6035 				//if the user supplied entryPath isn't the actual root
   6036 				$import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
   6037 
   6038 			}
   6039 
   6040 			// always look in user supplied import directories
   6041 			$import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
   6042 
   6043 
   6044 			foreach( $import_dirs as $rootpath => $rooturi){
   6045 				if( is_callable($rooturi) ){
   6046 					list($path, $uri) = call_user_func($rooturi, $evald_path);
   6047 					if( is_string($path) ){
   6048 						$full_path = $path;
   6049 						return array( $full_path, $uri );
   6050 					}
   6051 				}elseif( !empty($rootpath) ){
   6052 
   6053 
   6054 					if( $rooturi ){
   6055 						if( strpos($evald_path,$rooturi) === 0 ){
   6056 							$evald_path = substr( $evald_path, strlen($rooturi) );
   6057 						}
   6058 					}
   6059 
   6060 					$path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\');
   6061 
   6062 					if( file_exists($path) ){
   6063 						$full_path = Less_Environment::normalizePath($path);
   6064 						$uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path));
   6065 						return array( $full_path, $uri );
   6066 					} elseif( file_exists($path.'.less') ){
   6067 						$full_path = Less_Environment::normalizePath($path.'.less');
   6068 						$uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path.'.less'));
   6069 						return array( $full_path, $uri );
   6070 					}
   6071 				}
   6072 			}
   6073 		}
   6074 	}
   6075 
   6076 
   6077 	/**
   6078 	 * Parse the import url and return the rules
   6079 	 *
   6080 	 * @return Less_Tree_Media|array
   6081 	 */
   6082     public function ParseImport( $full_path, $uri, $env ){
   6083 
   6084 		$import_env = clone $env;
   6085 		if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){
   6086 			$import_env->currentFileInfo['reference'] = true;
   6087 		}
   6088 
   6089 		if( (isset($this->options['multiple']) && $this->options['multiple']) ){
   6090 			$import_env->importMultiple = true;
   6091 		}
   6092 
   6093 		$parser = new Less_Parser($import_env);
   6094 		$root = $parser->parseFile($full_path, $uri, true);
   6095 
   6096 
   6097 		$ruleset = new Less_Tree_Ruleset(array(), $root->rules );
   6098 		$ruleset->evalImports($import_env);
   6099 
   6100 		return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules;
   6101 	}
   6102 
   6103 
   6104 	/**
   6105 	 * Should the import be skipped?
   6106 	 *
   6107 	 * @return boolean|null
   6108 	 */
   6109 	private function Skip($path, $env){
   6110 
   6111 		$path = Less_Parser::winPath(realpath($path));
   6112 
   6113 		if( $path && Less_Parser::FileParsed($path) ){
   6114 
   6115 			if( isset($this->currentFileInfo['reference']) ){
   6116 				return true;
   6117 			}
   6118 
   6119 			return !isset($this->options['multiple']) && !$env->importMultiple;
   6120 		}
   6121 
   6122 	}
   6123 }
   6124  
   6125 
   6126 /**
   6127  * Javascript
   6128  *
   6129  * @package Less
   6130  * @subpackage tree
   6131  */
   6132 class Less_Tree_Javascript extends Less_Tree{
   6133 
   6134 	public $type = 'Javascript';
   6135 	public $escaped;
   6136 	public $expression;
   6137 	public $index;
   6138 
   6139 	/**
   6140 	 * @param boolean $index
   6141 	 * @param boolean $escaped
   6142 	 */
   6143 	public function __construct($string, $index, $escaped){
   6144 		$this->escaped = $escaped;
   6145 		$this->expression = $string;
   6146 		$this->index = $index;
   6147 	}
   6148 
   6149 	public function compile(){
   6150 		return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */');
   6151 	}
   6152 
   6153 }
   6154  
   6155 
   6156 /**
   6157  * Keyword
   6158  *
   6159  * @package Less
   6160  * @subpackage tree
   6161  */
   6162 class Less_Tree_Keyword extends Less_Tree{
   6163 
   6164 	public $value;
   6165 	public $type = 'Keyword';
   6166 
   6167 	/**
   6168 	 * @param string $value
   6169 	 */
   6170 	public function __construct($value){
   6171 		$this->value = $value;
   6172 	}
   6173 
   6174 	public function compile(){
   6175 		return $this;
   6176 	}
   6177 
   6178     /**
   6179      * @see Less_Tree::genCSS
   6180      */
   6181 	public function genCSS( $output ){
   6182 
   6183 		if( $this->value === '%') {
   6184 			throw new Less_Exception_Compiler("Invalid % without number");
   6185 		}
   6186 
   6187 		$output->add( $this->value );
   6188 	}
   6189 
   6190 	public function compare($other) {
   6191 		if ($other instanceof Less_Tree_Keyword) {
   6192 			return $other->value === $this->value ? 0 : 1;
   6193 		} else {
   6194 			return -1;
   6195 		}
   6196 	}
   6197 }
   6198  
   6199 
   6200 /**
   6201  * Media
   6202  *
   6203  * @package Less
   6204  * @subpackage tree
   6205  */
   6206 class Less_Tree_Media extends Less_Tree{
   6207 
   6208 	public $features;
   6209 	public $rules;
   6210 	public $index;
   6211 	public $currentFileInfo;
   6212 	public $isReferenced;
   6213 	public $type = 'Media';
   6214 
   6215 	public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){
   6216 
   6217 		$this->index = $index;
   6218 		$this->currentFileInfo = $currentFileInfo;
   6219 
   6220 		$selectors = $this->emptySelectors();
   6221 
   6222 		$this->features = new Less_Tree_Value($features);
   6223 
   6224 		$this->rules = array(new Less_Tree_Ruleset($selectors, $value));
   6225 		$this->rules[0]->allowImports = true;
   6226 	}
   6227 
   6228     public function accept( $visitor ){
   6229 		$this->features = $visitor->visitObj($this->features);
   6230 		$this->rules = $visitor->visitArray($this->rules);
   6231 	}
   6232 
   6233     /**
   6234      * @see Less_Tree::genCSS
   6235      */
   6236     public function genCSS( $output ){
   6237 
   6238 		$output->add( '@media ', $this->currentFileInfo, $this->index );
   6239 		$this->features->genCSS( $output );
   6240 		Less_Tree::outputRuleset( $output, $this->rules);
   6241 
   6242 	}
   6243 
   6244 	public function compile($env) {
   6245 
   6246 		$media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo );
   6247 
   6248 		$strictMathBypass = false;
   6249 		if( Less_Parser::$options['strictMath'] === false) {
   6250 			$strictMathBypass = true;
   6251 			Less_Parser::$options['strictMath'] = true;
   6252 		}
   6253 
   6254 		$media->features = $this->features->compile($env);
   6255 
   6256 		if( $strictMathBypass ){
   6257 			Less_Parser::$options['strictMath'] = false;
   6258 		}
   6259 
   6260 		$env->mediaPath[] = $media;
   6261 		$env->mediaBlocks[] = $media;
   6262 
   6263 		array_unshift($env->frames, $this->rules[0]);
   6264 		$media->rules = array($this->rules[0]->compile($env));
   6265 		array_shift($env->frames);
   6266 
   6267 		array_pop($env->mediaPath);
   6268 
   6269 		return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env);
   6270 	}
   6271 
   6272 	public function variable($name) {
   6273 		return $this->rules[0]->variable($name);
   6274 	}
   6275 
   6276 	public function find($selector) {
   6277 		return $this->rules[0]->find($selector, $this);
   6278 	}
   6279 
   6280 	public function emptySelectors(){
   6281 		$el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo );
   6282 		$sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) );
   6283 		$sels[0]->mediaEmpty = true;
   6284         return $sels;
   6285 	}
   6286 
   6287 	public function markReferenced(){
   6288 		$this->rules[0]->markReferenced();
   6289 		$this->isReferenced = true;
   6290 		Less_Tree::ReferencedArray($this->rules[0]->rules);
   6291 	}
   6292 
   6293 	// evaltop
   6294 	public function compileTop($env) {
   6295 		$result = $this;
   6296 
   6297 		if (count($env->mediaBlocks) > 1) {
   6298 			$selectors = $this->emptySelectors();
   6299 			$result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks);
   6300 			$result->multiMedia = true;
   6301 		}
   6302 
   6303 		$env->mediaBlocks = array();
   6304 		$env->mediaPath = array();
   6305 
   6306 		return $result;
   6307 	}
   6308 
   6309 	public function compileNested($env) {
   6310 		$path = array_merge($env->mediaPath, array($this));
   6311 
   6312 		// Extract the media-query conditions separated with `,` (OR).
   6313 		foreach ($path as $key => $p) {
   6314 			$value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
   6315 			$path[$key] = is_array($value) ? $value : array($value);
   6316 		}
   6317 
   6318 		// Trace all permutations to generate the resulting media-query.
   6319 		//
   6320 		// (a, b and c) with nested (d, e) ->
   6321 		//	a and d
   6322 		//	a and e
   6323 		//	b and c and d
   6324 		//	b and c and e
   6325 
   6326 		$permuted = $this->permute($path);
   6327 		$expressions = array();
   6328 		foreach($permuted as $path){
   6329 
   6330 			for( $i=0, $len=count($path); $i < $len; $i++){
   6331 				$path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]);
   6332 			}
   6333 
   6334 			for( $i = count($path) - 1; $i > 0; $i-- ){
   6335 				array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and')));
   6336 			}
   6337 
   6338 			$expressions[] = new Less_Tree_Expression($path);
   6339 		}
   6340 		$this->features = new Less_Tree_Value($expressions);
   6341 
   6342 
   6343 
   6344 		// Fake a tree-node that doesn't output anything.
   6345 		return new Less_Tree_Ruleset(array(), array());
   6346 	}
   6347 
   6348 	public function permute($arr) {
   6349 		if (!$arr)
   6350 			return array();
   6351 
   6352 		if (count($arr) == 1)
   6353 			return $arr[0];
   6354 
   6355 		$result = array();
   6356 		$rest = $this->permute(array_slice($arr, 1));
   6357 		foreach ($rest as $r) {
   6358 			foreach ($arr[0] as $a) {
   6359 				$result[] = array_merge(
   6360 					is_array($a) ? $a : array($a),
   6361 					is_array($r) ? $r : array($r)
   6362 				);
   6363 			}
   6364 		}
   6365 
   6366 		return $result;
   6367 	}
   6368 
   6369     public function bubbleSelectors($selectors) {
   6370 
   6371 		if( !$selectors) return;
   6372 
   6373 		$this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0])));
   6374 	}
   6375 
   6376 }
   6377  
   6378 
   6379 /**
   6380  * A simple css name-value pair
   6381  * ex: width:100px;
   6382  *
   6383  * In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
   6384  * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
   6385  *
   6386  * @package Less
   6387  * @subpackage tree
   6388  */
   6389 class Less_Tree_NameValue extends Less_Tree{
   6390 
   6391 	public $name;
   6392 	public $value;
   6393 	public $index;
   6394 	public $currentFileInfo;
   6395 	public $type = 'NameValue';
   6396 	public $important = '';
   6397 
   6398 	public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){
   6399 		$this->name = $name;
   6400 		$this->value = $value;
   6401 		$this->index = $index;
   6402 		$this->currentFileInfo = $currentFileInfo;
   6403 	}
   6404 
   6405     public function genCSS( $output ){
   6406 
   6407 		$output->add(
   6408 			$this->name
   6409 			. Less_Environment::$_outputMap[': ']
   6410 			. $this->value
   6411 			. $this->important
   6412 			. (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";")
   6413 			, $this->currentFileInfo, $this->index);
   6414 	}
   6415 
   6416 	public function compile ($env){
   6417 		return $this;
   6418 	}
   6419 
   6420 	public function makeImportant(){
   6421 		$new = new Less_Tree_NameValue($this->name, $this->value, $this->index, $this->currentFileInfo);
   6422 		$new->important = ' !important';
   6423 		return $new;
   6424 	}
   6425 
   6426 
   6427 }
   6428  
   6429 
   6430 /**
   6431  * Negative
   6432  *
   6433  * @package Less
   6434  * @subpackage tree
   6435  */
   6436 class Less_Tree_Negative extends Less_Tree{
   6437 
   6438 	public $value;
   6439 	public $type = 'Negative';
   6440 
   6441     public function __construct($node){
   6442 		$this->value = $node;
   6443 	}
   6444 
   6445 	//function accept($visitor) {
   6446 	//	$this->value = $visitor->visit($this->value);
   6447 	//}
   6448 
   6449     /**
   6450      * @see Less_Tree::genCSS
   6451      */
   6452     public function genCSS( $output ){
   6453 		$output->add( '-' );
   6454 		$this->value->genCSS( $output );
   6455 	}
   6456 
   6457     public function compile($env) {
   6458 		if( Less_Environment::isMathOn() ){
   6459 			$ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) );
   6460 			return $ret->compile($env);
   6461 		}
   6462 		return new Less_Tree_Negative( $this->value->compile($env) );
   6463 	}
   6464 } 
   6465 
   6466 /**
   6467  * Operation
   6468  *
   6469  * @package Less
   6470  * @subpackage tree
   6471  */
   6472 class Less_Tree_Operation extends Less_Tree{
   6473 
   6474 	public $op;
   6475 	public $operands;
   6476 	public $isSpaced;
   6477 	public $type = 'Operation';
   6478 
   6479 	/**
   6480 	 * @param string $op
   6481 	 */
   6482 	public function __construct($op, $operands, $isSpaced = false){
   6483 		$this->op = trim($op);
   6484 		$this->operands = $operands;
   6485 		$this->isSpaced = $isSpaced;
   6486 	}
   6487 
   6488     public function accept($visitor) {
   6489 		$this->operands = $visitor->visitArray($this->operands);
   6490 	}
   6491 
   6492 	public function compile($env){
   6493 		$a = $this->operands[0]->compile($env);
   6494 		$b = $this->operands[1]->compile($env);
   6495 
   6496 
   6497 		if( Less_Environment::isMathOn() ){
   6498 
   6499 			if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){
   6500 				$a = $a->toColor();
   6501 
   6502 			}elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){
   6503 				$b = $b->toColor();
   6504 
   6505 			}
   6506 
   6507 			if( !method_exists($a,'operate') ){
   6508 				throw new Less_Exception_Compiler("Operation on an invalid type");
   6509 			}
   6510 
   6511 			return $a->operate( $this->op, $b);
   6512 		}
   6513 
   6514 		return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced );
   6515 	}
   6516 
   6517 
   6518     /**
   6519      * @see Less_Tree::genCSS
   6520      */
   6521     public function genCSS( $output ){
   6522 		$this->operands[0]->genCSS( $output );
   6523 		if( $this->isSpaced ){
   6524 			$output->add( " " );
   6525 		}
   6526 		$output->add( $this->op );
   6527 		if( $this->isSpaced ){
   6528 			$output->add( ' ' );
   6529 		}
   6530 		$this->operands[1]->genCSS( $output );
   6531 	}
   6532 
   6533 }
   6534  
   6535 
   6536 /**
   6537  * Paren
   6538  *
   6539  * @package Less
   6540  * @subpackage tree
   6541  */
   6542 class Less_Tree_Paren extends Less_Tree{
   6543 
   6544 	public $value;
   6545 	public $type = 'Paren';
   6546 
   6547 	public function __construct($value) {
   6548 		$this->value = $value;
   6549 	}
   6550 
   6551     public function accept($visitor){
   6552 		$this->value = $visitor->visitObj($this->value);
   6553 	}
   6554 
   6555     /**
   6556      * @see Less_Tree::genCSS
   6557      */
   6558     public function genCSS( $output ){
   6559 		$output->add( '(' );
   6560 		$this->value->genCSS( $output );
   6561 		$output->add( ')' );
   6562 	}
   6563 
   6564 	public function compile($env) {
   6565 		return new Less_Tree_Paren($this->value->compile($env));
   6566 	}
   6567 
   6568 }
   6569  
   6570 
   6571 /**
   6572  * Quoted
   6573  *
   6574  * @package Less
   6575  * @subpackage tree
   6576  */
   6577 class Less_Tree_Quoted extends Less_Tree{
   6578 	public $escaped;
   6579 	public $value;
   6580 	public $quote;
   6581 	public $index;
   6582 	public $currentFileInfo;
   6583 	public $type = 'Quoted';
   6584 
   6585 	/**
   6586 	 * @param string $str
   6587 	 */
   6588 	public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){
   6589 		$this->escaped = $escaped;
   6590 		$this->value = $content;
   6591 		if( $str ){
   6592 			$this->quote = $str[0];
   6593 		}
   6594 		$this->index = $index;
   6595 		$this->currentFileInfo = $currentFileInfo;
   6596 	}
   6597 
   6598     /**
   6599      * @see Less_Tree::genCSS
   6600      */
   6601     public function genCSS( $output ){
   6602 		if( !$this->escaped ){
   6603 			$output->add( $this->quote, $this->currentFileInfo, $this->index );
   6604         }
   6605         $output->add( $this->value );
   6606         if( !$this->escaped ){
   6607 			$output->add( $this->quote );
   6608         }
   6609     }
   6610 
   6611 	public function compile($env){
   6612 
   6613 		$value = $this->value;
   6614 		if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){
   6615 			foreach($matches as $i => $match){
   6616 				$js = new Less_Tree_JavaScript($matches[1], $this->index, true);
   6617 				$js = $js->compile()->value;
   6618 				$value = str_replace($matches[0][$i], $js, $value);
   6619 			}
   6620 		}
   6621 
   6622 		if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){
   6623 			foreach($matches[1] as $i => $match){
   6624 				$v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo );
   6625 				$v = $v->compile($env);
   6626 				$v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS();
   6627 				$value = str_replace($matches[0][$i], $v, $value);
   6628 			}
   6629 		}
   6630 
   6631 		return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo);
   6632 	}
   6633 
   6634     public function compare($x) {
   6635 
   6636 		if( !Less_Parser::is_method($x, 'toCSS') ){
   6637 			return -1;
   6638 		}
   6639 
   6640 		$left = $this->toCSS();
   6641 		$right = $x->toCSS();
   6642 
   6643 		if ($left === $right) {
   6644 			return 0;
   6645 		}
   6646 
   6647 		return $left < $right ? -1 : 1;
   6648 	}
   6649 }
   6650  
   6651 
   6652 /**
   6653  * Rule
   6654  *
   6655  * @package Less
   6656  * @subpackage tree
   6657  */
   6658 class Less_Tree_Rule extends Less_Tree{
   6659 
   6660 	public $name;
   6661 	public $value;
   6662 	public $important;
   6663 	public $merge;
   6664 	public $index;
   6665 	public $inline;
   6666 	public $variable;
   6667 	public $currentFileInfo;
   6668 	public $type = 'Rule';
   6669 
   6670 	/**
   6671 	 * @param string $important
   6672 	 */
   6673 	public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null,  $inline = false){
   6674 		$this->name = $name;
   6675 		$this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value));
   6676 		$this->important = $important ? ' ' . trim($important) : '';
   6677 		$this->merge = $merge;
   6678 		$this->index = $index;
   6679 		$this->currentFileInfo = $currentFileInfo;
   6680 		$this->inline = $inline;
   6681 		$this->variable = ( is_string($name) && $name[0] === '@');
   6682 	}
   6683 
   6684     public function accept($visitor) {
   6685 		$this->value = $visitor->visitObj( $this->value );
   6686 	}
   6687 
   6688     /**
   6689      * @see Less_Tree::genCSS
   6690      */
   6691     public function genCSS( $output ){
   6692 
   6693 		$output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index);
   6694 		try{
   6695 			$this->value->genCSS( $output);
   6696 
   6697 		}catch( Less_Exception_Parser $e ){
   6698 			$e->index = $this->index;
   6699 			$e->currentFile = $this->currentFileInfo;
   6700 			throw $e;
   6701 		}
   6702 		$output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index);
   6703 	}
   6704 
   6705 	public function compile ($env){
   6706 
   6707 		$name = $this->name;
   6708 		if( is_array($name) ){
   6709 			// expand 'primitive' name directly to get
   6710 			// things faster (~10% for benchmark.less):
   6711 			if( count($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){
   6712 				$name = $name[0]->value;
   6713 			}else{
   6714 				$name = $this->CompileName($env,$name);
   6715 			}
   6716 		}
   6717 
   6718 		$strictMathBypass = Less_Parser::$options['strictMath'];
   6719 		if( $name === "font" && !Less_Parser::$options['strictMath'] ){
   6720 			Less_Parser::$options['strictMath'] = true;
   6721 		}
   6722 
   6723 		try {
   6724 			$evaldValue = $this->value->compile($env);
   6725 
   6726 			if( !$this->variable && $evaldValue->type === "DetachedRuleset") {
   6727 				throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo);
   6728 			}
   6729 
   6730 			if( Less_Environment::$mixin_stack ){
   6731 				$return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline);
   6732 			}else{
   6733 				$this->name = $name;
   6734 				$this->value = $evaldValue;
   6735 				$return = $this;
   6736 			}
   6737 
   6738 		}catch( Less_Exception_Parser $e ){
   6739 			if( !is_numeric($e->index) ){
   6740 				$e->index = $this->index;
   6741 				$e->currentFile = $this->currentFileInfo;
   6742 			}
   6743 			throw $e;
   6744 		}
   6745 
   6746 		Less_Parser::$options['strictMath'] = $strictMathBypass;
   6747 
   6748 		return $return;
   6749 	}
   6750 
   6751 
   6752     public function CompileName( $env, $name ){
   6753 		$output = new Less_Output();
   6754 		foreach($name as $n){
   6755 			$n->compile($env)->genCSS($output);
   6756 		}
   6757 		return $output->toString();
   6758 	}
   6759 
   6760     public function makeImportant(){
   6761 		return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline);
   6762 	}
   6763 
   6764 }
   6765  
   6766 
   6767 /**
   6768  * Ruleset
   6769  *
   6770  * @package Less
   6771  * @subpackage tree
   6772  */
   6773 class Less_Tree_Ruleset extends Less_Tree{
   6774 
   6775 	protected $lookups;
   6776 	public $_variables;
   6777 	public $_rulesets;
   6778 
   6779 	public $strictImports;
   6780 
   6781 	public $selectors;
   6782 	public $rules;
   6783 	public $root;
   6784 	public $allowImports;
   6785 	public $paths;
   6786 	public $firstRoot;
   6787 	public $type = 'Ruleset';
   6788 	public $multiMedia;
   6789 	public $allExtends;
   6790 
   6791 	public $ruleset_id;
   6792 	public $originalRuleset;
   6793 
   6794 	public $first_oelements;
   6795 
   6796 	public function SetRulesetIndex(){
   6797 		$this->ruleset_id = Less_Parser::$next_id++;
   6798 		$this->originalRuleset = $this->ruleset_id;
   6799 
   6800 		if( $this->selectors ){
   6801 			foreach($this->selectors as $sel){
   6802 				if( $sel->_oelements ){
   6803 					$this->first_oelements[$sel->_oelements[0]] = true;
   6804 				}
   6805 			}
   6806 		}
   6807 	}
   6808 
   6809 	public function __construct($selectors, $rules, $strictImports = null){
   6810 		$this->selectors = $selectors;
   6811 		$this->rules = $rules;
   6812 		$this->lookups = array();
   6813 		$this->strictImports = $strictImports;
   6814 		$this->SetRulesetIndex();
   6815 	}
   6816 
   6817 	public function accept( $visitor ){
   6818 		if( $this->paths ){
   6819 			$paths_len = count($this->paths);
   6820 			for($i = 0,$paths_len; $i < $paths_len; $i++ ){
   6821 				$this->paths[$i] = $visitor->visitArray($this->paths[$i]);
   6822 			}
   6823 		}elseif( $this->selectors ){
   6824 			$this->selectors = $visitor->visitArray($this->selectors);
   6825 		}
   6826 
   6827 		if( $this->rules ){
   6828 			$this->rules = $visitor->visitArray($this->rules);
   6829 		}
   6830 	}
   6831 
   6832 	public function compile($env){
   6833 
   6834 		$ruleset = $this->PrepareRuleset($env);
   6835 
   6836 
   6837 		// Store the frames around mixin definitions,
   6838 		// so they can be evaluated like closures when the time comes.
   6839 		$rsRuleCnt = count($ruleset->rules);
   6840 		for( $i = 0; $i < $rsRuleCnt; $i++ ){
   6841 			if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
   6842 				$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
   6843 			}
   6844 		}
   6845 
   6846 		$mediaBlockCount = 0;
   6847 		if( $env instanceof Less_Environment ){
   6848 			$mediaBlockCount = count($env->mediaBlocks);
   6849 		}
   6850 
   6851 		// Evaluate mixin calls.
   6852 		$this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
   6853 
   6854 
   6855 		// Evaluate everything else
   6856 		for( $i=0; $i<$rsRuleCnt; $i++ ){
   6857 			if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
   6858 				$ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
   6859 			}
   6860 		}
   6861 
   6862         // Evaluate everything else
   6863 		for( $i=0; $i<$rsRuleCnt; $i++ ){
   6864 			$rule = $ruleset->rules[$i];
   6865 
   6866             // for rulesets, check if it is a css guard and can be removed
   6867 			if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
   6868 
   6869                 // check if it can be folded in (e.g. & where)
   6870 				if( $rule->selectors[0]->isJustParentSelector() ){
   6871 					array_splice($ruleset->rules,$i--,1);
   6872 					$rsRuleCnt--;
   6873 
   6874 					for($j = 0; $j < count($rule->rules); $j++ ){
   6875 						$subRule = $rule->rules[$j];
   6876 						if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
   6877 							array_splice($ruleset->rules, ++$i, 0, array($subRule));
   6878 							$rsRuleCnt++;
   6879 						}
   6880 					}
   6881 
   6882                 }
   6883             }
   6884         }
   6885 
   6886 
   6887 		// Pop the stack
   6888 		$env->shiftFrame();
   6889 
   6890 		if ($mediaBlockCount) {
   6891 			$len = count($env->mediaBlocks);
   6892 			for($i = $mediaBlockCount; $i < $len; $i++ ){
   6893 				$env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
   6894 			}
   6895 		}
   6896 
   6897 		return $ruleset;
   6898 	}
   6899 
   6900 	/**
   6901 	 * Compile Less_Tree_Mixin_Call objects
   6902 	 *
   6903 	 * @param Less_Tree_Ruleset $ruleset
   6904 	 * @param integer $rsRuleCnt
   6905 	 */
   6906 	private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
   6907 		for($i=0; $i < $rsRuleCnt; $i++){
   6908 			$rule = $ruleset->rules[$i];
   6909 
   6910 			if( $rule instanceof Less_Tree_Mixin_Call ){
   6911 				$rule = $rule->compile($env);
   6912 
   6913 				$temp = array();
   6914 				foreach($rule as $r){
   6915 					if( ($r instanceof Less_Tree_Rule) && $r->variable ){
   6916 						// do not pollute the scope if the variable is
   6917 						// already there. consider returning false here
   6918 						// but we need a way to "return" variable from mixins
   6919 						if( !$ruleset->variable($r->name) ){
   6920 							$temp[] = $r;
   6921 						}
   6922 					}else{
   6923 						$temp[] = $r;
   6924 					}
   6925 				}
   6926 				$temp_count = count($temp)-1;
   6927 				array_splice($ruleset->rules, $i, 1, $temp);
   6928 				$rsRuleCnt += $temp_count;
   6929 				$i += $temp_count;
   6930 				$ruleset->resetCache();
   6931 
   6932 			}elseif( $rule instanceof Less_Tree_RulesetCall ){
   6933 
   6934 				$rule = $rule->compile($env);
   6935 				$rules = array();
   6936 				foreach($rule->rules as $r){
   6937 					if( ($r instanceof Less_Tree_Rule) && $r->variable ){
   6938 						continue;
   6939 					}
   6940 					$rules[] = $r;
   6941 				}
   6942 
   6943 				array_splice($ruleset->rules, $i, 1, $rules);
   6944 				$temp_count = count($rules);
   6945 				$rsRuleCnt += $temp_count - 1;
   6946 				$i += $temp_count-1;
   6947 				$ruleset->resetCache();
   6948 			}
   6949 
   6950 		}
   6951 	}
   6952 
   6953 
   6954 	/**
   6955 	 * Compile the selectors and create a new ruleset object for the compile() method
   6956 	 *
   6957 	 */
   6958 	private function PrepareRuleset($env){
   6959 
   6960 		$hasOnePassingSelector = false;
   6961 		$selectors = array();
   6962 		if( $this->selectors ){
   6963 			Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
   6964 
   6965 			foreach($this->selectors as $s){
   6966 				$selector = $s->compile($env);
   6967 				$selectors[] = $selector;
   6968 				if( $selector->evaldCondition ){
   6969 					$hasOnePassingSelector = true;
   6970 				}
   6971 			}
   6972 
   6973 			Less_Tree_DefaultFunc::reset();
   6974 		} else {
   6975 			$hasOnePassingSelector = true;
   6976 		}
   6977 
   6978 		if( $this->rules && $hasOnePassingSelector ){
   6979 			$rules = $this->rules;
   6980 		}else{
   6981 			$rules = array();
   6982 		}
   6983 
   6984 		$ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
   6985 
   6986 		$ruleset->originalRuleset = $this->ruleset_id;
   6987 
   6988 		$ruleset->root = $this->root;
   6989 		$ruleset->firstRoot = $this->firstRoot;
   6990 		$ruleset->allowImports = $this->allowImports;
   6991 
   6992 
   6993 		// push the current ruleset to the frames stack
   6994 		$env->unshiftFrame($ruleset);
   6995 
   6996 
   6997 		// Evaluate imports
   6998 		if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
   6999 			$ruleset->evalImports($env);
   7000 		}
   7001 
   7002 		return $ruleset;
   7003 	}
   7004 
   7005 	function evalImports($env) {
   7006 
   7007 		$rules_len = count($this->rules);
   7008 		for($i=0; $i < $rules_len; $i++){
   7009 			$rule = $this->rules[$i];
   7010 
   7011 			if( $rule instanceof Less_Tree_Import ){
   7012 				$rules = $rule->compile($env);
   7013 				if( is_array($rules) ){
   7014 					array_splice($this->rules, $i, 1, $rules);
   7015 					$temp_count = count($rules)-1;
   7016 					$i += $temp_count;
   7017 					$rules_len += $temp_count;
   7018 				}else{
   7019 					array_splice($this->rules, $i, 1, array($rules));
   7020 				}
   7021 
   7022 				$this->resetCache();
   7023 			}
   7024 		}
   7025 	}
   7026 
   7027 	function makeImportant(){
   7028 
   7029 		$important_rules = array();
   7030 		foreach($this->rules as $rule){
   7031 			if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ){
   7032 				$important_rules[] = $rule->makeImportant();
   7033 			}else{
   7034 				$important_rules[] = $rule;
   7035 			}
   7036 		}
   7037 
   7038 		return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
   7039 	}
   7040 
   7041 	public function matchArgs($args){
   7042 		return !$args;
   7043 	}
   7044 
   7045 	// lets you call a css selector with a guard
   7046 	public function matchCondition( $args, $env ){
   7047 		$lastSelector = end($this->selectors);
   7048 
   7049 		if( !$lastSelector->evaldCondition ){
   7050 			return false;
   7051 		}
   7052 		if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
   7053 			return false;
   7054 		}
   7055 		return true;
   7056 	}
   7057 
   7058 	function resetCache(){
   7059 		$this->_rulesets = null;
   7060 		$this->_variables = null;
   7061 		$this->lookups = array();
   7062 	}
   7063 
   7064 	public function variables(){
   7065 		$this->_variables = array();
   7066 		foreach( $this->rules as $r){
   7067 			if ($r instanceof Less_Tree_Rule && $r->variable === true) {
   7068 				$this->_variables[$r->name] = $r;
   7069 			}
   7070 		}
   7071 	}
   7072 
   7073 	public function variable($name){
   7074 
   7075 		if( is_null($this->_variables) ){
   7076 			$this->variables();
   7077 		}
   7078 		return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
   7079 	}
   7080 
   7081 	public function find( $selector, $self = null ){
   7082 
   7083 		$key = implode(' ',$selector->_oelements);
   7084 
   7085 		if( !isset($this->lookups[$key]) ){
   7086 
   7087 			if( !$self ){
   7088 				$self = $this->ruleset_id;
   7089 			}
   7090 
   7091 			$this->lookups[$key] = array();
   7092 
   7093 			$first_oelement = $selector->_oelements[0];
   7094 
   7095 			foreach($this->rules as $rule){
   7096 				if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
   7097 
   7098 					if( isset($rule->first_oelements[$first_oelement]) ){
   7099 
   7100 						foreach( $rule->selectors as $ruleSelector ){
   7101 							$match = $selector->match($ruleSelector);
   7102 							if( $match ){
   7103 								if( $selector->elements_len > $match ){
   7104 									$this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
   7105 								} else {
   7106 									$this->lookups[$key][] = $rule;
   7107 								}
   7108 								break;
   7109 							}
   7110 						}
   7111 					}
   7112 				}
   7113 			}
   7114 		}
   7115 
   7116 		return $this->lookups[$key];
   7117 	}
   7118 
   7119 
   7120 	/**
   7121 	 * @see Less_Tree::genCSS
   7122 	 */
   7123 	public function genCSS( $output ){
   7124 
   7125 		if( !$this->root ){
   7126 			Less_Environment::$tabLevel++;
   7127 		}
   7128 
   7129 		$tabRuleStr = $tabSetStr = '';
   7130 		if( !Less_Parser::$options['compress'] ){
   7131 			if( Less_Environment::$tabLevel ){
   7132 				$tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel );
   7133 				$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel-1 );
   7134 			}else{
   7135 				$tabSetStr = $tabRuleStr = "\n";
   7136 			}
   7137 		}
   7138 
   7139 
   7140 		$ruleNodes = array();
   7141 		$rulesetNodes = array();
   7142 		foreach($this->rules as $rule){
   7143 
   7144 			$class = get_class($rule);
   7145 			if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
   7146 				$rulesetNodes[] = $rule;
   7147 			}else{
   7148 				$ruleNodes[] = $rule;
   7149 			}
   7150 		}
   7151 
   7152 		// If this is the root node, we don't render
   7153 		// a selector, or {}.
   7154 		if( !$this->root ){
   7155 
   7156 			/*
   7157 			debugInfo = tree.debugInfo(env, this, tabSetStr);
   7158 
   7159 			if (debugInfo) {
   7160 				output.add(debugInfo);
   7161 				output.add(tabSetStr);
   7162 			}
   7163 			*/
   7164 
   7165 			$paths_len = count($this->paths);
   7166 			for( $i = 0; $i < $paths_len; $i++ ){
   7167 				$path = $this->paths[$i];
   7168 				$firstSelector = true;
   7169 
   7170 				foreach($path as $p){
   7171 					$p->genCSS( $output, $firstSelector );
   7172 					$firstSelector = false;
   7173 				}
   7174 
   7175 				if( $i + 1 < $paths_len ){
   7176 					$output->add( ',' . $tabSetStr );
   7177 				}
   7178 			}
   7179 
   7180 			$output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
   7181 		}
   7182 
   7183 		// Compile rules and rulesets
   7184 		$ruleNodes_len = count($ruleNodes);
   7185 		$rulesetNodes_len = count($rulesetNodes);
   7186 		for( $i = 0; $i < $ruleNodes_len; $i++ ){
   7187 			$rule = $ruleNodes[$i];
   7188 
   7189 			// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
   7190 			// In this instance we do not know whether it is the last property
   7191 			if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
   7192 				Less_Environment::$lastRule = true;
   7193 			}
   7194 
   7195 			$rule->genCSS( $output );
   7196 
   7197 			if( !Less_Environment::$lastRule ){
   7198 				$output->add( $tabRuleStr );
   7199 			}else{
   7200 				Less_Environment::$lastRule = false;
   7201 			}
   7202 		}
   7203 
   7204 		if( !$this->root ){
   7205 			$output->add( $tabSetStr . '}' );
   7206 			Less_Environment::$tabLevel--;
   7207 		}
   7208 
   7209 		$firstRuleset = true;
   7210 		$space = ($this->root ? $tabRuleStr : $tabSetStr);
   7211 		for( $i = 0; $i < $rulesetNodes_len; $i++ ){
   7212 
   7213 			if( $ruleNodes_len && $firstRuleset ){
   7214 				$output->add( $space );
   7215 			}elseif( !$firstRuleset ){
   7216 				$output->add( $space );
   7217 			}
   7218 			$firstRuleset = false;
   7219 			$rulesetNodes[$i]->genCSS( $output);
   7220 		}
   7221 
   7222 		if( !Less_Parser::$options['compress'] && $this->firstRoot ){
   7223 			$output->add( "\n" );
   7224 		}
   7225 
   7226 	}
   7227 
   7228 
   7229 	function markReferenced(){
   7230 		if( !$this->selectors ){
   7231 			return;
   7232 		}
   7233 		foreach($this->selectors as $selector){
   7234 			$selector->markReferenced();
   7235 		}
   7236 	}
   7237 
   7238 	public function joinSelectors( $context, $selectors ){
   7239 		$paths = array();
   7240 		if( is_array($selectors) ){
   7241 			foreach($selectors as $selector) {
   7242 				$this->joinSelector( $paths, $context, $selector);
   7243 			}
   7244 		}
   7245 		return $paths;
   7246 	}
   7247 
   7248 	public function joinSelector( &$paths, $context, $selector){
   7249 
   7250 		$hasParentSelector = false;
   7251 
   7252 		foreach($selector->elements as $el) {
   7253 			if( $el->value === '&') {
   7254 				$hasParentSelector = true;
   7255 			}
   7256 		}
   7257 
   7258 		if( !$hasParentSelector ){
   7259 			if( $context ){
   7260 				foreach($context as $context_el){
   7261 					$paths[] = array_merge($context_el, array($selector) );
   7262 				}
   7263 			}else {
   7264 				$paths[] = array($selector);
   7265 			}
   7266 			return;
   7267 		}
   7268 
   7269 
   7270 		// The paths are [[Selector]]
   7271 		// The first list is a list of comma seperated selectors
   7272 		// The inner list is a list of inheritance seperated selectors
   7273 		// e.g.
   7274 		// .a, .b {
   7275 		//   .c {
   7276 		//   }
   7277 		// }
   7278 		// == [[.a] [.c]] [[.b] [.c]]
   7279 		//
   7280 
   7281 		// the elements from the current selector so far
   7282 		$currentElements = array();
   7283 		// the current list of new selectors to add to the path.
   7284 		// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
   7285 		// by the parents
   7286 		$newSelectors = array(array());
   7287 
   7288 
   7289 		foreach( $selector->elements as $el){
   7290 
   7291 			// non parent reference elements just get added
   7292 			if( $el->value !== '&' ){
   7293 				$currentElements[] = $el;
   7294 			} else {
   7295 				// the new list of selectors to add
   7296 				$selectorsMultiplied = array();
   7297 
   7298 				// merge the current list of non parent selector elements
   7299 				// on to the current list of selectors to add
   7300 				if( $currentElements ){
   7301 					$this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
   7302 				}
   7303 
   7304 				// loop through our current selectors
   7305 				foreach($newSelectors as $sel){
   7306 
   7307 					// if we don't have any parent paths, the & might be in a mixin so that it can be used
   7308 					// whether there are parents or not
   7309 					if( !$context ){
   7310 						// the combinator used on el should now be applied to the next element instead so that
   7311 						// it is not lost
   7312 						if( $sel ){
   7313 							$sel[0]->elements = array_slice($sel[0]->elements,0);
   7314 							$sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
   7315 						}
   7316 						$selectorsMultiplied[] = $sel;
   7317 					}else {
   7318 
   7319 						// and the parent selectors
   7320 						foreach($context as $parentSel){
   7321 							// We need to put the current selectors
   7322 							// then join the last selector's elements on to the parents selectors
   7323 
   7324 							// our new selector path
   7325 							$newSelectorPath = array();
   7326 							// selectors from the parent after the join
   7327 							$afterParentJoin = array();
   7328 							$newJoinedSelectorEmpty = true;
   7329 
   7330 							//construct the joined selector - if & is the first thing this will be empty,
   7331 							// if not newJoinedSelector will be the last set of elements in the selector
   7332 							if( $sel ){
   7333 								$newSelectorPath = $sel;
   7334 								$lastSelector = array_pop($newSelectorPath);
   7335 								$newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
   7336 								$newJoinedSelectorEmpty = false;
   7337 							}
   7338 							else {
   7339 								$newJoinedSelector = $selector->createDerived(array());
   7340 							}
   7341 
   7342 							//put together the parent selectors after the join
   7343 							if ( count($parentSel) > 1) {
   7344 								$afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
   7345 							}
   7346 
   7347 							if ( $parentSel ){
   7348 								$newJoinedSelectorEmpty = false;
   7349 
   7350 								// join the elements so far with the first part of the parent
   7351 								$newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
   7352 
   7353 								$newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
   7354 							}
   7355 
   7356 							if (!$newJoinedSelectorEmpty) {
   7357 								// now add the joined selector
   7358 								$newSelectorPath[] = $newJoinedSelector;
   7359 							}
   7360 
   7361 							// and the rest of the parent
   7362 							$newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
   7363 
   7364 							// add that to our new set of selectors
   7365 							$selectorsMultiplied[] = $newSelectorPath;
   7366 						}
   7367 					}
   7368 				}
   7369 
   7370 				// our new selectors has been multiplied, so reset the state
   7371 				$newSelectors = $selectorsMultiplied;
   7372 				$currentElements = array();
   7373 			}
   7374 		}
   7375 
   7376 		// if we have any elements left over (e.g. .a& .b == .b)
   7377 		// add them on to all the current selectors
   7378 		if( $currentElements ){
   7379 			$this->mergeElementsOnToSelectors($currentElements, $newSelectors);
   7380 		}
   7381 		foreach( $newSelectors as $new_sel){
   7382 			if( $new_sel ){
   7383 				$paths[] = $new_sel;
   7384 			}
   7385 		}
   7386 	}
   7387 
   7388 	function mergeElementsOnToSelectors( $elements, &$selectors){
   7389 
   7390 		if( !$selectors ){
   7391 			$selectors[] = array( new Less_Tree_Selector($elements) );
   7392 			return;
   7393 		}
   7394 
   7395 
   7396 		foreach( $selectors as &$sel){
   7397 
   7398 			// if the previous thing in sel is a parent this needs to join on to it
   7399 			if( $sel ){
   7400 				$last = count($sel)-1;
   7401 				$sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
   7402 			}else{
   7403 				$sel[] = new Less_Tree_Selector( $elements );
   7404 			}
   7405 		}
   7406 	}
   7407 }
   7408  
   7409 
   7410 /**
   7411  * RulesetCall
   7412  *
   7413  * @package Less
   7414  * @subpackage tree
   7415  */
   7416 class Less_Tree_RulesetCall extends Less_Tree{
   7417 
   7418 	public $variable;
   7419 	public $type = "RulesetCall";
   7420 
   7421     public function __construct($variable){
   7422 		$this->variable = $variable;
   7423 	}
   7424 
   7425     public function accept($visitor) {}
   7426 
   7427     public function compile( $env ){
   7428 		$variable = new Less_Tree_Variable($this->variable);
   7429 		$detachedRuleset = $variable->compile($env);
   7430 		return $detachedRuleset->callEval($env);
   7431 	}
   7432 }
   7433 
   7434  
   7435 
   7436 /**
   7437  * Selector
   7438  *
   7439  * @package Less
   7440  * @subpackage tree
   7441  */
   7442 class Less_Tree_Selector extends Less_Tree{
   7443 
   7444 	public $elements;
   7445 	public $condition;
   7446 	public $extendList = array();
   7447 	public $_css;
   7448 	public $index;
   7449 	public $evaldCondition = false;
   7450 	public $type = 'Selector';
   7451 	public $currentFileInfo = array();
   7452 	public $isReferenced;
   7453 	public $mediaEmpty;
   7454 
   7455 	public $elements_len = 0;
   7456 
   7457 	public $_oelements;
   7458 	public $_oelements_len;
   7459 	public $cacheable = true;
   7460 
   7461 	/**
   7462 	 * @param boolean $isReferenced
   7463 	 */
   7464 	public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){
   7465 
   7466 		$this->elements = $elements;
   7467 		$this->elements_len = count($elements);
   7468 		$this->extendList = $extendList;
   7469 		$this->condition = $condition;
   7470 		if( $currentFileInfo ){
   7471 			$this->currentFileInfo = $currentFileInfo;
   7472 		}
   7473 		$this->isReferenced = $isReferenced;
   7474 		if( !$condition ){
   7475 			$this->evaldCondition = true;
   7476 		}
   7477 
   7478 		$this->CacheElements();
   7479 	}
   7480 
   7481     public function accept($visitor) {
   7482 		$this->elements = $visitor->visitArray($this->elements);
   7483 		$this->extendList = $visitor->visitArray($this->extendList);
   7484 		if( $this->condition ){
   7485 			$this->condition = $visitor->visitObj($this->condition);
   7486 		}
   7487 
   7488 		if( $visitor instanceof Less_Visitor_extendFinder ){
   7489 			$this->CacheElements();
   7490 		}
   7491 	}
   7492 
   7493     public function createDerived( $elements, $extendList = null, $evaldCondition = null ){
   7494 		$newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced);
   7495 		$newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
   7496 		return $newSelector;
   7497 	}
   7498 
   7499 
   7500 	public function match( $other ){
   7501 
   7502 		if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){
   7503 			return 0;
   7504 		}
   7505 
   7506 		for( $i = 0; $i < $other->_oelements_len; $i++ ){
   7507 			if( $this->elements[$i]->value !== $other->_oelements[$i]) {
   7508 				return 0;
   7509 			}
   7510 		}
   7511 
   7512 		return $other->_oelements_len; // return number of matched elements
   7513 	}
   7514 
   7515 
   7516 	public function CacheElements(){
   7517 
   7518 		$this->_oelements = array();
   7519 		$css = '';
   7520 
   7521 		foreach($this->elements as $v){
   7522 
   7523 			$css .= $v->combinator;
   7524 			if( !$v->value_is_object ){
   7525 				$css .= $v->value;
   7526 				continue;
   7527 			}
   7528 
   7529 			if( !property_exists($v->value,'value') || !is_string($v->value->value) ){
   7530 				$this->cacheable = false;
   7531 				return;
   7532 			}
   7533 			$css .= $v->value->value;
   7534 		}
   7535 
   7536 		$this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches);
   7537 		if( $this->_oelements_len ){
   7538 			$this->_oelements = $matches[0];
   7539 
   7540 			if( $this->_oelements[0] === '&' ){
   7541 				array_shift($this->_oelements);
   7542 				$this->_oelements_len--;
   7543 			}
   7544 		}
   7545 	}
   7546 
   7547 	public function isJustParentSelector(){
   7548 		return !$this->mediaEmpty &&
   7549 			count($this->elements) === 1 &&
   7550 			$this->elements[0]->value === '&' &&
   7551 			($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '');
   7552 	}
   7553 
   7554 	public function compile($env) {
   7555 
   7556 		$elements = array();
   7557 		foreach($this->elements as $el){
   7558 			$elements[] = $el->compile($env);
   7559 		}
   7560 
   7561 		$extendList = array();
   7562 		foreach($this->extendList as $el){
   7563 			$extendList[] = $el->compile($el);
   7564 		}
   7565 
   7566 		$evaldCondition = false;
   7567 		if( $this->condition ){
   7568 			$evaldCondition = $this->condition->compile($env);
   7569 		}
   7570 
   7571 		return $this->createDerived( $elements, $extendList, $evaldCondition );
   7572 	}
   7573 
   7574 
   7575 	/**
   7576 	 * @see Less_Tree::genCSS
   7577 	 */
   7578     public function genCSS( $output, $firstSelector = true ){
   7579 
   7580 		if( !$firstSelector && $this->elements[0]->combinator === "" ){
   7581 			$output->add(' ', $this->currentFileInfo, $this->index);
   7582 		}
   7583 
   7584 		foreach($this->elements as $element){
   7585 			$element->genCSS( $output );
   7586 		}
   7587 	}
   7588 
   7589     public function markReferenced(){
   7590 		$this->isReferenced = true;
   7591 	}
   7592 
   7593     public function getIsReferenced(){
   7594 		return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced;
   7595 	}
   7596 
   7597     public function getIsOutput(){
   7598 		return $this->evaldCondition;
   7599 	}
   7600 
   7601 }
   7602  
   7603 
   7604 /**
   7605  * UnicodeDescriptor
   7606  *
   7607  * @package Less
   7608  * @subpackage tree
   7609  */
   7610 class Less_Tree_UnicodeDescriptor extends Less_Tree{
   7611 
   7612 	public $value;
   7613 	public $type = 'UnicodeDescriptor';
   7614 
   7615 	public function __construct($value){
   7616 		$this->value = $value;
   7617 	}
   7618 
   7619     /**
   7620      * @see Less_Tree::genCSS
   7621      */
   7622 	public function genCSS( $output ){
   7623 		$output->add( $this->value );
   7624 	}
   7625 
   7626 	public function compile(){
   7627 		return $this;
   7628 	}
   7629 }
   7630 
   7631  
   7632 
   7633 /**
   7634  * Unit
   7635  *
   7636  * @package Less
   7637  * @subpackage tree
   7638  */
   7639 class Less_Tree_Unit extends Less_Tree{
   7640 
   7641 	var $numerator = array();
   7642 	var $denominator = array();
   7643 	public $backupUnit;
   7644 	public $type = 'Unit';
   7645 
   7646     public function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){
   7647 		$this->numerator = $numerator;
   7648 		$this->denominator = $denominator;
   7649 		$this->backupUnit = $backupUnit;
   7650 	}
   7651 
   7652     public function __clone(){
   7653 	}
   7654 
   7655     /**
   7656      * @see Less_Tree::genCSS
   7657      */
   7658     public function genCSS( $output ){
   7659 
   7660 		if( $this->numerator ){
   7661 			$output->add( $this->numerator[0] );
   7662 		}elseif( $this->denominator ){
   7663 			$output->add( $this->denominator[0] );
   7664 		}elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){
   7665 			$output->add( $this->backupUnit );
   7666 			return ;
   7667 		}
   7668 	}
   7669 
   7670     public function toString(){
   7671 		$returnStr = implode('*',$this->numerator);
   7672 		foreach($this->denominator as $d){
   7673 			$returnStr .= '/'.$d;
   7674 		}
   7675 		return $returnStr;
   7676 	}
   7677 
   7678     public function __toString(){
   7679 		return $this->toString();
   7680 	}
   7681 
   7682 
   7683 	/**
   7684 	 * @param Less_Tree_Unit $other
   7685 	 */
   7686     public function compare($other) {
   7687 		return $this->is( $other->toString() ) ? 0 : -1;
   7688 	}
   7689 
   7690     public function is($unitString){
   7691 		return $this->toString() === $unitString;
   7692 	}
   7693 
   7694     public function isLength(){
   7695 		$css = $this->toCSS();
   7696 		return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css);
   7697 	}
   7698 
   7699     public function isAngle() {
   7700 		return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
   7701 	}
   7702 
   7703     public function isEmpty(){
   7704 		return !$this->numerator && !$this->denominator;
   7705 	}
   7706 
   7707     public function isSingular() {
   7708 		return count($this->numerator) <= 1 && !$this->denominator;
   7709 	}
   7710 
   7711 
   7712     public function usedUnits(){
   7713 		$result = array();
   7714 
   7715 		foreach(Less_Tree_UnitConversions::$groups as $groupName){
   7716 			$group = Less_Tree_UnitConversions::${$groupName};
   7717 
   7718 			foreach($this->numerator as $atomicUnit){
   7719 				if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
   7720 					$result[$groupName] = $atomicUnit;
   7721 				}
   7722 			}
   7723 
   7724 			foreach($this->denominator as $atomicUnit){
   7725 				if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){
   7726 					$result[$groupName] = $atomicUnit;
   7727 				}
   7728 			}
   7729 		}
   7730 
   7731 		return $result;
   7732 	}
   7733 
   7734     public function cancel(){
   7735 		$counter = array();
   7736 		$backup = null;
   7737 
   7738 		foreach($this->numerator as $atomicUnit){
   7739 			if( !$backup ){
   7740 				$backup = $atomicUnit;
   7741 			}
   7742 			$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1;
   7743 		}
   7744 
   7745 		foreach($this->denominator as $atomicUnit){
   7746 			if( !$backup ){
   7747 				$backup = $atomicUnit;
   7748 			}
   7749 			$counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1;
   7750 		}
   7751 
   7752 		$this->numerator = array();
   7753 		$this->denominator = array();
   7754 
   7755 		foreach($counter as $atomicUnit => $count){
   7756 			if( $count > 0 ){
   7757 				for( $i = 0; $i < $count; $i++ ){
   7758 					$this->numerator[] = $atomicUnit;
   7759 				}
   7760 			}elseif( $count < 0 ){
   7761 				for( $i = 0; $i < -$count; $i++ ){
   7762 					$this->denominator[] = $atomicUnit;
   7763 				}
   7764 			}
   7765 		}
   7766 
   7767 		if( !$this->numerator && !$this->denominator && $backup ){
   7768 			$this->backupUnit = $backup;
   7769 		}
   7770 
   7771 		sort($this->numerator);
   7772 		sort($this->denominator);
   7773 	}
   7774 
   7775 
   7776 }
   7777 
   7778  
   7779 
   7780 /**
   7781  * UnitConversions
   7782  *
   7783  * @package Less
   7784  * @subpackage tree
   7785  */
   7786 class Less_Tree_UnitConversions{
   7787 
   7788 	public static $groups = array('length','duration','angle');
   7789 
   7790 	public static $length = array(
   7791 		'm'=> 1,
   7792 		'cm'=> 0.01,
   7793 		'mm'=> 0.001,
   7794 		'in'=> 0.0254,
   7795 		'px'=> 0.000264583, // 0.0254 / 96,
   7796 		'pt'=> 0.000352778, // 0.0254 / 72,
   7797 		'pc'=> 0.004233333, // 0.0254 / 72 * 12
   7798 		);
   7799 
   7800 	public static $duration = array(
   7801 		's'=> 1,
   7802 		'ms'=> 0.001
   7803 		);
   7804 
   7805 	public static $angle = array(
   7806 		'rad' => 0.1591549430919,	// 1/(2*M_PI),
   7807 		'deg' => 0.002777778, 		// 1/360,
   7808 		'grad'=> 0.0025,			// 1/400,
   7809 		'turn'=> 1
   7810 		);
   7811 
   7812 } 
   7813 
   7814 /**
   7815  * Url
   7816  *
   7817  * @package Less
   7818  * @subpackage tree
   7819  */
   7820 class Less_Tree_Url extends Less_Tree{
   7821 
   7822 	public $attrs;
   7823 	public $value;
   7824 	public $currentFileInfo;
   7825 	public $isEvald;
   7826 	public $type = 'Url';
   7827 
   7828 	public function __construct($value, $currentFileInfo = null, $isEvald = null){
   7829 		$this->value = $value;
   7830 		$this->currentFileInfo = $currentFileInfo;
   7831 		$this->isEvald = $isEvald;
   7832 	}
   7833 
   7834     public function accept( $visitor ){
   7835 		$this->value = $visitor->visitObj($this->value);
   7836 	}
   7837 
   7838     /**
   7839      * @see Less_Tree::genCSS
   7840      */
   7841     public function genCSS( $output ){
   7842 		$output->add( 'url(' );
   7843 		$this->value->genCSS( $output );
   7844 		$output->add( ')' );
   7845 	}
   7846 
   7847 	/**
   7848 	 * @param Less_Functions $ctx
   7849 	 */
   7850 	public function compile($ctx){
   7851 		$val = $this->value->compile($ctx);
   7852 
   7853 		if( !$this->isEvald ){
   7854 			// Add the base path if the URL is relative
   7855 			if( Less_Parser::$options['relativeUrls']
   7856 				&& $this->currentFileInfo
   7857 				&& is_string($val->value)
   7858 				&& Less_Environment::isPathRelative($val->value)
   7859 			){
   7860 				$rootpath = $this->currentFileInfo['uri_root'];
   7861 				if ( !$val->quote ){
   7862 					$rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath );
   7863 				}
   7864 				$val->value = $rootpath . $val->value;
   7865 			}
   7866 
   7867 			$val->value = Less_Environment::normalizePath( $val->value);
   7868 		}
   7869 
   7870 		// Add cache buster if enabled
   7871 		if( Less_Parser::$options['urlArgs'] ){
   7872 			if( !preg_match('/^\s*data:/',$val->value) ){
   7873 				$delimiter = strpos($val->value,'?') === false ? '?' : '&';
   7874 				$urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
   7875 				$hash_pos = strpos($val->value,'#');
   7876 				if( $hash_pos !== false ){
   7877 					$val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0);
   7878 				} else {
   7879 					$val->value .= $urlArgs;
   7880 				}
   7881 			}
   7882 		}
   7883 
   7884 		return new Less_Tree_URL($val, $this->currentFileInfo, true);
   7885 	}
   7886 
   7887 }
   7888  
   7889 
   7890 /**
   7891  * Value
   7892  *
   7893  * @package Less
   7894  * @subpackage tree
   7895  */
   7896 class Less_Tree_Value extends Less_Tree{
   7897 
   7898 	public $type = 'Value';
   7899 	public $value;
   7900 
   7901 	public function __construct($value){
   7902 		$this->value = $value;
   7903 	}
   7904 
   7905     public function accept($visitor) {
   7906 		$this->value = $visitor->visitArray($this->value);
   7907 	}
   7908 
   7909 	public function compile($env){
   7910 
   7911 		$ret = array();
   7912 		$i = 0;
   7913 		foreach($this->value as $i => $v){
   7914 			$ret[] = $v->compile($env);
   7915 		}
   7916 		if( $i > 0 ){
   7917 			return new Less_Tree_Value($ret);
   7918 		}
   7919 		return $ret[0];
   7920 	}
   7921 
   7922     /**
   7923      * @see Less_Tree::genCSS
   7924      */
   7925 	function genCSS( $output ){
   7926 		$len = count($this->value);
   7927 		for($i = 0; $i < $len; $i++ ){
   7928 			$this->value[$i]->genCSS( $output );
   7929 			if( $i+1 < $len ){
   7930 				$output->add( Less_Environment::$_outputMap[','] );
   7931 			}
   7932 		}
   7933 	}
   7934 
   7935 }
   7936  
   7937 
   7938 /**
   7939  * Variable
   7940  *
   7941  * @package Less
   7942  * @subpackage tree
   7943  */
   7944 class Less_Tree_Variable extends Less_Tree{
   7945 
   7946 	public $name;
   7947 	public $index;
   7948 	public $currentFileInfo;
   7949 	public $evaluating = false;
   7950 	public $type = 'Variable';
   7951 
   7952     /**
   7953      * @param string $name
   7954      */
   7955     public function __construct($name, $index = null, $currentFileInfo = null) {
   7956         $this->name = $name;
   7957         $this->index = $index;
   7958 		$this->currentFileInfo = $currentFileInfo;
   7959     }
   7960 
   7961 	public function compile($env) {
   7962 
   7963 		if( $this->name[1] === '@' ){
   7964 			$v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1, $this->currentFileInfo);
   7965 			$name = '@' . $v->compile($env)->value;
   7966 		}else{
   7967 			$name = $this->name;
   7968 		}
   7969 
   7970 		if ($this->evaluating) {
   7971 			throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo);
   7972 		}
   7973 
   7974 		$this->evaluating = true;
   7975 
   7976 		foreach($env->frames as $frame){
   7977 			if( $v = $frame->variable($name) ){
   7978 				$r = $v->value->compile($env);
   7979 				$this->evaluating = false;
   7980 				return $r;
   7981 			}
   7982 		}
   7983 
   7984 		throw new Less_Exception_Compiler("variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo);
   7985 	}
   7986 
   7987 }
   7988  
   7989 
   7990 
   7991 class Less_Tree_Mixin_Call extends Less_Tree{
   7992 
   7993 	public $selector;
   7994 	public $arguments;
   7995 	public $index;
   7996 	public $currentFileInfo;
   7997 
   7998 	public $important;
   7999 	public $type = 'MixinCall';
   8000 
   8001 	/**
   8002 	 * less.js: tree.mixin.Call
   8003 	 *
   8004 	 */
   8005 	public function __construct($elements, $args, $index, $currentFileInfo, $important = false){
   8006 		$this->selector = new Less_Tree_Selector($elements);
   8007 		$this->arguments = $args;
   8008 		$this->index = $index;
   8009 		$this->currentFileInfo = $currentFileInfo;
   8010 		$this->important = $important;
   8011 	}
   8012 
   8013 	//function accept($visitor){
   8014 	//	$this->selector = $visitor->visit($this->selector);
   8015 	//	$this->arguments = $visitor->visit($this->arguments);
   8016 	//}
   8017 
   8018 
   8019 	public function compile($env){
   8020 
   8021 		$rules = array();
   8022 		$match = false;
   8023 		$isOneFound = false;
   8024 		$candidates = array();
   8025 		$defaultUsed = false;
   8026 		$conditionResult = array();
   8027 
   8028 		$args = array();
   8029 		foreach($this->arguments as $a){
   8030 			$args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) );
   8031 		}
   8032 
   8033 		foreach($env->frames as $frame){
   8034 
   8035 			$mixins = $frame->find($this->selector);
   8036 
   8037 			if( !$mixins ){
   8038 				continue;
   8039 			}
   8040 
   8041 			$isOneFound = true;
   8042 			$defNone = 0;
   8043 			$defTrue = 1;
   8044 			$defFalse = 2;
   8045 
   8046 			// To make `default()` function independent of definition order we have two "subpasses" here.
   8047 			// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
   8048 			// and build candidate list with corresponding flags. Then, when we know all possible matches,
   8049 			// we make a final decision.
   8050 
   8051 			$mixins_len = count($mixins);
   8052 			for( $m = 0; $m < $mixins_len; $m++ ){
   8053 				$mixin = $mixins[$m];
   8054 
   8055 				if( $this->IsRecursive( $env, $mixin ) ){
   8056 					continue;
   8057 				}
   8058 
   8059 				if( $mixin->matchArgs($args, $env) ){
   8060 
   8061 					$candidate = array('mixin' => $mixin, 'group' => $defNone);
   8062 
   8063 					if( $mixin instanceof Less_Tree_Ruleset ){
   8064 
   8065 						for( $f = 0; $f < 2; $f++ ){
   8066 							Less_Tree_DefaultFunc::value($f);
   8067 							$conditionResult[$f] = $mixin->matchCondition( $args, $env);
   8068 						}
   8069 						if( $conditionResult[0] || $conditionResult[1] ){
   8070 							if( $conditionResult[0] != $conditionResult[1] ){
   8071 								$candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
   8072 							}
   8073 
   8074 							$candidates[] = $candidate;
   8075 						}
   8076 					}else{
   8077 						$candidates[] = $candidate;
   8078 					}
   8079 
   8080 					$match = true;
   8081 				}
   8082 			}
   8083 
   8084 			Less_Tree_DefaultFunc::reset();
   8085 
   8086 
   8087 			$count = array(0, 0, 0);
   8088 			for( $m = 0; $m < count($candidates); $m++ ){
   8089 				$count[ $candidates[$m]['group'] ]++;
   8090 			}
   8091 
   8092 			if( $count[$defNone] > 0 ){
   8093 				$defaultResult = $defFalse;
   8094 			} else {
   8095 				$defaultResult = $defTrue;
   8096 				if( ($count[$defTrue] + $count[$defFalse]) > 1 ){
   8097 					throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format($args) . '`' );
   8098 				}
   8099 			}
   8100 
   8101 
   8102 			$candidates_length = count($candidates);
   8103 			$length_1 = ($candidates_length == 1);
   8104 
   8105 			for( $m = 0; $m < $candidates_length; $m++){
   8106 				$candidate = $candidates[$m]['group'];
   8107 				if( ($candidate === $defNone) || ($candidate === $defaultResult) ){
   8108 					try{
   8109 						$mixin = $candidates[$m]['mixin'];
   8110 						if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
   8111 							$mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false);
   8112 							$mixin->originalRuleset = $mixins[$m]->originalRuleset;
   8113 						}
   8114 						$rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules);
   8115 					} catch (Exception $e) {
   8116 						//throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
   8117 						throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo);
   8118 					}
   8119 				}
   8120 			}
   8121 
   8122 			if( $match ){
   8123 				if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){
   8124 					Less_Tree::ReferencedArray($rules);
   8125 				}
   8126 
   8127 				return $rules;
   8128 			}
   8129 		}
   8130 
   8131 		if( $isOneFound ){
   8132 			throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo);
   8133 
   8134 		}else{
   8135 			throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index);
   8136 		}
   8137 
   8138 	}
   8139 
   8140 	/**
   8141 	 * Format the args for use in exception messages
   8142 	 *
   8143 	 */
   8144 	private function Format($args){
   8145 		$message = array();
   8146 		if( $args ){
   8147 			foreach($args as $a){
   8148 				$argValue = '';
   8149 				if( $a['name'] ){
   8150 					$argValue .= $a['name'] . ':';
   8151 				}
   8152 				if( is_object($a['value']) ){
   8153 					$argValue .= $a['value']->toCSS();
   8154 				}else{
   8155 					$argValue .= '???';
   8156 				}
   8157 				$message[] = $argValue;
   8158 			}
   8159 		}
   8160 		return implode(', ',$message);
   8161 	}
   8162 
   8163 
   8164 	/**
   8165 	 * Are we in a recursive mixin call?
   8166 	 *
   8167 	 * @return bool
   8168 	 */
   8169 	private function IsRecursive( $env, $mixin ){
   8170 
   8171 		foreach($env->frames as $recur_frame){
   8172 			if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
   8173 
   8174 				if( $mixin === $recur_frame ){
   8175 					return true;
   8176 				}
   8177 
   8178 				if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){
   8179 					return true;
   8180 				}
   8181 			}
   8182 		}
   8183 
   8184 		return false;
   8185 	}
   8186 
   8187 }
   8188 
   8189 
   8190  
   8191 
   8192 class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{
   8193 	public $name;
   8194 	public $selectors;
   8195 	public $params;
   8196 	public $arity		= 0;
   8197 	public $rules;
   8198 	public $lookups		= array();
   8199 	public $required	= 0;
   8200 	public $frames		= array();
   8201 	public $condition;
   8202 	public $variadic;
   8203 	public $type		= 'MixinDefinition';
   8204 
   8205 
   8206 	// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
   8207 	public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){
   8208 		$this->name = $name;
   8209 		$this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name))));
   8210 
   8211 		$this->params = $params;
   8212 		$this->condition = $condition;
   8213 		$this->variadic = $variadic;
   8214 		$this->rules = $rules;
   8215 
   8216 		if( $params ){
   8217 			$this->arity = count($params);
   8218 			foreach( $params as $p ){
   8219 				if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) {
   8220 					$this->required++;
   8221 				}
   8222 			}
   8223 		}
   8224 
   8225 		$this->frames = $frames;
   8226 		$this->SetRulesetIndex();
   8227 	}
   8228 
   8229 
   8230 
   8231 	//function accept( $visitor ){
   8232 	//	$this->params = $visitor->visit($this->params);
   8233 	//	$this->rules = $visitor->visit($this->rules);
   8234 	//	$this->condition = $visitor->visit($this->condition);
   8235 	//}
   8236 
   8237 
   8238 	public function toCSS(){
   8239 		return '';
   8240 	}
   8241 
   8242 	// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
   8243 	public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){
   8244 		$frame = new Less_Tree_Ruleset(null, array());
   8245 		$params = $this->params;
   8246 		$mixinEnv = null;
   8247 		$argsLength = 0;
   8248 
   8249 		if( $args ){
   8250 			$argsLength = count($args);
   8251 			for($i = 0; $i < $argsLength; $i++ ){
   8252 				$arg = $args[$i];
   8253 
   8254 				if( $arg && $arg['name'] ){
   8255 					$isNamedFound = false;
   8256 
   8257 					foreach($params as $j => $param){
   8258 						if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) {
   8259 							$evaldArguments[$j] = $arg['value']->compile($env);
   8260 							array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) );
   8261 							$isNamedFound = true;
   8262 							break;
   8263 						}
   8264 					}
   8265 					if ($isNamedFound) {
   8266 						array_splice($args, $i, 1);
   8267 						$i--;
   8268 						$argsLength--;
   8269 						continue;
   8270 					} else {
   8271 						throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found');
   8272 					}
   8273 				}
   8274 			}
   8275 		}
   8276 
   8277 		$argIndex = 0;
   8278 		foreach($params as $i => $param){
   8279 
   8280 			if ( isset($evaldArguments[$i]) ){ continue; }
   8281 
   8282 			$arg = null;
   8283 			if( isset($args[$argIndex]) ){
   8284 				$arg = $args[$argIndex];
   8285 			}
   8286 
   8287 			if (isset($param['name']) && $param['name']) {
   8288 
   8289 				if( isset($param['variadic']) ){
   8290 					$varargs = array();
   8291 					for ($j = $argIndex; $j < $argsLength; $j++) {
   8292 						$varargs[] = $args[$j]['value']->compile($env);
   8293 					}
   8294 					$expression = new Less_Tree_Expression($varargs);
   8295 					array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env)));
   8296 				}else{
   8297 					$val = ($arg && $arg['value']) ? $arg['value'] : false;
   8298 
   8299 					if ($val) {
   8300 						$val = $val->compile($env);
   8301 					} else if ( isset($param['value']) ) {
   8302 
   8303 						if( !$mixinEnv ){
   8304 							$mixinEnv = new Less_Environment();
   8305 							$mixinEnv->frames = array_merge( array($frame), $mixinFrames);
   8306 						}
   8307 
   8308 						$val = $param['value']->compile($mixinEnv);
   8309 						$frame->resetCache();
   8310 					} else {
   8311 						throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")");
   8312 					}
   8313 
   8314 					array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val));
   8315 					$evaldArguments[$i] = $val;
   8316 				}
   8317 			}
   8318 
   8319 			if ( isset($param['variadic']) && $args) {
   8320 				for ($j = $argIndex; $j < $argsLength; $j++) {
   8321 					$evaldArguments[$j] = $args[$j]['value']->compile($env);
   8322 				}
   8323 			}
   8324 			$argIndex++;
   8325 		}
   8326 
   8327 		ksort($evaldArguments);
   8328 		$evaldArguments = array_values($evaldArguments);
   8329 
   8330 		return $frame;
   8331 	}
   8332 
   8333 	public function compile($env) {
   8334 		if( $this->frames ){
   8335 			return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
   8336 		}
   8337 		return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
   8338 	}
   8339 
   8340 	public function evalCall($env, $args = NULL, $important = NULL) {
   8341 
   8342 		Less_Environment::$mixin_stack++;
   8343 
   8344 		$_arguments = array();
   8345 
   8346 		if( $this->frames ){
   8347 			$mixinFrames = array_merge($this->frames, $env->frames);
   8348 		}else{
   8349 			$mixinFrames = $env->frames;
   8350 		}
   8351 
   8352 		$frame = $this->compileParams($env, $mixinFrames, $args, $_arguments);
   8353 
   8354 		$ex = new Less_Tree_Expression($_arguments);
   8355 		array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env)));
   8356 
   8357 
   8358 		$ruleset = new Less_Tree_Ruleset(null, $this->rules);
   8359 		$ruleset->originalRuleset = $this->ruleset_id;
   8360 
   8361 
   8362 		$ruleSetEnv = new Less_Environment();
   8363 		$ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames );
   8364 		$ruleset = $ruleset->compile( $ruleSetEnv );
   8365 
   8366 		if( $important ){
   8367 			$ruleset = $ruleset->makeImportant();
   8368 		}
   8369 
   8370 		Less_Environment::$mixin_stack--;
   8371 
   8372 		return $ruleset;
   8373 	}
   8374 
   8375 
   8376 	public function matchCondition($args, $env) {
   8377 
   8378 		if( !$this->condition ){
   8379 			return true;
   8380 		}
   8381 
   8382 		// set array to prevent error on array_merge
   8383 		if(!is_array($this->frames)) {
   8384              $this->frames = array();
   8385         }
   8386 
   8387 		$frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args );
   8388 
   8389 		$compile_env = new Less_Environment();
   8390 		$compile_env->frames = array_merge(
   8391 				array($frame)		// the parameter variables
   8392 				, $this->frames		// the parent namespace/mixin frames
   8393 				, $env->frames		// the current environment frames
   8394 			);
   8395 
   8396 		$compile_env->functions = $env->functions;
   8397 
   8398 		return (bool)$this->condition->compile($compile_env);
   8399 	}
   8400 
   8401 	public function matchArgs($args, $env = NULL){
   8402 		$argsLength = count($args);
   8403 
   8404 		if( !$this->variadic ){
   8405 			if( $argsLength < $this->required ){
   8406 				return false;
   8407 			}
   8408 			if( $argsLength > count($this->params) ){
   8409 				return false;
   8410 			}
   8411 		}else{
   8412 			if( $argsLength < ($this->required - 1)){
   8413 				return false;
   8414 			}
   8415 		}
   8416 
   8417 		$len = min($argsLength, $this->arity);
   8418 
   8419 		for( $i = 0; $i < $len; $i++ ){
   8420 			if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){
   8421 				if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){
   8422 					return false;
   8423 				}
   8424 			}
   8425 		}
   8426 
   8427 		return true;
   8428 	}
   8429 
   8430 }
   8431  
   8432 
   8433 /**
   8434  * Extend Finder Visitor
   8435  *
   8436  * @package Less
   8437  * @subpackage visitor
   8438  */
   8439 class Less_Visitor_extendFinder extends Less_Visitor{
   8440 
   8441 	public $contexts = array();
   8442 	public $allExtendsStack;
   8443 	public $foundExtends;
   8444 
   8445 	public function __construct(){
   8446 		$this->contexts = array();
   8447 		$this->allExtendsStack = array(array());
   8448 		parent::__construct();
   8449 	}
   8450 
   8451 	/**
   8452 	 * @param Less_Tree_Ruleset $root
   8453 	 */
   8454     public function run($root){
   8455 		$root = $this->visitObj($root);
   8456 		$root->allExtends =& $this->allExtendsStack[0];
   8457 		return $root;
   8458 	}
   8459 
   8460     public function visitRule($ruleNode, &$visitDeeper ){
   8461 		$visitDeeper = false;
   8462 	}
   8463 
   8464     public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
   8465 		$visitDeeper = false;
   8466 	}
   8467 
   8468     public function visitRuleset($rulesetNode){
   8469 
   8470 		if( $rulesetNode->root ){
   8471 			return;
   8472 		}
   8473 
   8474 		$allSelectorsExtendList = array();
   8475 
   8476 		// get &:extend(.a); rules which apply to all selectors in this ruleset
   8477 		if( $rulesetNode->rules ){
   8478 			foreach($rulesetNode->rules as $rule){
   8479 				if( $rule instanceof Less_Tree_Extend ){
   8480 					$allSelectorsExtendList[] = $rule;
   8481 					$rulesetNode->extendOnEveryPath = true;
   8482 				}
   8483 			}
   8484 		}
   8485 
   8486 
   8487 		// now find every selector and apply the extends that apply to all extends
   8488 		// and the ones which apply to an individual extend
   8489 		foreach($rulesetNode->paths as $selectorPath){
   8490 			$selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1];
   8491 
   8492 			$j = 0;
   8493 			foreach($selector->extendList as $extend){
   8494 				$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
   8495 			}
   8496 			foreach($allSelectorsExtendList as $extend){
   8497 				$this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j);
   8498 			}
   8499 		}
   8500 
   8501 		$this->contexts[] = $rulesetNode->selectors;
   8502 	}
   8503 
   8504     public function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){
   8505 		$this->foundExtends = true;
   8506 		$extend = clone $extend;
   8507 		$extend->findSelfSelectors( $selectorPath );
   8508 		$extend->ruleset = $rulesetNode;
   8509 		if( $j === 0 ){
   8510 			$extend->firstExtendOnThisSelectorPath = true;
   8511 		}
   8512 
   8513 		$end_key = count($this->allExtendsStack)-1;
   8514 		$this->allExtendsStack[$end_key][] = $extend;
   8515 		$j++;
   8516 	}
   8517 
   8518 
   8519     public function visitRulesetOut( $rulesetNode ){
   8520 		if( !is_object($rulesetNode) || !$rulesetNode->root ){
   8521 			array_pop($this->contexts);
   8522 		}
   8523 	}
   8524 
   8525     public function visitMedia( $mediaNode ){
   8526 		$mediaNode->allExtends = array();
   8527 		$this->allExtendsStack[] =& $mediaNode->allExtends;
   8528 	}
   8529 
   8530     public function visitMediaOut(){
   8531 		array_pop($this->allExtendsStack);
   8532 	}
   8533 
   8534     public function visitDirective( $directiveNode ){
   8535 		$directiveNode->allExtends = array();
   8536 		$this->allExtendsStack[] =& $directiveNode->allExtends;
   8537 	}
   8538 
   8539     public function visitDirectiveOut(){
   8540 		array_pop($this->allExtendsStack);
   8541 	}
   8542 }
   8543 
   8544 
   8545  
   8546 
   8547 /*
   8548 class Less_Visitor_import extends Less_VisitorReplacing{
   8549 
   8550 	public $_visitor;
   8551 	public $_importer;
   8552 	public $importCount;
   8553 
   8554 	function __construct( $evalEnv ){
   8555 		$this->env = $evalEnv;
   8556 		$this->importCount = 0;
   8557 		parent::__construct();
   8558 	}
   8559 
   8560 
   8561 	function run( $root ){
   8562 		$root = $this->visitObj($root);
   8563 		$this->isFinished = true;
   8564 
   8565 		//if( $this->importCount === 0) {
   8566 		//	$this->_finish();
   8567 		//}
   8568 	}
   8569 
   8570 	function visitImport($importNode, &$visitDeeper ){
   8571 		$importVisitor = $this;
   8572 		$inlineCSS = $importNode->options['inline'];
   8573 
   8574 		if( !$importNode->css || $inlineCSS ){
   8575 			$evaldImportNode = $importNode->compileForImport($this->env);
   8576 
   8577 			if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
   8578 				$importNode = $evaldImportNode;
   8579 				$this->importCount++;
   8580 				$env = clone $this->env;
   8581 
   8582 				if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
   8583 					$env->importMultiple = true;
   8584 				}
   8585 
   8586 				//get path & uri
   8587 				$path_and_uri = null;
   8588 				if( is_callable(Less_Parser::$options['import_callback']) ){
   8589 					$path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
   8590 				}
   8591 
   8592 				if( !$path_and_uri ){
   8593 					$path_and_uri = $importNode->PathAndUri();
   8594 				}
   8595 
   8596 				if( $path_and_uri ){
   8597 					list($full_path, $uri) = $path_and_uri;
   8598 				}else{
   8599 					$full_path = $uri = $importNode->getPath();
   8600 				}
   8601 
   8602 
   8603 				//import once
   8604 				if( $importNode->skip( $full_path, $env) ){
   8605 					return array();
   8606 				}
   8607 
   8608 				if( $importNode->options['inline'] ){
   8609 					//todo needs to reference css file not import
   8610 					//$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
   8611 
   8612 					Less_Parser::AddParsedFile($full_path);
   8613 					$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
   8614 
   8615 					if( $importNode->features ){
   8616 						return new Less_Tree_Media( array($contents), $importNode->features->value );
   8617 					}
   8618 
   8619 					return array( $contents );
   8620 				}
   8621 
   8622 
   8623 				// css ?
   8624 				if( $importNode->css ){
   8625 					$features = ( $importNode->features ? $importNode->features->compile($env) : null );
   8626 					return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
   8627 				}
   8628 
   8629 				return $importNode->ParseImport( $full_path, $uri, $env );
   8630 			}
   8631 
   8632 		}
   8633 
   8634 		$visitDeeper = false;
   8635 		return $importNode;
   8636 	}
   8637 
   8638 
   8639 	function visitRule( $ruleNode, &$visitDeeper ){
   8640 		$visitDeeper = false;
   8641 		return $ruleNode;
   8642 	}
   8643 
   8644 	function visitDirective($directiveNode, $visitArgs){
   8645 		array_unshift($this->env->frames,$directiveNode);
   8646 		return $directiveNode;
   8647 	}
   8648 
   8649 	function visitDirectiveOut($directiveNode) {
   8650 		array_shift($this->env->frames);
   8651 	}
   8652 
   8653 	function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
   8654 		array_unshift($this->env->frames,$mixinDefinitionNode);
   8655 		return $mixinDefinitionNode;
   8656 	}
   8657 
   8658 	function visitMixinDefinitionOut($mixinDefinitionNode) {
   8659 		array_shift($this->env->frames);
   8660 	}
   8661 
   8662 	function visitRuleset($rulesetNode, $visitArgs) {
   8663 		array_unshift($this->env->frames,$rulesetNode);
   8664 		return $rulesetNode;
   8665 	}
   8666 
   8667 	function visitRulesetOut($rulesetNode) {
   8668 		array_shift($this->env->frames);
   8669 	}
   8670 
   8671 	function visitMedia($mediaNode, $visitArgs) {
   8672 		array_unshift($this->env->frames, $mediaNode->ruleset);
   8673 		return $mediaNode;
   8674 	}
   8675 
   8676 	function visitMediaOut($mediaNode) {
   8677 		array_shift($this->env->frames);
   8678 	}
   8679 
   8680 }
   8681 */
   8682 
   8683 
   8684  
   8685 
   8686 /**
   8687  * Join Selector Visitor
   8688  *
   8689  * @package Less
   8690  * @subpackage visitor
   8691  */
   8692 class Less_Visitor_joinSelector extends Less_Visitor{
   8693 
   8694 	public $contexts = array( array() );
   8695 
   8696 	/**
   8697 	 * @param Less_Tree_Ruleset $root
   8698 	 */
   8699 	public function run( $root ){
   8700 		return $this->visitObj($root);
   8701 	}
   8702 
   8703     public function visitRule( $ruleNode, &$visitDeeper ){
   8704 		$visitDeeper = false;
   8705 	}
   8706 
   8707     public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
   8708 		$visitDeeper = false;
   8709 	}
   8710 
   8711     public function visitRuleset( $rulesetNode ){
   8712 
   8713 		$paths = array();
   8714 
   8715 		if( !$rulesetNode->root ){
   8716 			$selectors = array();
   8717 
   8718 			if( $rulesetNode->selectors && $rulesetNode->selectors ){
   8719 				foreach($rulesetNode->selectors as $selector){
   8720 					if( $selector->getIsOutput() ){
   8721 						$selectors[] = $selector;
   8722 					}
   8723 				}
   8724 			}
   8725 
   8726 			if( !$selectors ){
   8727 				$rulesetNode->selectors = null;
   8728 				$rulesetNode->rules = null;
   8729 			}else{
   8730 				$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
   8731 				$paths = $rulesetNode->joinSelectors( $context, $selectors);
   8732 			}
   8733 
   8734 			$rulesetNode->paths = $paths;
   8735 		}
   8736 
   8737 		$this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
   8738 	}
   8739 
   8740     public function visitRulesetOut(){
   8741 		array_pop($this->contexts);
   8742 	}
   8743 
   8744     public function visitMedia($mediaNode) {
   8745 		$context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1];
   8746 
   8747 		if( !count($context) || (is_object($context[0]) && $context[0]->multiMedia) ){
   8748 			$mediaNode->rules[0]->root = true;
   8749 		}
   8750 	}
   8751 
   8752 }
   8753 
   8754  
   8755 
   8756 /**
   8757  * Process Extends Visitor
   8758  *
   8759  * @package Less
   8760  * @subpackage visitor
   8761  */
   8762 class Less_Visitor_processExtends extends Less_Visitor{
   8763 
   8764 	public $allExtendsStack;
   8765 
   8766 	/**
   8767 	 * @param Less_Tree_Ruleset $root
   8768 	 */
   8769 	public function run( $root ){
   8770 		$extendFinder = new Less_Visitor_extendFinder();
   8771 		$extendFinder->run( $root );
   8772 		if( !$extendFinder->foundExtends){
   8773 			return $root;
   8774 		}
   8775 
   8776 		$root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends);
   8777 
   8778 		$this->allExtendsStack = array();
   8779 		$this->allExtendsStack[] = &$root->allExtends;
   8780 
   8781 		return $this->visitObj( $root );
   8782 	}
   8783 
   8784 	private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){
   8785 		//
   8786 		// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
   8787 		// the selector we would do normally, but we are also adding an extend with the same target selector
   8788 		// this means this new extend can then go and alter other extends
   8789 		//
   8790 		// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
   8791 		// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
   8792 		// we look at each selector at a time, as is done in visitRuleset
   8793 
   8794 		$extendsToAdd = array();
   8795 
   8796 
   8797 		//loop through comparing every extend with every target extend.
   8798 		// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
   8799 		// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
   8800 		// and the second is the target.
   8801 		// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
   8802 		// case when processing media queries
   8803 		for( $extendIndex = 0, $extendsList_len = count($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){
   8804 			for( $targetExtendIndex = 0; $targetExtendIndex < count($extendsListTarget); $targetExtendIndex++ ){
   8805 
   8806 				$extend = $extendsList[$extendIndex];
   8807 				$targetExtend = $extendsListTarget[$targetExtendIndex];
   8808 
   8809 				// look for circular references
   8810 				if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){
   8811 					continue;
   8812 				}
   8813 
   8814 				// find a match in the target extends self selector (the bit before :extend)
   8815 				$selectorPath = array( $targetExtend->selfSelectors[0] );
   8816 				$matches = $this->findMatch( $extend, $selectorPath);
   8817 
   8818 
   8819 				if( $matches ){
   8820 
   8821 					// we found a match, so for each self selector..
   8822 					foreach($extend->selfSelectors as $selfSelector ){
   8823 
   8824 
   8825 						// process the extend as usual
   8826 						$newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector);
   8827 
   8828 						// but now we create a new extend from it
   8829 						$newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0);
   8830 						$newExtend->selfSelectors = $newSelector;
   8831 
   8832 						// add the extend onto the list of extends for that selector
   8833 						end($newSelector)->extendList = array($newExtend);
   8834 						//$newSelector[ count($newSelector)-1]->extendList = array($newExtend);
   8835 
   8836 						// record that we need to add it.
   8837 						$extendsToAdd[] = $newExtend;
   8838 						$newExtend->ruleset = $targetExtend->ruleset;
   8839 
   8840 						//remember its parents for circular references
   8841 						$newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids);
   8842 
   8843 						// only process the selector once.. if we have :extend(.a,.b) then multiple
   8844 						// extends will look at the same selector path, so when extending
   8845 						// we know that any others will be duplicates in terms of what is added to the css
   8846 						if( $targetExtend->firstExtendOnThisSelectorPath ){
   8847 							$newExtend->firstExtendOnThisSelectorPath = true;
   8848 							$targetExtend->ruleset->paths[] = $newSelector;
   8849 						}
   8850 					}
   8851 				}
   8852 			}
   8853 		}
   8854 
   8855 		if( $extendsToAdd ){
   8856 			// try to detect circular references to stop a stack overflow.
   8857 			// may no longer be needed.			$this->extendChainCount++;
   8858 			if( $iterationCount > 100) {
   8859 
   8860 				try{
   8861 					$selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
   8862 					$selectorTwo = $extendsToAdd[0]->selector->toCSS();
   8863 				}catch(Exception $e){
   8864 					$selectorOne = "{unable to calculate}";
   8865 					$selectorTwo = "{unable to calculate}";
   8866 				}
   8867 
   8868 				throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")");
   8869 			}
   8870 
   8871 			// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
   8872 			$extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1);
   8873 		}
   8874 
   8875 		return array_merge($extendsList, $extendsToAdd);
   8876 	}
   8877 
   8878 
   8879 	protected function visitRule( $ruleNode, &$visitDeeper ){
   8880 		$visitDeeper = false;
   8881 	}
   8882 
   8883 	protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){
   8884 		$visitDeeper = false;
   8885 	}
   8886 
   8887 	protected function visitSelector( $selectorNode, &$visitDeeper ){
   8888 		$visitDeeper = false;
   8889 	}
   8890 
   8891 	protected function visitRuleset($rulesetNode){
   8892 
   8893 
   8894 		if( $rulesetNode->root ){
   8895 			return;
   8896 		}
   8897 
   8898 		$allExtends	= end($this->allExtendsStack);
   8899 		$paths_len = count($rulesetNode->paths);
   8900 
   8901 		// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
   8902 		foreach($allExtends as $allExtend){
   8903 			for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){
   8904 
   8905 				// extending extends happens initially, before the main pass
   8906 				if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){
   8907 					continue;
   8908 				}
   8909 
   8910 				$selectorPath = $rulesetNode->paths[$pathIndex];
   8911 
   8912 				if( end($selectorPath)->extendList ){
   8913 					continue;
   8914 				}
   8915 
   8916 				$this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath);
   8917 
   8918 			}
   8919 		}
   8920 	}
   8921 
   8922 
   8923 	private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){
   8924 		$matches = $this->findMatch($extend, $selectorPath);
   8925 
   8926 		if( $matches ){
   8927 			foreach($extend->selfSelectors as $selfSelector ){
   8928 				$rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector);
   8929 			}
   8930 		}
   8931 	}
   8932 
   8933 
   8934 
   8935 	private function findMatch($extend, $haystackSelectorPath ){
   8936 
   8937 
   8938 		if( !$this->HasMatches($extend, $haystackSelectorPath) ){
   8939 			return false;
   8940 		}
   8941 
   8942 
   8943 		//
   8944 		// look through the haystack selector path to try and find the needle - extend.selector
   8945 		// returns an array of selector matches that can then be replaced
   8946 		//
   8947 		$needleElements = $extend->selector->elements;
   8948 		$potentialMatches = array();
   8949 		$potentialMatches_len = 0;
   8950 		$potentialMatch = null;
   8951 		$matches = array();
   8952 
   8953 
   8954 
   8955 		// loop through the haystack elements
   8956 		$haystack_path_len = count($haystackSelectorPath);
   8957 		for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){
   8958 			$hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
   8959 
   8960 			$haystack_elements_len = count($hackstackSelector->elements);
   8961 			for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){
   8962 
   8963 				$haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
   8964 
   8965 				// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
   8966 				if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){
   8967 					$potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator);
   8968 					$potentialMatches_len++;
   8969 				}
   8970 
   8971 				for($i = 0; $i < $potentialMatches_len; $i++ ){
   8972 
   8973 					$potentialMatch = &$potentialMatches[$i];
   8974 					$potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
   8975 
   8976 
   8977 					// if we are still valid and have finished, test whether we have elements after and whether these are allowed
   8978 					if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){
   8979 						$potentialMatch['finished'] = true;
   8980 
   8981 						if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){
   8982 							$potentialMatch = null;
   8983 						}
   8984 					}
   8985 
   8986 					// if null we remove, if not, we are still valid, so either push as a valid match or continue
   8987 					if( $potentialMatch ){
   8988 						if( $potentialMatch['finished'] ){
   8989 							$potentialMatch['length'] = $extend->selector->elements_len;
   8990 							$potentialMatch['endPathIndex'] = $haystackSelectorIndex;
   8991 							$potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
   8992 							$potentialMatches = array(); // we don't allow matches to overlap, so start matching again
   8993 							$potentialMatches_len = 0;
   8994 							$matches[] = $potentialMatch;
   8995 						}
   8996 						continue;
   8997 					}
   8998 
   8999 					array_splice($potentialMatches, $i, 1);
   9000 					$potentialMatches_len--;
   9001 					$i--;
   9002 				}
   9003 			}
   9004 		}
   9005 
   9006 		return $matches;
   9007 	}
   9008 
   9009 
   9010 	// Before going through all the nested loops, lets check to see if a match is possible
   9011 	// Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
   9012 	private function HasMatches($extend, $haystackSelectorPath){
   9013 
   9014 		if( !$extend->selector->cacheable ){
   9015 			return true;
   9016 		}
   9017 
   9018 		$first_el = $extend->selector->_oelements[0];
   9019 
   9020 		foreach($haystackSelectorPath as $hackstackSelector){
   9021 			if( !$hackstackSelector->cacheable ){
   9022 				return true;
   9023 			}
   9024 
   9025 			if( in_array($first_el, $hackstackSelector->_oelements) ){
   9026 				return true;
   9027 			}
   9028 		}
   9029 
   9030 		return false;
   9031 	}
   9032 
   9033 
   9034 	/**
   9035 	 * @param integer $hackstackElementIndex
   9036 	 */
   9037 	private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){
   9038 
   9039 
   9040 		if( $potentialMatch['matched'] > 0 ){
   9041 
   9042 			// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
   9043 			// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
   9044 			// what the resulting combinator will be
   9045 			$targetCombinator = $haystackElement->combinator;
   9046 			if( $targetCombinator === '' && $hackstackElementIndex === 0 ){
   9047 				$targetCombinator = ' ';
   9048 			}
   9049 
   9050 			if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){
   9051 				return null;
   9052 			}
   9053 		}
   9054 
   9055 		// if we don't match, null our match to indicate failure
   9056 		if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){
   9057 			return null;
   9058 		}
   9059 
   9060 		$potentialMatch['finished'] = false;
   9061 		$potentialMatch['matched']++;
   9062 
   9063 		return $potentialMatch;
   9064 	}
   9065 
   9066 
   9067 	private function isElementValuesEqual( $elementValue1, $elementValue2 ){
   9068 
   9069 		if( $elementValue1 === $elementValue2 ){
   9070 			return true;
   9071 		}
   9072 
   9073 		if( is_string($elementValue1) || is_string($elementValue2) ) {
   9074 			return false;
   9075 		}
   9076 
   9077 		if( $elementValue1 instanceof Less_Tree_Attribute ){
   9078 			return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
   9079 		}
   9080 
   9081 		$elementValue1 = $elementValue1->value;
   9082 		if( $elementValue1 instanceof Less_Tree_Selector ){
   9083 			return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
   9084 		}
   9085 
   9086 		return false;
   9087 	}
   9088 
   9089 
   9090 	/**
   9091 	 * @param Less_Tree_Selector $elementValue1
   9092 	 */
   9093 	private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){
   9094 
   9095 		$elementValue2 = $elementValue2->value;
   9096 		if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){
   9097 			return false;
   9098 		}
   9099 
   9100 		for( $i = 0; $i < $elementValue1->elements_len; $i++ ){
   9101 
   9102 			if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){
   9103 				if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){
   9104 					return false;
   9105 				}
   9106 			}
   9107 
   9108 			if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){
   9109 				return false;
   9110 			}
   9111 		}
   9112 
   9113 		return true;
   9114 	}
   9115 
   9116 
   9117 	/**
   9118 	 * @param Less_Tree_Attribute $elementValue1
   9119 	 */
   9120 	private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){
   9121 
   9122 		if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){
   9123 			return false;
   9124 		}
   9125 
   9126 		if( !$elementValue1->value || !$elementValue2->value ){
   9127 			if( $elementValue1->value || $elementValue2->value ) {
   9128 				return false;
   9129 			}
   9130 			return true;
   9131 		}
   9132 
   9133 		$elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value );
   9134 		$elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value );
   9135 
   9136 		return $elementValue1 === $elementValue2;
   9137 	}
   9138 
   9139 
   9140 	private function extendSelector($matches, $selectorPath, $replacementSelector){
   9141 
   9142 		//for a set of matches, replace each match with the replacement selector
   9143 
   9144 		$currentSelectorPathIndex = 0;
   9145 		$currentSelectorPathElementIndex = 0;
   9146 		$path = array();
   9147 		$selectorPath_len = count($selectorPath);
   9148 
   9149 		for($matchIndex = 0, $matches_len = count($matches); $matchIndex < $matches_len; $matchIndex++ ){
   9150 
   9151 
   9152 			$match = $matches[$matchIndex];
   9153 			$selector = $selectorPath[ $match['pathIndex'] ];
   9154 
   9155 			$firstElement = new Less_Tree_Element(
   9156 				$match['initialCombinator'],
   9157 				$replacementSelector->elements[0]->value,
   9158 				$replacementSelector->elements[0]->index,
   9159 				$replacementSelector->elements[0]->currentFileInfo
   9160 			);
   9161 
   9162 			if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){
   9163 				$last_path = end($path);
   9164 				$last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
   9165 				$currentSelectorPathElementIndex = 0;
   9166 				$currentSelectorPathIndex++;
   9167 			}
   9168 
   9169 			$newElements = array_merge(
   9170 				array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice
   9171 				, array($firstElement)
   9172 				, array_slice($replacementSelector->elements,1)
   9173 				);
   9174 
   9175 			if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){
   9176 				$last_key = count($path)-1;
   9177 				$path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements);
   9178 			}else{
   9179 				$path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ));
   9180 				$path[] = new Less_Tree_Selector( $newElements );
   9181 			}
   9182 
   9183 			$currentSelectorPathIndex = $match['endPathIndex'];
   9184 			$currentSelectorPathElementIndex = $match['endPathElementIndex'];
   9185 			if( $currentSelectorPathElementIndex >= count($selectorPath[$currentSelectorPathIndex]->elements) ){
   9186 				$currentSelectorPathElementIndex = 0;
   9187 				$currentSelectorPathIndex++;
   9188 			}
   9189 		}
   9190 
   9191 		if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){
   9192 			$last_path = end($path);
   9193 			$last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex));
   9194 			$currentSelectorPathIndex++;
   9195 		}
   9196 
   9197 		$slice_len = $selectorPath_len - $currentSelectorPathIndex;
   9198 		$path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len));
   9199 
   9200 		return $path;
   9201 	}
   9202 
   9203 
   9204 	protected function visitMedia( $mediaNode ){
   9205 		$newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) );
   9206 		$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends);
   9207 	}
   9208 
   9209 	protected function visitMediaOut(){
   9210 		array_pop( $this->allExtendsStack );
   9211 	}
   9212 
   9213 	protected function visitDirective( $directiveNode ){
   9214 		$newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) );
   9215 		$this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends);
   9216 	}
   9217 
   9218 	protected function visitDirectiveOut(){
   9219 		array_pop($this->allExtendsStack);
   9220 	}
   9221 
   9222 } 
   9223 
   9224 /**
   9225  * toCSS Visitor
   9226  *
   9227  * @package Less
   9228  * @subpackage visitor
   9229  */
   9230 class Less_Visitor_toCSS extends Less_VisitorReplacing{
   9231 
   9232 	private $charset;
   9233 
   9234 	public function __construct(){
   9235 		parent::__construct();
   9236 	}
   9237 
   9238 	/**
   9239 	 * @param Less_Tree_Ruleset $root
   9240 	 */
   9241 	public function run( $root ){
   9242 		return $this->visitObj($root);
   9243 	}
   9244 
   9245 	public function visitRule( $ruleNode ){
   9246 		if( $ruleNode->variable ){
   9247 			return array();
   9248 		}
   9249 		return $ruleNode;
   9250 	}
   9251 
   9252 	public function visitMixinDefinition($mixinNode){
   9253 		// mixin definitions do not get eval'd - this means they keep state
   9254 		// so we have to clear that state here so it isn't used if toCSS is called twice
   9255 		$mixinNode->frames = array();
   9256 		return array();
   9257 	}
   9258 
   9259 	public function visitExtend(){
   9260 		return array();
   9261 	}
   9262 
   9263 	public function visitComment( $commentNode ){
   9264 		if( $commentNode->isSilent() ){
   9265 			return array();
   9266 		}
   9267 		return $commentNode;
   9268 	}
   9269 
   9270 	public function visitMedia( $mediaNode, &$visitDeeper ){
   9271 		$mediaNode->accept($this);
   9272 		$visitDeeper = false;
   9273 
   9274 		if( !$mediaNode->rules ){
   9275 			return array();
   9276 		}
   9277 		return $mediaNode;
   9278 	}
   9279 
   9280 	public function visitDirective( $directiveNode ){
   9281 		if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
   9282 			return array();
   9283 		}
   9284 		if( $directiveNode->name === '@charset' ){
   9285 			// Only output the debug info together with subsequent @charset definitions
   9286 			// a comment (or @media statement) before the actual @charset directive would
   9287 			// be considered illegal css as it has to be on the first line
   9288 			if( isset($this->charset) && $this->charset ){
   9289 
   9290 				//if( $directiveNode->debugInfo ){
   9291 				//	$comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
   9292 				//	$comment->debugInfo = $directiveNode->debugInfo;
   9293 				//	return $this->visit($comment);
   9294 				//}
   9295 
   9296 
   9297 				return array();
   9298 			}
   9299 			$this->charset = true;
   9300 		}
   9301 		return $directiveNode;
   9302 	}
   9303 
   9304 	public function checkPropertiesInRoot( $rulesetNode ){
   9305 
   9306 		if( !$rulesetNode->firstRoot ){
   9307 			return;
   9308 		}
   9309 
   9310 		foreach($rulesetNode->rules as $ruleNode){
   9311 			if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
   9312 				$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
   9313 				throw new Less_Exception_Compiler($msg);
   9314 			}
   9315 		}
   9316 	}
   9317 
   9318 
   9319 	public function visitRuleset( $rulesetNode, &$visitDeeper ){
   9320 
   9321 		$visitDeeper = false;
   9322 
   9323 		$this->checkPropertiesInRoot( $rulesetNode );
   9324 
   9325 		if( $rulesetNode->root ){
   9326 			return $this->visitRulesetRoot( $rulesetNode );
   9327 		}
   9328 
   9329 		$rulesets = array();
   9330 		$rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
   9331 
   9332 
   9333 		// Compile rules and rulesets
   9334 		$nodeRuleCnt = count($rulesetNode->rules);
   9335 		for( $i = 0; $i < $nodeRuleCnt; ){
   9336 			$rule = $rulesetNode->rules[$i];
   9337 
   9338 			if( property_exists($rule,'rules') ){
   9339 				// visit because we are moving them out from being a child
   9340 				$rulesets[] = $this->visitObj($rule);
   9341 				array_splice($rulesetNode->rules,$i,1);
   9342 				$nodeRuleCnt--;
   9343 				continue;
   9344 			}
   9345 			$i++;
   9346 		}
   9347 
   9348 
   9349 		// accept the visitor to remove rules and refactor itself
   9350 		// then we can decide now whether we want it or not
   9351 		if( $nodeRuleCnt > 0 ){
   9352 			$rulesetNode->accept($this);
   9353 
   9354 			if( $rulesetNode->rules ){
   9355 
   9356 				if( count($rulesetNode->rules) >  1 ){
   9357 					$this->_mergeRules( $rulesetNode->rules );
   9358 					$this->_removeDuplicateRules( $rulesetNode->rules );
   9359 				}
   9360 
   9361 				// now decide whether we keep the ruleset
   9362 				if( $rulesetNode->paths ){
   9363 					//array_unshift($rulesets, $rulesetNode);
   9364 					array_splice($rulesets,0,0,array($rulesetNode));
   9365 				}
   9366 			}
   9367 
   9368 		}
   9369 
   9370 
   9371 		if( count($rulesets) === 1 ){
   9372 			return $rulesets[0];
   9373 		}
   9374 		return $rulesets;
   9375 	}
   9376 
   9377 
   9378 	/**
   9379 	 * Helper function for visitiRuleset
   9380 	 *
   9381 	 * return array|Less_Tree_Ruleset
   9382 	 */
   9383 	private function visitRulesetRoot( $rulesetNode ){
   9384 		$rulesetNode->accept( $this );
   9385 		if( $rulesetNode->firstRoot || $rulesetNode->rules ){
   9386 			return $rulesetNode;
   9387 		}
   9388 		return array();
   9389 	}
   9390 
   9391 
   9392 	/**
   9393 	 * Helper function for visitRuleset()
   9394 	 *
   9395 	 * @return array
   9396 	 */
   9397 	private function visitRulesetPaths($rulesetNode){
   9398 
   9399 		$paths = array();
   9400 		foreach($rulesetNode->paths as $p){
   9401 			if( $p[0]->elements[0]->combinator === ' ' ){
   9402 				$p[0]->elements[0]->combinator = '';
   9403 			}
   9404 
   9405 			foreach($p as $pi){
   9406 				if( $pi->getIsReferenced() && $pi->getIsOutput() ){
   9407 					$paths[] = $p;
   9408 					break;
   9409 				}
   9410 			}
   9411 		}
   9412 
   9413 		return $paths;
   9414 	}
   9415 
   9416 	protected function _removeDuplicateRules( &$rules ){
   9417 		// remove duplicates
   9418 		$ruleCache = array();
   9419 		for( $i = count($rules)-1; $i >= 0 ; $i-- ){
   9420 			$rule = $rules[$i];
   9421 			if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
   9422 
   9423 				if( !isset($ruleCache[$rule->name]) ){
   9424 					$ruleCache[$rule->name] = $rule;
   9425 				}else{
   9426 					$ruleList =& $ruleCache[$rule->name];
   9427 
   9428 					if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
   9429 						$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
   9430 					}
   9431 
   9432 					$ruleCSS = $rule->toCSS();
   9433 					if( array_search($ruleCSS,$ruleList) !== false ){
   9434 						array_splice($rules,$i,1);
   9435 					}else{
   9436 						$ruleList[] = $ruleCSS;
   9437 					}
   9438 				}
   9439 			}
   9440 		}
   9441 	}
   9442 
   9443 	protected function _mergeRules( &$rules ){
   9444 		$groups = array();
   9445 
   9446 		//obj($rules);
   9447 
   9448 		$rules_len = count($rules);
   9449 		for( $i = 0; $i < $rules_len; $i++ ){
   9450 			$rule = $rules[$i];
   9451 
   9452 			if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
   9453 
   9454 				$key = $rule->name;
   9455 				if( $rule->important ){
   9456 					$key .= ',!';
   9457 				}
   9458 
   9459 				if( !isset($groups[$key]) ){
   9460 					$groups[$key] = array();
   9461 				}else{
   9462 					array_splice($rules, $i--, 1);
   9463 					$rules_len--;
   9464 				}
   9465 
   9466 				$groups[$key][] = $rule;
   9467 			}
   9468 		}
   9469 
   9470 
   9471 		foreach($groups as $parts){
   9472 
   9473 			if( count($parts) > 1 ){
   9474 				$rule = $parts[0];
   9475 				$spacedGroups = array();
   9476 				$lastSpacedGroup = array();
   9477 				$parts_mapped = array();
   9478 				foreach($parts as $p){
   9479 					if( $p->merge === '+' ){
   9480 						if( $lastSpacedGroup ){
   9481 							$spacedGroups[] = self::toExpression($lastSpacedGroup);
   9482 						}
   9483 						$lastSpacedGroup = array();
   9484 					}
   9485 					$lastSpacedGroup[] = $p;
   9486 				}
   9487 
   9488 				$spacedGroups[] = self::toExpression($lastSpacedGroup);
   9489 				$rule->value = self::toValue($spacedGroups);
   9490 			}
   9491 		}
   9492 
   9493 	}
   9494 
   9495 	public static function toExpression($values){
   9496 		$mapped = array();
   9497 		foreach($values as $p){
   9498 			$mapped[] = $p->value;
   9499 		}
   9500 		return new Less_Tree_Expression( $mapped );
   9501 	}
   9502 
   9503 	public static function toValue($values){
   9504 		//return new Less_Tree_Value($values); ??
   9505 
   9506 		$mapped = array();
   9507 		foreach($values as $p){
   9508 			$mapped[] = $p;
   9509 		}
   9510 		return new Less_Tree_Value($mapped);
   9511 	}
   9512 }
   9513 
   9514  
   9515 
   9516 /**
   9517  * Parser Exception
   9518  *
   9519  * @package Less
   9520  * @subpackage exception
   9521  */
   9522 class Less_Exception_Parser extends Exception{
   9523 
   9524 	/**
   9525 	 * The current file
   9526 	 *
   9527 	 * @var Less_ImportedFile
   9528 	 */
   9529 	public $currentFile;
   9530 
   9531 	/**
   9532 	 * The current parser index
   9533 	 *
   9534 	 * @var integer
   9535 	 */
   9536 	public $index;
   9537 
   9538 	protected $input;
   9539 
   9540 	protected $details = array();
   9541 
   9542 
   9543 	/**
   9544 	 * Constructor
   9545 	 *
   9546 	 * @param string $message
   9547 	 * @param Exception $previous Previous exception
   9548 	 * @param integer $index The current parser index
   9549 	 * @param Less_FileInfo|string $currentFile The file
   9550 	 * @param integer $code The exception code
   9551 	 */
   9552 	public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
   9553 
   9554 		if (PHP_VERSION_ID < 50300) {
   9555 			$this->previous = $previous;
   9556 			parent::__construct($message, $code);
   9557 		} else {
   9558 			parent::__construct($message, $code, $previous);
   9559 		}
   9560 
   9561 		$this->currentFile = $currentFile;
   9562 		$this->index = $index;
   9563 
   9564 		$this->genMessage();
   9565 	}
   9566 
   9567 
   9568 	protected function getInput(){
   9569 
   9570 		if( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists($this->currentFile['filename']) ){
   9571 			$this->input = file_get_contents( $this->currentFile['filename'] );
   9572 		}
   9573 	}
   9574 
   9575 
   9576 
   9577 	/**
   9578 	 * Converts the exception to string
   9579 	 *
   9580 	 * @return string
   9581 	 */
   9582 	public function genMessage(){
   9583 
   9584 		if( $this->currentFile && $this->currentFile['filename'] ){
   9585 			$this->message .= ' in '.basename($this->currentFile['filename']);
   9586 		}
   9587 
   9588 		if( $this->index !== null ){
   9589 			$this->getInput();
   9590 			if( $this->input ){
   9591 				$line = self::getLineNumber();
   9592 				$this->message .= ' on line '.$line.', column '.self::getColumn();
   9593 
   9594 				$lines = explode("\n",$this->input);
   9595 
   9596 				$count = count($lines);
   9597 				$start_line = max(0, $line-3);
   9598 				$last_line = min($count, $start_line+6);
   9599 				$num_len = strlen($last_line);
   9600 				for( $i = $start_line; $i < $last_line; $i++ ){
   9601 					$this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i];
   9602 				}
   9603 			}
   9604 		}
   9605 
   9606 	}
   9607 
   9608 	/**
   9609 	 * Returns the line number the error was encountered
   9610 	 *
   9611 	 * @return integer
   9612 	 */
   9613 	public function getLineNumber(){
   9614 		if( $this->index ){
   9615 			// https://bugs.php.net/bug.php?id=49790
   9616 			if (ini_get("mbstring.func_overload")) {
   9617 				return substr_count(substr($this->input, 0, $this->index), "\n") + 1;
   9618 			} else {
   9619 				return substr_count($this->input, "\n", 0, $this->index) + 1;
   9620 			}
   9621 		}
   9622 		return 1;
   9623 	}
   9624 
   9625 
   9626 	/**
   9627 	 * Returns the column the error was encountered
   9628 	 *
   9629 	 * @return integer
   9630 	 */
   9631 	public function getColumn(){
   9632 
   9633 		$part = substr($this->input, 0, $this->index);
   9634 		$pos = strrpos($part,"\n");
   9635 		return $this->index - $pos;
   9636 	}
   9637 
   9638 }
   9639  
   9640 
   9641 /**
   9642  * Chunk Exception
   9643  *
   9644  * @package Less
   9645  * @subpackage exception
   9646  */
   9647 class Less_Exception_Chunk extends Less_Exception_Parser{
   9648 
   9649 
   9650 	protected $parserCurrentIndex = 0;
   9651 
   9652 	protected $emitFrom = 0;
   9653 
   9654 	protected $input_len;
   9655 
   9656 
   9657 	/**
   9658 	 * Constructor
   9659 	 *
   9660 	 * @param string $input
   9661 	 * @param Exception $previous Previous exception
   9662 	 * @param integer $index The current parser index
   9663 	 * @param Less_FileInfo|string $currentFile The file
   9664 	 * @param integer $code The exception code
   9665 	 */
   9666 	public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){
   9667 
   9668 		$this->message = 'ParseError: Unexpected input'; //default message
   9669 
   9670 		$this->index = $index;
   9671 
   9672 		$this->currentFile = $currentFile;
   9673 
   9674 		$this->input = $input;
   9675 		$this->input_len = strlen($input);
   9676 
   9677 		$this->Chunks();
   9678 		$this->genMessage();
   9679 	}
   9680 
   9681 
   9682 	/**
   9683 	 * See less.js chunks()
   9684 	 * We don't actually need the chunks
   9685 	 *
   9686 	 */
   9687 	protected function Chunks(){
   9688 		$level = 0;
   9689 		$parenLevel = 0;
   9690 		$lastMultiCommentEndBrace = null;
   9691 		$lastOpening = null;
   9692 		$lastMultiComment = null;
   9693 		$lastParen = null;
   9694 
   9695 		for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){
   9696 			$cc = $this->CharCode($this->parserCurrentIndex);
   9697 			if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) {
   9698 				// a-z or whitespace
   9699 				continue;
   9700 			}
   9701 
   9702 			switch ($cc) {
   9703 
   9704 				// (
   9705 				case 40:
   9706 					$parenLevel++;
   9707 					$lastParen = $this->parserCurrentIndex;
   9708 					break;
   9709 
   9710 				// )
   9711 				case 41:
   9712 					$parenLevel--;
   9713 					if( $parenLevel < 0 ){
   9714 						return $this->fail("missing opening `(`");
   9715 					}
   9716 					break;
   9717 
   9718 				// ;
   9719 				case 59:
   9720 					//if (!$parenLevel) { $this->emitChunk();	}
   9721 					break;
   9722 
   9723 				// {
   9724 				case 123:
   9725 					$level++;
   9726 					$lastOpening = $this->parserCurrentIndex;
   9727 					break;
   9728 
   9729 				// }
   9730 				case 125:
   9731 					$level--;
   9732 					if( $level < 0 ){
   9733 						return $this->fail("missing opening `{`");
   9734 
   9735 					}
   9736 					//if (!$level && !$parenLevel) { $this->emitChunk(); }
   9737 					break;
   9738 				// \
   9739 				case 92:
   9740 					if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; break; }
   9741 					return $this->fail("unescaped `\\`");
   9742 
   9743 				// ", ' and `
   9744 				case 34:
   9745 				case 39:
   9746 				case 96:
   9747 					$matched = 0;
   9748 					$currentChunkStartIndex = $this->parserCurrentIndex;
   9749 					for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
   9750 						$cc2 = $this->CharCode($this->parserCurrentIndex);
   9751 						if ($cc2 > 96) { continue; }
   9752 						if ($cc2 == $cc) { $matched = 1; break; }
   9753 						if ($cc2 == 92) {        // \
   9754 							if ($this->parserCurrentIndex == $this->input_len - 1) {
   9755 								return $this->fail("unescaped `\\`");
   9756 							}
   9757 							$this->parserCurrentIndex++;
   9758 						}
   9759 					}
   9760 					if ($matched) { break; }
   9761 					return $this->fail("unmatched `" . chr($cc) . "`", $currentChunkStartIndex);
   9762 
   9763 				// /, check for comment
   9764 				case 47:
   9765 					if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { break; }
   9766 					$cc2 = $this->CharCode($this->parserCurrentIndex+1);
   9767 					if ($cc2 == 47) {
   9768 						// //, find lnfeed
   9769 						for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) {
   9770 							$cc2 = $this->CharCode($this->parserCurrentIndex);
   9771 							if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; }
   9772 						}
   9773 					} else if ($cc2 == 42) {
   9774 						// /*, find */
   9775 						$lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
   9776 						for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) {
   9777 							$cc2 = $this->CharCode($this->parserCurrentIndex);
   9778 							if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; }
   9779 							if ($cc2 != 42) { continue; }
   9780 							if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; }
   9781 						}
   9782 						if ($this->parserCurrentIndex == $this->input_len - 1) {
   9783 							return $this->fail("missing closing `*/`", $currentChunkStartIndex);
   9784 						}
   9785 					}
   9786 					break;
   9787 
   9788 				// *, check for unmatched */
   9789 				case 42:
   9790 					if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) {
   9791 						return $this->fail("unmatched `/*`");
   9792 					}
   9793 					break;
   9794 			}
   9795 		}
   9796 
   9797 		if( $level !== 0 ){
   9798 			if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){
   9799 				return $this->fail("missing closing `}` or `*/`", $lastOpening);
   9800 			} else {
   9801 				return $this->fail("missing closing `}`", $lastOpening);
   9802 			}
   9803 		} else if ( $parenLevel !== 0 ){
   9804 			return $this->fail("missing closing `)`", $lastParen);
   9805 		}
   9806 
   9807 
   9808 		//chunk didn't fail
   9809 
   9810 
   9811 		//$this->emitChunk(true);
   9812 	}
   9813 
   9814 	public function CharCode($pos){
   9815 		return ord($this->input[$pos]);
   9816 	}
   9817 
   9818 
   9819 	public function fail( $msg, $index = null ){
   9820 
   9821 		if( !$index ){
   9822 			$this->index = $this->parserCurrentIndex;
   9823 		}else{
   9824 			$this->index = $index;
   9825 		}
   9826 		$this->message = 'ParseError: '.$msg;
   9827 	}
   9828 
   9829 
   9830 	/*
   9831 	function emitChunk( $force = false ){
   9832 		$len = $this->parserCurrentIndex - $this->emitFrom;
   9833 		if ((($len < 512) && !$force) || !$len) {
   9834 			return;
   9835 		}
   9836 		$chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
   9837 		$this->emitFrom = $this->parserCurrentIndex + 1;
   9838 	}
   9839 	*/
   9840 
   9841 }
   9842  
   9843 
   9844 /**
   9845  * Compiler Exception
   9846  *
   9847  * @package Less
   9848  * @subpackage exception
   9849  */
   9850 class Less_Exception_Compiler extends Less_Exception_Parser{
   9851 
   9852 } 
   9853 
   9854 /**
   9855  * Parser output with source map
   9856  *
   9857  * @package Less
   9858  * @subpackage Output
   9859  */
   9860 class Less_Output_Mapped extends Less_Output {
   9861 
   9862 	/**
   9863 	 * The source map generator
   9864 	 *
   9865 	 * @var Less_SourceMap_Generator
   9866 	 */
   9867 	protected $generator;
   9868 
   9869 	/**
   9870 	 * Current line
   9871 	 *
   9872 	 * @var integer
   9873 	 */
   9874 	protected $lineNumber = 0;
   9875 
   9876 	/**
   9877 	 * Current column
   9878 	 *
   9879 	 * @var integer
   9880 	 */
   9881 	protected $column = 0;
   9882 
   9883 	/**
   9884 	 * Array of contents map (file and its content)
   9885 	 *
   9886 	 * @var array
   9887 	 */
   9888 	protected $contentsMap = array();
   9889 
   9890 	/**
   9891 	 * Constructor
   9892 	 *
   9893 	 * @param array $contentsMap Array of filename to contents map
   9894 	 * @param Less_SourceMap_Generator $generator
   9895 	 */
   9896 	public function __construct(array $contentsMap, $generator){
   9897 		$this->contentsMap = $contentsMap;
   9898 		$this->generator = $generator;
   9899 	}
   9900 
   9901 	/**
   9902 	 * Adds a chunk to the stack
   9903 	 * The $index for less.php may be different from less.js since less.php does not chunkify inputs
   9904 	 *
   9905 	 * @param string $chunk
   9906 	 * @param string $fileInfo
   9907 	 * @param integer $index
   9908 	 * @param mixed $mapLines
   9909 	 */
   9910 	public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){
   9911 
   9912 		//ignore adding empty strings
   9913 		if( $chunk === '' ){
   9914 			return;
   9915 		}
   9916 
   9917 
   9918 		$sourceLines = array();
   9919 		$sourceColumns = ' ';
   9920 
   9921 
   9922 		if( $fileInfo ){
   9923 
   9924 			$url = $fileInfo['currentUri'];
   9925 
   9926 			if( isset($this->contentsMap[$url]) ){
   9927 				$inputSource = substr($this->contentsMap[$url], 0, $index);
   9928 				$sourceLines = explode("\n", $inputSource);
   9929 				$sourceColumns = end($sourceLines);
   9930 			}else{
   9931 				throw new Exception('Filename '.$url.' not in contentsMap');
   9932 			}
   9933 
   9934 		}
   9935 
   9936 		$lines = explode("\n", $chunk);
   9937 		$columns = end($lines);
   9938 
   9939 		if($fileInfo){
   9940 
   9941 			if(!$mapLines){
   9942 				$this->generator->addMapping(
   9943 						$this->lineNumber + 1,					// generated_line
   9944 						$this->column,							// generated_column
   9945 						count($sourceLines),					// original_line
   9946 						strlen($sourceColumns),					// original_column
   9947 						$fileInfo
   9948 				);
   9949 			}else{
   9950 				for($i = 0, $count = count($lines); $i < $count; $i++){
   9951 					$this->generator->addMapping(
   9952 						$this->lineNumber + $i + 1,				// generated_line
   9953 						$i === 0 ? $this->column : 0,			// generated_column
   9954 						count($sourceLines) + $i,				// original_line
   9955 						$i === 0 ? strlen($sourceColumns) : 0, 	// original_column
   9956 						$fileInfo
   9957 					);
   9958 				}
   9959 			}
   9960 		}
   9961 
   9962 		if(count($lines) === 1){
   9963 			$this->column += strlen($columns);
   9964 		}else{
   9965 			$this->lineNumber += count($lines) - 1;
   9966 			$this->column = strlen($columns);
   9967 		}
   9968 
   9969 		// add only chunk
   9970 		parent::add($chunk);
   9971 	}
   9972 
   9973 } 
   9974 
   9975 /**
   9976  * Encode / Decode Base64 VLQ.
   9977  *
   9978  * @package Less
   9979  * @subpackage SourceMap
   9980  */
   9981 class Less_SourceMap_Base64VLQ {
   9982 
   9983 	/**
   9984 	 * Shift
   9985 	 *
   9986 	 * @var integer
   9987 	 */
   9988 	private $shift = 5;
   9989 
   9990 	/**
   9991 	 * Mask
   9992 	 *
   9993 	 * @var integer
   9994 	 */
   9995 	private $mask = 0x1F; // == (1 << shift) == 0b00011111
   9996 
   9997 	/**
   9998 	 * Continuation bit
   9999 	 *
  10000 	 * @var integer
  10001 	 */
  10002 	private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
  10003 
  10004 	/**
  10005 	 * Char to integer map
  10006 	 *
  10007 	 * @var array
  10008 	 */
  10009 	private $charToIntMap = array(
  10010 		'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
  10011 		'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
  10012 		'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
  10013 		'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
  10014 		'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
  10015 		'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
  10016 		'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
  10017 		'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
  10018 		5 => 57,	6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
  10019 	);
  10020 
  10021 	/**
  10022 	 * Integer to char map
  10023 	 *
  10024 	 * @var array
  10025 	 */
  10026 	private $intToCharMap = array(
  10027 		0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
  10028 		7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
  10029 		14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
  10030 		21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
  10031 		28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
  10032 		35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
  10033 		42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
  10034 		49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
  10035 		56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
  10036 		63 => '/',
  10037 	);
  10038 
  10039 	/**
  10040 	 * Constructor
  10041 	 */
  10042 	public function __construct(){
  10043 		// I leave it here for future reference
  10044 		// foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
  10045 		// {
  10046 		//	 $this->charToIntMap[$char] = $i;
  10047 		//	 $this->intToCharMap[$i] = $char;
  10048 		// }
  10049 	}
  10050 
  10051 	/**
  10052 	 * Convert from a two-complement value to a value where the sign bit is
  10053 	 * is placed in the least significant bit.	For example, as decimals:
  10054 	 *	 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
  10055 	 *	 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
  10056 	 * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
  10057 	 * even on a 64 bit machine.
  10058 	 * @param string $aValue
  10059 	 */
  10060 	public function toVLQSigned($aValue){
  10061 		return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
  10062 	}
  10063 
  10064 	/**
  10065 	 * Convert to a two-complement value from a value where the sign bit is
  10066 	 * is placed in the least significant bit. For example, as decimals:
  10067 	 *	 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
  10068 	 *	 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
  10069 	 * We assume that the value was generated with a 32 bit machine in mind.
  10070 	 * Hence
  10071 	 *	 1 becomes -2147483648
  10072 	 * even on a 64 bit machine.
  10073 	 * @param integer $aValue
  10074 	 */
  10075 	public function fromVLQSigned($aValue){
  10076 		return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
  10077 	}
  10078 
  10079 	/**
  10080 	 * Return the base 64 VLQ encoded value.
  10081 	 *
  10082 	 * @param string $aValue The value to encode
  10083 	 * @return string The encoded value
  10084 	 */
  10085 	public function encode($aValue){
  10086 		$encoded = '';
  10087 		$vlq = $this->toVLQSigned($aValue);
  10088 		do
  10089 		{
  10090 			$digit = $vlq & $this->mask;
  10091 			$vlq = $this->zeroFill($vlq, $this->shift);
  10092 			if($vlq > 0){
  10093 				$digit |= $this->continuationBit;
  10094 			}
  10095 			$encoded .= $this->base64Encode($digit);
  10096 		} while($vlq > 0);
  10097 
  10098 		return $encoded;
  10099 	}
  10100 
  10101 	/**
  10102 	 * Return the value decoded from base 64 VLQ.
  10103 	 *
  10104 	 * @param string $encoded The encoded value to decode
  10105 	 * @return integer The decoded value
  10106 	 */
  10107 	public function decode($encoded){
  10108 		$vlq = 0;
  10109 		$i = 0;
  10110 		do
  10111 		{
  10112 			$digit = $this->base64Decode($encoded[$i]);
  10113 			$vlq |= ($digit & $this->mask) << ($i * $this->shift);
  10114 			$i++;
  10115 		} while($digit & $this->continuationBit);
  10116 
  10117 		return $this->fromVLQSigned($vlq);
  10118 	}
  10119 
  10120 	/**
  10121 	 * Right shift with zero fill.
  10122 	 *
  10123 	 * @param integer $a number to shift
  10124 	 * @param integer $b number of bits to shift
  10125 	 * @return integer
  10126 	 */
  10127 	public function zeroFill($a, $b){
  10128 		return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
  10129 	}
  10130 
  10131 	/**
  10132 	 * Encode single 6-bit digit as base64.
  10133 	 *
  10134 	 * @param integer $number
  10135 	 * @return string
  10136 	 * @throws Exception If the number is invalid
  10137 	 */
  10138 	public function base64Encode($number){
  10139 		if($number < 0 || $number > 63){
  10140 			throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
  10141 		}
  10142 		return $this->intToCharMap[$number];
  10143 	}
  10144 
  10145 	/**
  10146 	 * Decode single 6-bit digit from base64
  10147 	 *
  10148 	 * @param string $char
  10149 	 * @return number
  10150 	 * @throws Exception If the number is invalid
  10151 	 */
  10152 	public function base64Decode($char){
  10153 		if(!array_key_exists($char, $this->charToIntMap)){
  10154 			throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
  10155 		}
  10156 		return $this->charToIntMap[$char];
  10157 	}
  10158 
  10159 }
  10160  
  10161 
  10162 /**
  10163  * Source map generator
  10164  *
  10165  * @package Less
  10166  * @subpackage Output
  10167  */
  10168 class Less_SourceMap_Generator extends Less_Configurable {
  10169 
  10170 	/**
  10171 	 * What version of source map does the generator generate?
  10172 	 */
  10173 	const VERSION = 3;
  10174 
  10175 	/**
  10176 	 * Array of default options
  10177 	 *
  10178 	 * @var array
  10179 	 */
  10180 	protected $defaultOptions = array(
  10181 			// an optional source root, useful for relocating source files
  10182 			// on a server or removing repeated values in the 'sources' entry.
  10183 			// This value is prepended to the individual entries in the 'source' field.
  10184 			'sourceRoot'			=> '',
  10185 
  10186 			// an optional name of the generated code that this source map is associated with.
  10187 			'sourceMapFilename'		=> null,
  10188 
  10189 			// url of the map
  10190 			'sourceMapURL'			=> null,
  10191 
  10192 			// absolute path to a file to write the map to
  10193 			'sourceMapWriteTo'		=> null,
  10194 
  10195 			// output source contents?
  10196 			'outputSourceFiles'		=> false,
  10197 
  10198 			// base path for filename normalization
  10199 			'sourceMapRootpath'		=> '',
  10200 
  10201 			// base path for filename normalization
  10202 			'sourceMapBasepath'   => ''
  10203 	);
  10204 
  10205 	/**
  10206 	 * The base64 VLQ encoder
  10207 	 *
  10208 	 * @var Less_SourceMap_Base64VLQ
  10209 	 */
  10210 	protected $encoder;
  10211 
  10212 	/**
  10213 	 * Array of mappings
  10214 	 *
  10215 	 * @var array
  10216 	 */
  10217 	protected $mappings = array();
  10218 
  10219 	/**
  10220 	 * The root node
  10221 	 *
  10222 	 * @var Less_Tree_Ruleset
  10223 	 */
  10224 	protected $root;
  10225 
  10226 	/**
  10227 	 * Array of contents map
  10228 	 *
  10229 	 * @var array
  10230 	 */
  10231 	protected $contentsMap = array();
  10232 
  10233 	/**
  10234 	 * File to content map
  10235 	 *
  10236 	 * @var array
  10237 	 */
  10238 	protected $sources = array();
  10239 	protected $source_keys = array();
  10240 
  10241 	/**
  10242 	 * Constructor
  10243 	 *
  10244 	 * @param Less_Tree_Ruleset $root The root node
  10245 	 * @param array $options Array of options
  10246 	 */
  10247 	public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){
  10248 		$this->root = $root;
  10249 		$this->contentsMap = $contentsMap;
  10250 		$this->encoder = new Less_SourceMap_Base64VLQ();
  10251 
  10252 		$this->SetOptions($options);
  10253 		
  10254 		$this->options['sourceMapRootpath'] = $this->fixWindowsPath($this->options['sourceMapRootpath'], true);
  10255 		$this->options['sourceMapBasepath'] = $this->fixWindowsPath($this->options['sourceMapBasepath'], true);
  10256 	}
  10257 
  10258 	/**
  10259 	 * Generates the CSS
  10260 	 *
  10261 	 * @return string
  10262 	 */
  10263 	public function generateCSS(){
  10264 		$output = new Less_Output_Mapped($this->contentsMap, $this);
  10265 
  10266 		// catch the output
  10267 		$this->root->genCSS($output);
  10268 
  10269 
  10270 		$sourceMapUrl				= $this->getOption('sourceMapURL');
  10271 		$sourceMapFilename			= $this->getOption('sourceMapFilename');
  10272 		$sourceMapContent			= $this->generateJson();
  10273 		$sourceMapWriteTo			= $this->getOption('sourceMapWriteTo');
  10274 
  10275 		if( !$sourceMapUrl && $sourceMapFilename ){
  10276 			$sourceMapUrl = $this->normalizeFilename($sourceMapFilename);
  10277 		}
  10278 
  10279 		// write map to a file
  10280 		if( $sourceMapWriteTo ){
  10281 			$this->saveMap($sourceMapWriteTo, $sourceMapContent);
  10282 		}
  10283 
  10284 		// inline the map
  10285 		if( !$sourceMapUrl ){
  10286 			$sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent));
  10287 		}
  10288 
  10289 		if( $sourceMapUrl ){
  10290 			$output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) );
  10291 		}
  10292 
  10293 		return $output->toString();
  10294 	}
  10295 
  10296 	/**
  10297 	 * Saves the source map to a file
  10298 	 *
  10299 	 * @param string $file The absolute path to a file
  10300 	 * @param string $content The content to write
  10301 	 * @throws Exception If the file could not be saved
  10302 	 */
  10303 	protected function saveMap($file, $content){
  10304 		$dir = dirname($file);
  10305 		// directory does not exist
  10306 		if( !is_dir($dir) ){
  10307 			// FIXME: create the dir automatically?
  10308 			throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
  10309 		}
  10310 		// FIXME: proper saving, with dir write check!
  10311 		if(file_put_contents($file, $content) === false){
  10312 			throw new Exception(sprintf('Cannot save the source map to "%s"', $file));
  10313 		}
  10314 		return true;
  10315 	}
  10316 
  10317 	/**
  10318 	 * Normalizes the filename
  10319 	 *
  10320 	 * @param string $filename
  10321 	 * @return string
  10322 	 */
  10323 	protected function normalizeFilename($filename){
  10324 
  10325 		$filename = $this->fixWindowsPath($filename);
  10326 
  10327 		$rootpath = $this->getOption('sourceMapRootpath');
  10328 		$basePath = $this->getOption('sourceMapBasepath');
  10329 
  10330 		// "Trim" the 'sourceMapBasepath' from the output filename.
  10331 		//if (strpos($filename, strval($basePath)) === 0) {
  10332 		//	$filename = substr($filename, strlen($basePath));
  10333 		//}
  10334 
  10335 		// Remove extra leading path separators.
  10336 		if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){
  10337 			$filename = substr($filename, 1);
  10338 		}
  10339 
  10340 		return $rootpath . $filename;
  10341 	}
  10342 
  10343 	/**
  10344 	 * Adds a mapping
  10345 	 *
  10346 	 * @param integer $generatedLine The line number in generated file
  10347 	 * @param integer $generatedColumn The column number in generated file
  10348 	 * @param integer $originalLine The line number in original file
  10349 	 * @param integer $originalColumn The column number in original file
  10350 	 * @param string $sourceFile The original source file
  10351 	 */
  10352 	public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){
  10353 
  10354 		$this->mappings[] = array(
  10355 			'generated_line' => $generatedLine,
  10356 			'generated_column' => $generatedColumn,
  10357 			'original_line' => $originalLine,
  10358 			'original_column' => $originalColumn,
  10359 			'source_file' => $fileInfo['currentUri']
  10360 		);
  10361 
  10362 		$this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
  10363 	}
  10364 
  10365 
  10366 	/**
  10367 	 * Generates the JSON source map
  10368 	 *
  10369 	 * @return string
  10370 	 * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
  10371 	 */
  10372 	protected function generateJson(){
  10373 
  10374 		$sourceMap = array();
  10375 		$mappings = $this->generateMappings();
  10376 
  10377 		// File version (always the first entry in the object) and must be a positive integer.
  10378 		$sourceMap['version'] = self::VERSION;
  10379 
  10380 
  10381 		// An optional name of the generated code that this source map is associated with.
  10382 		$file = $this->getOption('sourceMapFilename');
  10383 		if( $file ){
  10384 			$sourceMap['file'] = $file;
  10385 		}
  10386 
  10387 
  10388 		// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry.	This value is prepended to the individual entries in the 'source' field.
  10389 		$root = $this->getOption('sourceRoot');
  10390 		if( $root ){
  10391 			$sourceMap['sourceRoot'] = $root;
  10392 		}
  10393 
  10394 
  10395 		// A list of original sources used by the 'mappings' entry.
  10396 		$sourceMap['sources'] = array();
  10397 		foreach($this->sources as $source_uri => $source_filename){
  10398 			$sourceMap['sources'][] = $this->normalizeFilename($source_filename);
  10399 		}
  10400 
  10401 
  10402 		// A list of symbol names used by the 'mappings' entry.
  10403 		$sourceMap['names'] = array();
  10404 
  10405 		// A string with the encoded mapping data.
  10406 		$sourceMap['mappings'] = $mappings;
  10407 
  10408 		if( $this->getOption('outputSourceFiles') ){
  10409 			// An optional list of source content, useful when the 'source' can't be hosted.
  10410 			// The contents are listed in the same order as the sources above.
  10411 			// 'null' may be used if some original sources should be retrieved by name.
  10412 			$sourceMap['sourcesContent'] = $this->getSourcesContent();
  10413 		}
  10414 
  10415 		// less.js compat fixes
  10416 		if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){
  10417 			unset($sourceMap['sourceRoot']);
  10418 		}
  10419 
  10420 		return json_encode($sourceMap);
  10421 	}
  10422 
  10423 	/**
  10424 	 * Returns the sources contents
  10425 	 *
  10426 	 * @return array|null
  10427 	 */
  10428 	protected function getSourcesContent(){
  10429 		if(empty($this->sources)){
  10430 			return;
  10431 		}
  10432 		$content = array();
  10433 		foreach($this->sources as $sourceFile){
  10434 			$content[] = file_get_contents($sourceFile);
  10435 		}
  10436 		return $content;
  10437 	}
  10438 
  10439 	/**
  10440 	 * Generates the mappings string
  10441 	 *
  10442 	 * @return string
  10443 	 */
  10444 	public function generateMappings(){
  10445 
  10446 		if( !count($this->mappings) ){
  10447 			return '';
  10448 		}
  10449 
  10450 		$this->source_keys = array_flip(array_keys($this->sources));
  10451 
  10452 
  10453 		// group mappings by generated line number.
  10454 		$groupedMap = $groupedMapEncoded = array();
  10455 		foreach($this->mappings as $m){
  10456 			$groupedMap[$m['generated_line']][] = $m;
  10457 		}
  10458 		ksort($groupedMap);
  10459 
  10460 		$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
  10461 
  10462 		foreach($groupedMap as $lineNumber => $line_map){
  10463 			while(++$lastGeneratedLine < $lineNumber){
  10464 				$groupedMapEncoded[] = ';';
  10465 			}
  10466 
  10467 			$lineMapEncoded = array();
  10468 			$lastGeneratedColumn = 0;
  10469 
  10470 			foreach($line_map as $m){
  10471 				$mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
  10472 				$lastGeneratedColumn = $m['generated_column'];
  10473 
  10474 				// find the index
  10475 				if( $m['source_file'] ){
  10476 					$index = $this->findFileIndex($m['source_file']);
  10477 					if( $index !== false ){
  10478 						$mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
  10479 						$lastOriginalIndex = $index;
  10480 
  10481 						// lines are stored 0-based in SourceMap spec version 3
  10482 						$mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
  10483 						$lastOriginalLine = $m['original_line'] - 1;
  10484 
  10485 						$mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
  10486 						$lastOriginalColumn = $m['original_column'];
  10487 					}
  10488 				}
  10489 
  10490 				$lineMapEncoded[] = $mapEncoded;
  10491 			}
  10492 
  10493 			$groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
  10494 		}
  10495 
  10496 		return rtrim(implode($groupedMapEncoded), ';');
  10497 	}
  10498 
  10499 	/**
  10500 	 * Finds the index for the filename
  10501 	 *
  10502 	 * @param string $filename
  10503 	 * @return integer|false
  10504 	 */
  10505 	protected function findFileIndex($filename){
  10506 		return $this->source_keys[$filename];
  10507 	}
  10508 
  10509 	/**
  10510 	 * fix windows paths
  10511 	 * @param  string $path
  10512 	 * @return string      
  10513 	 */
  10514 	public function fixWindowsPath($path, $addEndSlash = false){
  10515 		$slash = ($addEndSlash) ? '/' : '';
  10516 		if( !empty($path) ){
  10517 			$path = str_replace('\\', '/', $path);
  10518 			$path = rtrim($path,'/') . $slash;
  10519 		}
  10520 
  10521 		return $path;
  10522 	}
  10523 
  10524 }