Seditio Source
Root |
./othercms/PHPFusion 9.10.20/includes/classes/PHPFusion/Atom/lib/Less/Parser.php
<?php

require_once( dirname(__FILE__).'/Cache.php');

/**
 * Class for parsing and compiling less files into css
 *
 * @package Less
 * @subpackage parser
 *
 */
class Less_Parser{


   
/**
     * Default parser options
     */
   
public static $default_options = array(
       
'compress'                => false,            // option - whether to compress
       
'strictUnits'            => false,            // whether units need to evaluate correctly
       
'strictMath'            => false,            // whether math has to be within parenthesis
       
'relativeUrls'            => true,            // option - whether to adjust URL's to be relative
       
'urlArgs'                => '',                // whether to add args into url tokens
       
'numPrecision'            => 8,

       
'import_dirs'            => array(),
       
'import_callback'        => null,
       
'cache_dir'                => null,
       
'cache_method'            => 'php',             // false, 'serialize', 'php', 'var_export', 'callback';
       
'cache_callback_get'    => null,
       
'cache_callback_set'    => null,

       
'sourceMap'                => false,            // whether to output a source map
       
'sourceMapBasepath'        => null,
       
'sourceMapWriteTo'        => null,
       
'sourceMapURL'            => null,

       
'indentation'             => '  ',

       
'plugins'                => array(),

    );

    public static
$options = array();


    private
$input;                    // Less input string
   
private $input_len;                // input string length
   
private $pos;                    // current index in `input`
   
private $saveStack = array();    // holds state for backtracking
   
private $furthest;
    private
$mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding

    /**
     * @var Less_Environment
     */
   
private $env;

    protected
$rules = array();

    private static
$imports = array();

    public static
$has_extends = false;

    public static
$next_id = 0;

   
/**
     * Filename to contents of all parsed the files
     *
     * @var array
     */
   
public static $contentsMap = array();


   
/**
     * @param Less_Environment|array|null $env
     */
   
public function __construct( $env = null ){

       
// Top parser on an import tree must be sure there is one "env"
        // which will then be passed around by reference.
       
if( $env instanceof Less_Environment ){
           
$this->env = $env;
        }else{
           
$this->SetOptions(Less_Parser::$default_options);
           
$this->Reset( $env );
        }

       
// mbstring.func_overload > 1 bugfix
        // The encoding value must be set for each source file,
        // therefore, to conserve resources and improve the speed of this design is taken here
       
if (ini_get('mbstring.func_overload')) {
           
$this->mb_internal_encoding = ini_get('mbstring.internal_encoding');
            @
ini_set('mbstring.internal_encoding', 'ascii');
        }

    }


   
/**
     * Reset the parser state completely
     *
     */
   
public function Reset( $options = null ){
       
$this->rules = array();
       
self::$imports = array();
       
self::$has_extends = false;
       
self::$imports = array();
       
self::$contentsMap = array();

       
$this->env = new Less_Environment($options);

       
//set new options
       
if( is_array($options) ){
           
$this->SetOptions(Less_Parser::$default_options);
           
$this->SetOptions($options);
        }

       
$this->env->Init();
    }

   
/**
     * Set one or more compiler options
     *  options: import_dirs, cache_dir, cache_method
     *
     */
   
public function SetOptions( $options ){
        foreach(
$options as $option => $value){
           
$this->SetOption($option,$value);
        }
    }

   
/**
     * Set one compiler option
     *
     */
   
public function SetOption($option,$value){

        switch(
$option){

            case
'import_dirs':
               
$this->SetImportDirs($value);
            return;

            case
'cache_dir':
                if(
is_string($value) ){
                   
Less_Cache::SetCacheDir($value);
                   
Less_Cache::CheckCacheDir();
                }
            return;
        }

       
Less_Parser::$options[$option] = $value;
    }

   
/**
     * Registers a new custom function
     *
     * @param  string   $name     function name
     * @param  callable $callback callback
     */
   
public function registerFunction($name, $callback) {
       
$this->env->functions[$name] = $callback;
    }

   
/**
     * Removed an already registered function
     *
     * @param  string $name function name
     */
   
public function unregisterFunction($name) {
        if( isset(
$this->env->functions[$name]) )
            unset(
$this->env->functions[$name]);
    }


   
/**
     * Get the current css buffer
     *
     * @return string
     */
   
public function getCss(){

       
$precision = ini_get('precision');
        @
ini_set('precision',16);
       
$locale = setlocale(LC_NUMERIC, 0);
       
setlocale(LC_NUMERIC, "C");

        try {

             
$root = new Less_Tree_Ruleset(array(), $this->rules );
           
$root->root = true;
           
$root->firstRoot = true;


           
$this->PreVisitors($root);

           
self::$has_extends = false;
           
$evaldRoot = $root->compile($this->env);



           
$this->PostVisitors($evaldRoot);

            if(
Less_Parser::$options['sourceMap'] ){
               
$generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
               
// will also save file
                // FIXME: should happen somewhere else?
               
$css = $generator->generateCSS();
            }else{
               
$css = $evaldRoot->toCSS();
            }

            if(
Less_Parser::$options['compress'] ){
               
$css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
            }

        } catch (
Exception $exc) {
           
// Intentional fall-through so we can reset environment
       
}

       
//reset php settings
       
@ini_set('precision',$precision);
       
setlocale(LC_NUMERIC, $locale);

       
// If you previously defined $this->mb_internal_encoding
        // is required to return the encoding as it was before
       
if ($this->mb_internal_encoding != '') {
            @
ini_set("mbstring.internal_encoding", $this->mb_internal_encoding);
           
$this->mb_internal_encoding = '';
        }

       
// Rethrow exception after we handled resetting the environment
       
if (!empty($exc)) {
            throw
$exc;
        }

        return
$css;
    }

    public function
findValueOf($varName)
    {
        foreach(
$this->rules as $rule){
            if(isset(
$rule->variable) && ($rule->variable == true) && (str_replace("@","",$rule->name) == $varName)){
                return
$this->getVariableValue($rule);
            }
        }
        return
null;
    }

   
/**
     *
     * this function gets the private rules variable and returns an array of the found variables
     * it uses a helper method getVariableValue() that contains the logic ot fetch the value from the rule object
     *
     * @return array
     */
   
public function getVariables()
    {
       
$variables = array();

       
$not_variable_type = array(
           
'Comment',   // this include less comments ( // ) and css comments (/* */)
           
'Import',    // do not search variables in included files @import
           
'Ruleset',   // selectors (.someclass, #someid, …)
           
'Operation', //
       
);

       
// @TODO run compilation if not runned yet
       
foreach ($this->rules as $key => $rule) {
            if (
in_array($rule->type, $not_variable_type)) {
                continue;
            }

           
// Note: it seems rule->type is always Rule when variable = true
           
if ($rule->type == 'Rule' && $rule->variable) {
               
$variables[$rule->name] = $this->getVariableValue($rule);
            } else {
                if (
$rule->type == 'Comment') {
                   
$variables[] = $this->getVariableValue($rule);
                }
            }
        }
        return
$variables;
    }

    public function
findVarByName($var_name)
    {
        foreach(
$this->rules as $rule){
            if(isset(
$rule->variable) && ($rule->variable == true)){
                if(
$rule->name == $var_name){
                    return
$this->getVariableValue($rule);
                }
            }
        }
        return
null;
    }

   
/**
     *
     * This method gets the value of the less variable from the rules object.
     * Since the objects vary here we add the logic for extracting the css/less value.
     *
     * @param $var
     *
     * @return bool|string
     */
   
private function getVariableValue($var)
    {
        if (!
is_a($var, 'Less_Tree')) {
            throw new
Exception('var is not a Less_Tree object');
        }

        switch (
$var->type) {
            case
'Color':
                return
$this->rgb2html($var->rgb);
            case
'Unit':
                return
$var->value. $var->unit->numerator[0];
            case
'Variable':
                return
$this->findVarByName($var->name);
            case
'Keyword':
                return
$var->value;
            case
'Rule':
                return
$this->getVariableValue($var->value);
            case
'Value':
               
$value = '';
                foreach (
$var->value as $sub_value) {
                   
$value .= $this->getVariableValue($sub_value).' ';
                }
                return
$value;
            case
'Quoted':
                return
$var->quote.$var->value.$var->quote;
            case
'Dimension':
               
$value = $var->value;
                if (
$var->unit && $var->unit->numerator) {
                   
$value .= $var->unit->numerator[0];
                }
                return
$value;
            case
'Expression':
               
$value = "";
                foreach(
$var->value as $item) {
                   
$value .= $this->getVariableValue($item)." ";
                }
                return
$value;
            case
'Operation':
                throw new
Exception('getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()');
            case
'Comment':
            case
'Import':
            case
'Ruleset':
            default:
                throw new
Exception("type missing in switch/case getVariableValue for ".$var->type);
        }
        return
false;
    }

    private function
rgb2html($r, $g=-1, $b=-1)
    {
        if (
is_array($r) && sizeof($r) == 3)
            list(
$r, $g, $b) = $r;

       
$r = intval($r); $g = intval($g);
       
$b = intval($b);

       
$r = dechex($r<0?0:($r>255?255:$r));
       
$g = dechex($g<0?0:($g>255?255:$g));
       
$b = dechex($b<0?0:($b>255?255:$b));

       
$color = (strlen($r) < 2?'0':'').$r;
       
$color .= (strlen($g) < 2?'0':'').$g;
       
$color .= (strlen($b) < 2?'0':'').$b;
        return
'#'.$color;
    }

   
/**
     * Run pre-compile visitors
     *
     */
   
private function PreVisitors($root){

        if(
Less_Parser::$options['plugins'] ){
            foreach(
Less_Parser::$options['plugins'] as $plugin){
                if( !empty(
$plugin->isPreEvalVisitor) ){
                   
$plugin->run($root);
                }
            }
        }
    }


   
/**
     * Run post-compile visitors
     *
     */
   
private function PostVisitors($evaldRoot){

       
$visitors = array();
       
$visitors[] = new Less_Visitor_joinSelector();
        if(
self::$has_extends ){
           
$visitors[] = new Less_Visitor_processExtends();
        }
       
$visitors[] = new Less_Visitor_toCSS();


        if(
Less_Parser::$options['plugins'] ){
            foreach(
Less_Parser::$options['plugins'] as $plugin){
                if(
property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
                    continue;
                }

                if(
property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
                   
array_unshift( $visitors, $plugin);
                }else{
                   
$visitors[] = $plugin;
                }
            }
        }


        for(
$i = 0; $i < count($visitors); $i++ ){
           
$visitors[$i]->run($evaldRoot);
        }

    }


   
/**
     * Parse a Less string into css
     *
     * @param string $str The string to convert
     * @param string $uri_root The url of the file
     * @return Less_Tree_Ruleset|Less_Parser
     */
   
public function parse( $str, $file_uri = null ){

        if( !
$file_uri ){
           
$uri_root = '';
           
$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
        }else{
           
$file_uri = self::WinPath($file_uri);
           
$filename = $file_uri;
           
$uri_root = dirname($file_uri);
        }

       
$previousFileInfo = $this->env->currentFileInfo;
       
$uri_root = self::WinPath($uri_root);
       
$this->SetFileInfo($filename, $uri_root);

       
$this->input = $str;
       
$this->_parse();

        if(
$previousFileInfo ){
           
$this->env->currentFileInfo = $previousFileInfo;
        }

        return
$this;
    }


   
/**
     * Parse a Less string from a given file
     *
     * @throws Less_Exception_Parser
     * @param string $filename The file to parse
     * @param string $uri_root The url of the file
     * @param bool $returnRoot Indicates whether the return value should be a css string a root node
     * @return Less_Tree_Ruleset|Less_Parser
     */
   
public function parseFile( $filename, $uri_root = '', $returnRoot = false){

        if( !
file_exists($filename) ){
           
$this->Error(sprintf('File `%s` not found.', $filename));
        }


       
// fix uri_root?
        // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
       
if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
           
$uri_root = dirname($uri_root);
        }


       
$previousFileInfo = $this->env->currentFileInfo;


        if(
$filename ){
           
$filename = self::AbsPath($filename, true);
        }
       
$uri_root = self::WinPath($uri_root);

       
$this->SetFileInfo($filename, $uri_root);

       
self::AddParsedFile($filename);

        if(
$returnRoot ){
           
$rules = $this->GetRules( $filename );
           
$return = new Less_Tree_Ruleset(array(), $rules );
        }else{
           
$this->_parse( $filename );
           
$return = $this;
        }

        if(
$previousFileInfo ){
           
$this->env->currentFileInfo = $previousFileInfo;
        }

        return
$return;
    }


   
/**
     * Allows a user to set variables values
     * @param array $vars
     * @return Less_Parser
     */
   
public function ModifyVars( $vars ){

       
$this->input = Less_Parser::serializeVars( $vars );
       
$this->_parse();

        return
$this;
    }


   
/**
     * @param string $filename
     */
   
public function SetFileInfo( $filename, $uri_root = ''){

       
$filename = Less_Environment::normalizePath($filename);
       
$dirname = preg_replace('/[^\/\\\\]*$/','',$filename);

        if( !empty(
$uri_root) ){
           
$uri_root = rtrim($uri_root,'/').'/';
        }

       
$currentFileInfo = array();

       
//entry info
       
if( isset($this->env->currentFileInfo) ){
           
$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
           
$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
           
$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];

        }else{
           
$currentFileInfo['entryPath'] = $dirname;
           
$currentFileInfo['entryUri'] = $uri_root;
           
$currentFileInfo['rootpath'] = $dirname;
        }

       
$currentFileInfo['currentDirectory'] = $dirname;
       
$currentFileInfo['currentUri'] = $uri_root.basename($filename);
       
$currentFileInfo['filename'] = $filename;
       
$currentFileInfo['uri_root'] = $uri_root;


       
//inherit reference
       
if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
           
$currentFileInfo['reference'] = true;
        }

       
$this->env->currentFileInfo = $currentFileInfo;
    }


   
/**
     * @deprecated 1.5.1.2
     *
     */
   
public function SetCacheDir( $dir ){

        if( !
file_exists($dir) ){
            if(
mkdir($dir) ){
                return
true;
            }
            throw new
Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);

        }elseif( !
is_dir($dir) ){
            throw new
Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);

        }elseif( !
is_writable($dir) ){
            throw new
Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);

        }else{
           
$dir = self::WinPath($dir);
           
Less_Cache::$cache_dir = rtrim($dir,'/').'/';
            return
true;
        }
    }


   
/**
     * Set a list of directories or callbacks the parser should use for determining import paths
     *
     * @param array $dirs
     */
   
public function SetImportDirs( $dirs ){
       
Less_Parser::$options['import_dirs'] = array();

        foreach(
$dirs as $path => $uri_root){

           
$path = self::WinPath($path);
            if( !empty(
$path) ){
               
$path = rtrim($path,'/').'/';
            }

            if ( !
is_callable($uri_root) ){
               
$uri_root = self::WinPath($uri_root);
                if( !empty(
$uri_root) ){
                   
$uri_root = rtrim($uri_root,'/').'/';
                }
            }

           
Less_Parser::$options['import_dirs'][$path] = $uri_root;
        }
    }

   
/**
     * @param string $file_path
     */
   
private function _parse( $file_path = null ){
       
$this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
    }


   
/**
     * Return the results of parsePrimary for $file_path
     * Use cache and save cached results if possible
     *
     * @param string|null $file_path
     */
   
private function GetRules( $file_path ){

       
$this->SetInput($file_path);

       
$cache_file = $this->CacheFile( $file_path );
        if(
$cache_file ){
            if(
Less_Parser::$options['cache_method'] == 'callback' ){
                if(
is_callable(Less_Parser::$options['cache_callback_get']) ){
                   
$cache = call_user_func_array(
                       
Less_Parser::$options['cache_callback_get'],
                        array(
$this, $file_path, $cache_file)
                    );

                    if(
$cache ){
                       
$this->UnsetInput();
                        return
$cache;
                    }
                }

            }elseif(
file_exists($cache_file) ){
                switch(
Less_Parser::$options['cache_method']){

                   
// Using serialize
                    // Faster but uses more memory
                   
case 'serialize':
                       
$cache = unserialize(file_get_contents($cache_file));
                        if(
$cache ){
                           
touch($cache_file);
                           
$this->UnsetInput();
                            return
$cache;
                        }
                        break;


                       
// Using generated php code
                   
case 'var_export':
                    case
'php':
                       
$this->UnsetInput();
                        return include(
$cache_file);
                }
            }
        }

       
$rules = $this->parsePrimary();

        if(
$this->pos < $this->input_len ){
            throw new
Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
        }

       
$this->UnsetInput();


       
//save the cache
       
if( $cache_file ){
            if(
Less_Parser::$options['cache_method'] == 'callback' ){
                if(
is_callable(Less_Parser::$options['cache_callback_set']) ){
                   
call_user_func_array(
                       
Less_Parser::$options['cache_callback_set'],
                        array(
$this, $file_path, $cache_file, $rules)
                    );
                }

            }else{
               
//msg('write cache file');
               
switch(Less_Parser::$options['cache_method']){
                    case
'serialize':
                       
file_put_contents( $cache_file, serialize($rules) );
                        break;
                    case
'php':
                       
file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
                        break;
                    case
'var_export':
                       
//Requires __set_state()
                       
file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
                        break;
                }

               
Less_Cache::CleanCache();
            }
        }

        return
$rules;
    }


   
/**
     * Set up the input buffer
     *
     */
   
public function SetInput( $file_path ){

        if(
$file_path ){
           
$this->input = file_get_contents( $file_path );
        }

       
$this->pos = $this->furthest = 0;

       
// Remove potential UTF Byte Order Mark
       
$this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
       
$this->input_len = strlen($this->input);


        if(
Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
           
$uri = $this->env->currentFileInfo['currentUri'];
           
Less_Parser::$contentsMap[$uri] = $this->input;
        }

    }


   
/**
     * Free up some memory
     *
     */
   
public function UnsetInput(){
        unset(
$this->input, $this->pos, $this->input_len, $this->furthest);
       
$this->saveStack = array();
    }


    public function
CacheFile( $file_path ){

        if(
$file_path && $this->CacheEnabled() ){

           
$env = get_object_vars($this->env);
            unset(
$env['frames']);

           
$parts = array();
           
$parts[] = $file_path;
           
$parts[] = filesize( $file_path );
           
$parts[] = filemtime( $file_path );
           
$parts[] = $env;
           
$parts[] = Less_Version::cache_version;
           
$parts[] = Less_Parser::$options['cache_method'];
            return
Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache';
        }
    }


    static function
AddParsedFile($file){
       
self::$imports[] = $file;
    }

    static function
AllParsedFiles(){
        return
self::$imports;
    }

   
/**
     * @param string $file
     */
   
static function FileParsed($file){
        return
in_array($file,self::$imports);
    }


    function
save() {
       
$this->saveStack[] = $this->pos;
    }

    private function
restore() {
       
$this->pos = array_pop($this->saveStack);
    }

    private function
forget(){
       
array_pop($this->saveStack);
    }

   
/**
     * Determine if the character at the specified offset from the current position is a white space.
     *
     * @param int $offset
     *
     * @return bool
     */
   
private function isWhitespace($offset = 0) {
        return
strpos(" \t\n\r\v\f", $this->input[$this->pos + $offset]) !== false;
    }

   
/**
     * Parse from a token, regexp or string, and move forward if match
     *
     * @param array $toks
     * @return array
     */
   
private function match($toks){

       
// The match is confirmed, add the match length to `this::pos`,
        // and consume any extra white-space characters (' ' || '\n')
        // which come after that. The reason for this is that LeSS's
        // grammar is mostly white-space insensitive.
        //

       
foreach($toks as $tok){

           
$char = $tok[0];

            if(
$char === '/' ){
               
$match = $this->MatchReg($tok);

                if(
$match ){
                    return
count($match) === 1 ? $match[0] : $match;
                }

            }elseif(
$char === '#' ){
               
$match = $this->MatchChar($tok[1]);

            }else{
               
// Non-terminal, match using a function call
               
$match = $this->$tok();

            }

            if(
$match ){
                return
$match;
            }
        }
    }

   
/**
     * @param string[] $toks
     *
     * @return string
     */
   
private function MatchFuncs($toks){

        if(
$this->pos < $this->input_len ){
            foreach(
$toks as $tok){
               
$match = $this->$tok();
                if(
$match ){
                    return
$match;
                }
            }
        }

    }

   
// Match a single character in the input,
   
private function MatchChar($tok){
        if( (
$this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
           
$this->skipWhitespace(1);
            return
$tok;
        }
    }

   
// Match a regexp from the current start point
   
private function MatchReg($tok){

        if(
preg_match($tok, $this->input, $match, 0, $this->pos) ){
           
$this->skipWhitespace(strlen($match[0]));
            return
$match;
        }
    }


   
/**
     * Same as match(), but don't change the state of the parser,
     * just return the match.
     *
     * @param string $tok
     * @return integer
     */
   
public function PeekReg($tok){
        return
preg_match($tok, $this->input, $match, 0, $this->pos);
    }

   
/**
     * @param string $tok
     */
   
public function PeekChar($tok){
       
//return ($this->input[$this->pos] === $tok );
       
return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
    }


   
/**
     * @param integer $length
     */
   
public function skipWhitespace($length){

       
$this->pos += $length;

        for(;
$this->pos < $this->input_len; $this->pos++ ){
           
$c = $this->input[$this->pos];

            if( (
$c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
                break;
            }
        }
    }


   
/**
     * @param string $tok
     * @param string|null $msg
     */
   
public function expect($tok, $msg = NULL) {
       
$result = $this->match( array($tok) );
        if (!
$result) {
           
$this->Error( $msg    ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
        } else {
            return
$result;
        }
    }

   
/**
     * @param string $tok
     */
   
public function expectChar($tok, $msg = null ){
       
$result = $this->MatchChar($tok);
        if( !
$result ){
           
$msg = $msg ? $msg : "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'";
           
$this->Error( $msg );
        }else{
            return
$result;
        }
    }

   
//
    // Here in, the parsing rules/functions
    //
    // The basic structure of the syntax tree generated is as follows:
    //
    //   Ruleset ->  Rule -> Value -> Expression -> Entity
    //
    // Here's some LESS code:
    //
    //    .class {
    //      color: #fff;
    //      border: 1px solid #000;
    //      width: @w + 4px;
    //      > .child {...}
    //    }
    //
    // And here's what the parse tree might look like:
    //
    //     Ruleset (Selector '.class', [
    //         Rule ("color",  Value ([Expression [Color #fff]]))
    //         Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
    //         Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
    //         Ruleset (Selector [Element '>', '.child'], [...])
    //     ])
    //
    //  In general, most rules will try to parse a token with the `$()` function, and if the return
    //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
    //  first, before parsing, that's when we use `peek()`.
    //

    //
    // The `primary` rule is the *entry* and *exit* point of the parser.
    // The rules here can appear at any level of the parse tree.
    //
    // The recursive nature of the grammar is an interplay between the `block`
    // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
    // as represented by this simplified grammar:
    //
    //     primary  →  (ruleset | rule)+
    //     ruleset  →  selector+ block
    //     block    →  '{' primary '}'
    //
    // Only at one point is the primary rule not called from the
    // block rule: at the root level.
    //
   
private function parsePrimary(){
       
$root = array();

        while(
true ){

            if(
$this->pos >= $this->input_len ){
                break;
            }

           
$node = $this->parseExtend(true);
            if(
$node ){
               
$root = array_merge($root,$node);
                continue;
            }

           
//$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
           
$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));

            if(
$node ){
               
$root[] = $node;
            }elseif( !
$this->MatchReg('/\\G[\s\n;]+/') ){
                break;
            }

            if(
$this->PeekChar('}') ){
                break;
            }
        }

        return
$root;
    }



   
// We create a Comment node for CSS comments `/* */`,
    // but keep the LeSS comments `//` silent, by just skipping
    // over them.
   
private function parseComment(){

        if(
$this->input[$this->pos] !== '/' ){
            return;
        }

        if(
$this->input[$this->pos+1] === '/' ){
           
$match = $this->MatchReg('/\\G\/\/.*/');
            return
$this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
        }

       
//$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
       
$comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
       
if( $comment ){
            return
$this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
        }
    }

    private function
parseComments(){
       
$comments = array();

        while(
$this->pos < $this->input_len ){
           
$comment = $this->parseComment();
            if( !
$comment ){
                break;
            }

           
$comments[] = $comment;
        }

        return
$comments;
    }



   
//
    // A string, which supports escaping " and '
    //
    //     "milky way" 'he\'s the one!'
    //
   
private function parseEntitiesQuoted() {
       
$j = $this->pos;
       
$e = false;
       
$index = $this->pos;

        if(
$this->input[$this->pos] === '~' ){
           
$j++;
           
$e = true; // Escaped strings
       
}

       
$char = $this->input[$j];
        if(
$char !== '"' && $char !== "'" ){
            return;
        }

        if (
$e) {
           
$this->MatchChar('~');
        }


       
$matched = $this->MatchQuoted($char, $j+1);
        if(
$matched === false ){
            return;
        }

       
$quoted = $char.$matched.$char;
        return
$this->NewObj5('Less_Tree_Quoted',array($quoted, $matched, $e, $index, $this->env->currentFileInfo) );
    }


   
/**
     * When PCRE JIT is enabled in php, regular expressions don't work for matching quoted strings
     *
     *    $regex    = '/\\G\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/';
     *    $regex    = '/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"/';
     *
     */
   
private function MatchQuoted($quote_char, $i){

       
$matched = '';
        while(
$i < $this->input_len ){
           
$c = $this->input[$i];

           
//escaped character
           
if( $c === '\\' ){
               
$matched .= $c . $this->input[$i+1];
               
$i += 2;
                continue;
            }

            if(
$c === $quote_char ){
               
$this->pos = $i+1;
               
$this->skipWhitespace(0);
                return
$matched;
            }

            if(
$c === "\r" || $c === "\n" ){
                return
false;
            }

           
$i++;
           
$matched .= $c;
        }

        return
false;
    }


   
//
    // A catch-all word, such as:
    //
    //     black border-collapse
    //
   
private function parseEntitiesKeyword(){

       
//$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
       
$k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
        if(
$k ){
           
$k = $k[0];
           
$color = $this->fromKeyword($k);
            if(
$color ){
                return
$color;
            }
            return
$this->NewObj1('Less_Tree_Keyword',$k);
        }
    }

   
// duplicate of Less_Tree_Color::FromKeyword
   
private function FromKeyword( $keyword ){
       
$keyword = strtolower($keyword);

        if(
Less_Colors::hasOwnProperty($keyword) ){
           
// detect named color
           
return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
        }

        if(
$keyword === 'transparent' ){
            return
$this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
        }
    }

   
//
    // A function call
    //
    //     rgb(255, 0, 255)
    //
    // We also try to catch IE's `alpha()`, but let the `alpha` parser
    // deal with the details.
    //
    // The arguments are parsed with the `entities.arguments` parser.
    //
   
private function parseEntitiesCall(){
       
$index = $this->pos;

        if( !
preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
            return;
        }
       
$name = $name[1];
       
$nameLC = strtolower($name);

        if (
$nameLC === 'url') {
            return
null;
        }

       
$this->pos += strlen($name);

        if(
$nameLC === 'alpha' ){
           
$alpha_ret = $this->parseAlpha();
            if(
$alpha_ret ){
                return
$alpha_ret;
            }
        }

       
$this->MatchChar('('); // Parse the '(' and consume whitespace.

       
$args = $this->parseEntitiesArguments();

        if( !
$this->MatchChar(')') ){
            return;
        }

        if (
$name) {
            return
$this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
        }
    }

   
/**
     * Parse a list of arguments
     *
     * @return array
     */
   
private function parseEntitiesArguments(){

       
$args = array();
        while(
true ){
           
$arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
            if( !
$arg ){
                break;
            }

           
$args[] = $arg;
            if( !
$this->MatchChar(',') ){
                break;
            }
        }
        return
$args;
    }

    private function
parseEntitiesLiteral(){
        return
$this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
    }

   
// Assignments are argument entities for calls.
    // They are present in ie filter properties as shown below.
    //
    //     filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
    //
   
private function parseEntitiesAssignment() {

       
$key = $this->MatchReg('/\\G\w+(?=\s?=)/');
        if( !
$key ){
            return;
        }

        if( !
$this->MatchChar('=') ){
            return;
        }

       
$value = $this->parseEntity();
        if(
$value ){
            return
$this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
        }
    }

   
//
    // Parse url() tokens
    //
    // We use a specific rule for urls, because they don't really behave like
    // standard function calls. The difference is that the argument doesn't have
    // to be enclosed within a string, so it can't be parsed as an Expression.
    //
   
private function parseEntitiesUrl(){


        if(
$this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
            return;
        }

       
$value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
        if( !
$value ){
           
$value = '';
        }


       
$this->expectChar(')');


        if( isset(
$value->value) || $value instanceof Less_Tree_Variable ){
            return
$this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
        }

        return
$this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
    }


   
//
    // A Variable entity, such as `@fink`, in
    //
    //     width: @fink + 2px
    //
    // We use a different parser for variable definitions,
    // see `parsers.variable`.
    //
   
private function parseEntitiesVariable(){
       
$index = $this->pos;
        if (
$this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
            return
$this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
        }
    }


   
// A variable entity using the protective {} e.g. @{var}
   
private function parseEntitiesVariableCurly() {
       
$index = $this->pos;

        if(
$this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
            return
$this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
        }
    }

   
//
    // A Hexadecimal color
    //
    //     #4F3C2F
    //
    // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
    //
   
private function parseEntitiesColor(){
        if (
$this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
            return
$this->NewObj1('Less_Tree_Color',$rgb[1]);
        }
    }

   
//
    // A Dimension, that is, a number and a unit
    //
    //     0.5em 95%
    //
   
private function parseEntitiesDimension(){

       
$c = @ord($this->input[$this->pos]);

       
//Is the first char of the dimension 0-9, '.', '+' or '-'
       
if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
            return;
        }

       
$value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
        if(
$value ){

            if( isset(
$value[2]) ){
                return
$this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
            }
            return
$this->NewObj1('Less_Tree_Dimension',$value[1]);
        }
    }


   
//
    // A unicode descriptor, as is used in unicode-range
    //
    // U+0?? or U+00A1-00A9
    //
   
function parseUnicodeDescriptor() {
       
$ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
        if(
$ud ){
            return
$this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
        }
    }


   
//
    // JavaScript code to be evaluated
    //
    //     `window.location.href`
    //
   
private function parseEntitiesJavascript(){
       
$e = false;
       
$j = $this->pos;
        if(
$this->input[$j] === '~' ){
           
$j++;
           
$e = true;
        }
        if(
$this->input[$j] !== '`' ){
            return;
        }
        if(
$e ){
           
$this->MatchChar('~');
        }
       
$str = $this->MatchReg('/\\G`([^`]*)`/');
        if(
$str ){
            return
$this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
        }
    }


   
//
    // The variable part of a variable definition. Used in the `rule` parser
    //
    //     @fink:
    //
   
private function parseVariable(){
        if (
$this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
            return
$name[1];
        }
    }


   
//
    // The variable part of a variable definition. Used in the `rule` parser
    //
    // @fink();
    //
   
private function parseRulesetCall(){

        if(
$this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
            return
$this->NewObj1('Less_Tree_RulesetCall', $name[1] );
        }
    }


   
//
    // extend syntax - used to extend selectors
    //
   
function parseExtend($isRule = false){

       
$index = $this->pos;
       
$extendList = array();


        if( !
$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }

        do{
           
$option = null;
           
$elements = array();
            while(
true ){
               
$option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
                if(
$option ){ break; }
               
$e = $this->parseElement();
                if( !
$e ){ break; }
               
$elements[] = $e;
            }

            if(
$option ){
               
$option = $option[1];
            }

           
$extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));

        }while(
$this->MatchChar(",") );

       
$this->expect('/\\G\)/');

        if(
$isRule ){
           
$this->expect('/\\G;/');
        }

        return
$extendList;
    }


   
//
    // A Mixin call, with an optional argument list
    //
    //     #mixins > .square(#fff);
    //     .rounded(4px, black);
    //     .button;
    //
    // The `while` loop is there because mixins can be
    // namespaced, but we only support the child and descendant
    // selector for now.
    //
   
private function parseMixinCall(){

       
$char = $this->input[$this->pos];
        if(
$char !== '.' && $char !== '#' ){
            return;
        }

       
$index = $this->pos;
       
$this->save(); // stop us absorbing part of an invalid selector

       
$elements = $this->parseMixinCallElements();

        if(
$elements ){

            if(
$this->MatchChar('(') ){
               
$returned = $this->parseMixinArgs(true);
               
$args = $returned['args'];
               
$this->expectChar(')');
            }else{
               
$args = array();
            }

           
$important = $this->parseImportant();

            if(
$this->parseEnd() ){
               
$this->forget();
                return
$this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
            }
        }

       
$this->restore();
    }


    private function
parseMixinCallElements(){
       
$elements = array();
       
$c = null;

        while(
true ){
           
$elemIndex = $this->pos;
           
$e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
            if( !
$e ){
                break;
            }
           
$elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
           
$c = $this->MatchChar('>');
        }

        return
$elements;
    }



   
/**
     * @param boolean $isCall
     */
   
private function parseMixinArgs( $isCall ){
       
$expressions = array();
       
$argsSemiColon = array();
       
$isSemiColonSeperated = null;
       
$argsComma = array();
       
$expressionContainsNamed = null;
       
$name = null;
       
$returner = array('args'=>array(), 'variadic'=> false);

       
$this->save();

        while(
true ){
            if(
$isCall ){
               
$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
            } else {
               
$this->parseComments();
                if(
$this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
                   
$returner['variadic'] = true;
                    if(
$this->MatchChar(";") && !$isSemiColonSeperated ){
                       
$isSemiColonSeperated = true;
                    }

                    if(
$isSemiColonSeperated ){
                       
$argsSemiColon[] = array('variadic'=>true);
                    }else{
                       
$argsComma[] = array('variadic'=>true);
                    }
                    break;
                }
               
$arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
            }

            if( !
$arg ){
                break;
            }


           
$nameLoop = null;
            if(
$arg instanceof Less_Tree_Expression ){
               
$arg->throwAwayComments();
            }
           
$value = $arg;
           
$val = null;

            if(
$isCall ){
               
// Variable
               
if( property_exists($arg,'value') && count($arg->value) == 1 ){
                   
$val = $arg->value[0];
                }
            } else {
               
$val = $arg;
            }


            if(
$val instanceof Less_Tree_Variable ){

                if(
$this->MatchChar(':') ){
                    if(
$expressions ){
                        if(
$isSemiColonSeperated ){
                           
$this->Error('Cannot mix ; and , as delimiter types');
                        }
                       
$expressionContainsNamed = true;
                    }

                   
// we do not support setting a ruleset as a default variable - it doesn't make sense
                    // However if we do want to add it, there is nothing blocking it, just don't error
                    // and remove isCall dependency below
                   
$value = null;
                    if(
$isCall ){
                       
$value = $this->parseDetachedRuleset();
                    }
                    if( !
$value ){
                       
$value = $this->parseExpression();
                    }

                    if( !
$value ){
                        if(
$isCall ){
                           
$this->Error('could not understand value for named argument');
                        } else {
                           
$this->restore();
                           
$returner['args'] = array();
                            return
$returner;
                        }
                    }

                   
$nameLoop = ($name = $val->name);
                }elseif( !
$isCall && $this->MatchReg('/\\G\.{3}/') ){
                   
$returner['variadic'] = true;
                    if(
$this->MatchChar(";") && !$isSemiColonSeperated ){
                       
$isSemiColonSeperated = true;
                    }
                    if(
$isSemiColonSeperated ){
                       
$argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
                    }else{
                       
$argsComma[] = array('name'=> $arg->name, 'variadic' => true);
                    }
                    break;
                }elseif( !
$isCall ){
                   
$name = $nameLoop = $val->name;
                   
$value = null;
                }
            }

            if(
$value ){
               
$expressions[] = $value;
            }

           
$argsComma[] = array('name'=>$nameLoop, 'value'=>$value );

            if(
$this->MatchChar(',') ){
                continue;
            }

            if(
$this->MatchChar(';') || $isSemiColonSeperated ){

                if(
$expressionContainsNamed ){
                   
$this->Error('Cannot mix ; and , as delimiter types');
                }

               
$isSemiColonSeperated = true;

                if(
count($expressions) > 1 ){
                   
$value = $this->NewObj1('Less_Tree_Value', $expressions);
                }
               
$argsSemiColon[] = array('name'=>$name, 'value'=>$value );

               
$name = null;
               
$expressions = array();
               
$expressionContainsNamed = false;
            }
        }

       
$this->forget();
       
$returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
        return
$returner;
    }



   
//
    // A Mixin definition, with a list of parameters
    //
    //     .rounded (@radius: 2px, @color) {
    //        ...
    //     }
    //
    // Until we have a finer grained state-machine, we have to
    // do a look-ahead, to make sure we don't have a mixin call.
    // See the `rule` function for more information.
    //
    // We start by matching `.rounded (`, and then proceed on to
    // the argument list, which has optional default values.
    // We store the parameters in `params`, with a `value` key,
    // if there is a value, such as in the case of `@radius`.
    //
    // Once we've got our params list, and a closing `)`, we parse
    // the `{...}` block.
    //
   
private function parseMixinDefinition(){
       
$cond = null;

       
$char = $this->input[$this->pos];
        if( (
$char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
            return;
        }

       
$this->save();

       
$match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
        if(
$match ){
           
$name = $match[1];

           
$argInfo = $this->parseMixinArgs( false );
           
$params = $argInfo['args'];
           
$variadic = $argInfo['variadic'];


           
// .mixincall("@{a}");
            // looks a bit like a mixin definition..
            // also
            // .mixincall(@a: {rule: set;});
            // so we have to be nice and restore
           
if( !$this->MatchChar(')') ){
               
$this->furthest = $this->pos;
               
$this->restore();
                return;
            }


           
$this->parseComments();

            if (
$this->MatchReg('/\\Gwhen/')) { // Guard
               
$cond = $this->expect('parseConditions', 'Expected conditions');
            }

           
$ruleset = $this->parseBlock();

            if(
is_array($ruleset) ){
               
$this->forget();
                return
$this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
            }

           
$this->restore();
        }else{
           
$this->forget();
        }
    }

   
//
    // Entities are the smallest recognized token,
    // and can be found inside a rule's value.
    //
   
private function parseEntity(){

        return
$this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
    }

   
//
    // A Rule terminator. Note that we use `peek()` to check for '}',
    // because the `block` rule will be expecting it, but we still need to make sure
    // it's there, if ';' was omitted.
    //
   
private function parseEnd(){
        return
$this->MatchChar(';') || $this->PeekChar('}');
    }

   
//
    // IE's alpha function
    //
    //     alpha(opacity=88)
    //
   
private function parseAlpha(){

        if ( !
$this->MatchReg('/\\G\(opacity=/i')) {
            return;
        }

       
$value = $this->MatchReg('/\\G[0-9]+/');
        if(
$value ){
           
$value = $value[0];
        }else{
           
$value = $this->parseEntitiesVariable();
            if( !
$value ){
                return;
            }
        }

       
$this->expectChar(')');
        return
$this->NewObj1('Less_Tree_Alpha',$value);
    }


   
//
    // A Selector Element
    //
    //     div
    //     + h1
    //     #socks
    //     input[type="text"]
    //
    // Elements are the building blocks for Selectors,
    // they are made out of a `Combinator` (see combinator rule),
    // and an element name, such as a tag a class, or `*`.
    //
   
private function parseElement(){
       
$c = $this->parseCombinator();
       
$index = $this->pos;

       
$e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
           
'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );

        if(
is_null($e) ){
           
$this->save();
            if(
$this->MatchChar('(') ){
                if( (
$v = $this->parseSelector()) && $this->MatchChar(')') ){
                   
$e = $this->NewObj1('Less_Tree_Paren',$v);
                   
$this->forget();
                }else{
                   
$this->restore();
                }
            }else{
               
$this->forget();
            }
        }

        if( !
is_null($e) ){
            return
$this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
        }
    }

   
//
    // Combinators combine elements together, in a Selector.
    //
    // Because our parser isn't white-space sensitive, special care
    // has to be taken, when parsing the descendant combinator, ` `,
    // as it's an empty space. We have to check the previous character
    // in the input, to see if it's a ` ` character.
    //
   
private function parseCombinator(){
        if(
$this->pos < $this->input_len ){
           
$c = $this->input[$this->pos];
            if (
$c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){

               
$this->pos++;
                if(
$this->input[$this->pos] === '^' ){
                   
$c = '^^';
                   
$this->pos++;
                }

               
$this->skipWhitespace(0);

                return
$c;
            }

            if(
$this->pos > 0 && $this->isWhitespace(-1) ){
                return
' ';
            }
        }
    }

   
//
    // A CSS selector (see selector below)
    // with less extensions e.g. the ability to extend and guard
    //
   
private function parseLessSelector(){
        return
$this->parseSelector(true);
    }

   
//
    // A CSS Selector
    //
    //     .class > div + h1
    //     li a:hover
    //
    // Selectors are made out of one or more Elements, see above.
    //
   
private function parseSelector( $isLess = false ){
       
$elements = array();
       
$extendList = array();
       
$condition = null;
       
$when = false;
       
$extend = false;
       
$e = null;
       
$c = null;
       
$index = $this->pos;

        while( (
$isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
            if(
$when ){
               
$condition = $this->expect('parseConditions', 'expected condition');
            }elseif(
$condition ){
               
//error("CSS guard can only be used at the end of selector");
           
}elseif( $extend ){
               
$extendList = array_merge($extendList,$extend);
            }else{
               
//if( count($extendList) ){
                //error("Extend can only be used at the end of selector");
                //}
               
if( $this->pos < $this->input_len ){
                   
$c = $this->input[ $this->pos ];
                }
               
$elements[] = $e;
               
$e = null;
            }

            if(
$c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
        }

        if(
$elements ){
            return
$this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
        }
        if(
$extendList ) {
           
$this->Error('Extend must be used to extend a selector, it cannot be used on its own');
        }
    }

    private function
parseTag(){
        return (
$tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
    }

    private function
parseAttribute(){

       
$val = null;

        if( !
$this->MatchChar('[') ){
            return;
        }

       
$key = $this->parseEntitiesVariableCurly();
        if( !
$key ){
           
$key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
        }

       
$op = $this->MatchReg('/\\G[|~*$^]?=/');
        if(
$op ){
           
$val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
        }

       
$this->expectChar(']');

        return
$this->NewObj3('Less_Tree_Attribute',array( $key, $op === null ? null : $op[0], $val));
    }

   
//
    // The `block` rule is used by `ruleset` and `mixin.definition`.
    // It's a wrapper around the `primary` rule, with added `{}`.
    //
   
private function parseBlock(){
        if(
$this->MatchChar('{') ){
           
$content = $this->parsePrimary();
            if(
$this->MatchChar('}') ){
                return
$content;
            }
        }
    }

    private function
parseBlockRuleset(){
       
$block = $this->parseBlock();

        if(
$block ){
           
$block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
        }

        return
$block;
    }

    private function
parseDetachedRuleset(){
       
$blockRuleset = $this->parseBlockRuleset();
        if(
$blockRuleset ){
            return
$this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
        }
    }

   
//
    // div, .class, body > p {...}
    //
   
private function parseRuleset(){
       
$selectors = array();

       
$this->save();

        while(
true ){
           
$s = $this->parseLessSelector();
            if( !
$s ){
                break;
            }
           
$selectors[] = $s;
           
$this->parseComments();

            if(
$s->condition && count($selectors) > 1 ){
               
$this->Error('Guards are only currently allowed on a single selector.');
            }

            if( !
$this->MatchChar(',') ){
                break;
            }
            if(
$s->condition ){
               
$this->Error('Guards are only currently allowed on a single selector.');
            }
           
$this->parseComments();
        }


        if(
$selectors ){
           
$rules = $this->parseBlock();
            if(
is_array($rules) ){
               
$this->forget();
                return
$this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
           
}
        }

       
// Backtrack
       
$this->furthest = $this->pos;
       
$this->restore();
    }

   
/**
     * Custom less.php parse function for finding simple name-value css pairs
     * ex: width:100px;
     *
     */
   
private function parseNameValue(){

       
$index = $this->pos;
       
$this->save();


       
//$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
       
$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
        if(
$match ){

            if(
$match[4] == '}' ){
               
$this->pos = $index + strlen($match[0])-1;
            }

            if(
$match[3] ){
               
$match[2] .= ' !important';
            }

            return
$this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
        }

       
$this->restore();
    }


    private function
parseRule( $tryAnonymous = null ){

       
$merge = false;
       
$startOfRule = $this->pos;

       
$c = $this->input[$this->pos];
        if(
$c === '.' || $c === '#' || $c === '&' ){
            return;
        }

       
$this->save();
       
$name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));

        if(
$name ){

           
$isVariable = is_string($name);

           
$value = null;
            if(
$isVariable ){
               
$value = $this->parseDetachedRuleset();
            }

           
$important = null;
            if( !
$value ){

               
// prefer to try to parse first if its a variable or we are compressing
                // but always fallback on the other one
                //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
               
if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
                   
$value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
                }else{
                   
$value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
                }

               
$important = $this->parseImportant();

               
// a name returned by this.ruleProperty() is always an array of the form:
                // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
                // where each item is a tree.Keyword or tree.Variable
               
if( !$isVariable && is_array($name) ){
                   
$nm = array_pop($name);
                    if(
$nm->value ){
                       
$merge = $nm->value;
                    }
                }
            }


            if(
$value && $this->parseEnd() ){
               
$this->forget();
                return
$this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
            }else{
               
$this->furthest = $this->pos;
               
$this->restore();
                if(
$value && !$tryAnonymous ){
                    return
$this->parseRule(true);
                }
            }
        }else{
           
$this->forget();
        }
    }

    function
parseAnonymousValue(){

        if(
preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
           
$this->pos += strlen($match[1]);
            return
$this->NewObj1('Less_Tree_Anonymous',$match[1]);
        }
    }

   
//
    // An @import directive
    //
    //     @import "lib";
    //
    // Depending on our environment, importing is done differently:
    // In the browser, it's an XHR request, in Node, it would be a
    // file-system operation. The function used for importing is
    // stored in `import`, which we pass to the Import constructor.
    //
   
private function parseImport(){

       
$this->save();

       
$dir = $this->MatchReg('/\\G@import?\s+/');

        if(
$dir ){
           
$options = $this->parseImportOptions();
           
$path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));

            if(
$path ){
               
$features = $this->parseMediaFeatures();
                if(
$this->MatchChar(';') ){
                    if(
$features ){
                       
$features = $this->NewObj1('Less_Tree_Value',$features);
                    }

                   
$this->forget();
                    return
$this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
                }
            }
        }

       
$this->restore();
    }

    private function
parseImportOptions(){

       
$options = array();

       
// list of options, surrounded by parens
       
if( !$this->MatchChar('(') ){
            return
$options;
        }
        do{
           
$optionName = $this->parseImportOption();
            if(
$optionName ){
               
$value = true;
                switch(
$optionName ){
                    case
"css":
                       
$optionName = "less";
                       
$value = false;
                        break;
                    case
"once":
                       
$optionName = "multiple";
                       
$value = false;
                        break;
                }
               
$options[$optionName] = $value;
                if( !
$this->MatchChar(',') ){ break; }
            }
        }while(
$optionName );
       
$this->expectChar(')');
        return
$options;
    }

    private function
parseImportOption(){
       
$opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference|optional)/');
        if(
$opt ){
            return
$opt[1];
        }
    }

    private function
parseMediaFeature() {
       
$nodes = array();

        do{
           
$e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
            if(
$e ){
               
$nodes[] = $e;
            } elseif (
$this->MatchChar('(')) {
               
$p = $this->parseProperty();
               
$e = $this->parseValue();
                if (
$this->MatchChar(')')) {
                    if (
$p && $e) {
                       
$r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
                       
$nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
                    } elseif (
$e) {
                       
$nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
                    } else {
                        return
null;
                    }
                } else
                    return
null;
            }
        } while (
$e);

        if (
$nodes) {
            return
$this->NewObj1('Less_Tree_Expression',$nodes);
        }
    }

    private function
parseMediaFeatures() {
       
$features = array();

        do{
           
$e = $this->parseMediaFeature();
            if(
$e ){
               
$features[] = $e;
                if (!
$this->MatchChar(',')) break;
            }else{
               
$e = $this->parseEntitiesVariable();
                if(
$e ){
                   
$features[] = $e;
                    if (!
$this->MatchChar(',')) break;
                }
            }
        } while (
$e);

        return
$features ? $features : null;
    }

    private function
parseMedia() {
        if(
$this->MatchReg('/\\G@media/') ){
           
$features = $this->parseMediaFeatures();
           
$rules = $this->parseBlock();

            if(
is_array($rules) ){
                return
$this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
            }
        }
    }


   
//
    // A CSS Directive
    //
    // @charset "utf-8";
    //
   
private function parseDirective(){

        if( !
$this->PeekChar('@') ){
            return;
        }

       
$rules = null;
       
$index = $this->pos;
       
$hasBlock = true;
       
$hasIdentifier = false;
       
$hasExpression = false;
       
$hasUnknown = false;


       
$value = $this->MatchFuncs(array('parseImport','parseMedia'));
        if(
$value ){
            return
$value;
        }

       
$this->save();

       
$name = $this->MatchReg('/\\G@[a-z-]+/');

        if( !
$name ) return;
       
$name = $name[0];


       
$nonVendorSpecificName = $name;
       
$pos = strpos($name,'-', 2);
        if(
$name[1] == '-' && $pos > 0 ){
           
$nonVendorSpecificName = "@" . substr($name, $pos + 1);
        }


        switch(
$nonVendorSpecificName ){
           
/*
            case "@font-face":
            case "@viewport":
            case "@top-left":
            case "@top-left-corner":
            case "@top-center":
            case "@top-right":
            case "@top-right-corner":
            case "@bottom-left":
            case "@bottom-left-corner":
            case "@bottom-center":
            case "@bottom-right":
            case "@bottom-right-corner":
            case "@left-top":
            case "@left-middle":
            case "@left-bottom":
            case "@right-top":
            case "@right-middle":
            case "@right-bottom":
            hasBlock = true;
            break;
            */
           
case "@charset":
               
$hasIdentifier = true;
               
$hasBlock = false;
                break;
            case
"@namespace":
               
$hasExpression = true;
               
$hasBlock = false;
                break;
            case
"@keyframes":
               
$hasIdentifier = true;
                break;
            case
"@host":
            case
"@page":
            case
"@document":
            case
"@supports":
               
$hasUnknown = true;
                break;
        }

        if(
$hasIdentifier ){
           
$value = $this->parseEntity();
            if( !
$value ){
               
$this->error("expected " . $name . " identifier");
            }
        } else if(
$hasExpression ){
           
$value = $this->parseExpression();
            if( !
$value ){
               
$this->error("expected " . $name. " expression");
            }
        } else if (
$hasUnknown) {

           
$value = $this->MatchReg('/\\G[^{;]+/');
            if(
$value ){
               
$value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
            }
        }

        if(
$hasBlock ){
           
$rules = $this->parseBlockRuleset();
        }

        if(
$rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
           
$this->forget();
            return
$this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
        }

       
$this->restore();
    }


   
//
    // A Value is a comma-delimited list of Expressions
    //
    //     font-family: Baskerville, Georgia, serif;
    //
    // In a Rule, a Value represents everything after the `:`,
    // and before the `;`.
    //
   
private function parseValue(){
       
$expressions = array();

        do{
           
$e = $this->parseExpression();
            if(
$e ){
               
$expressions[] = $e;
                if (!
$this->MatchChar(',')) {
                    break;
                }
            }
        }while(
$e);

        if(
$expressions ){
            return
$this->NewObj1('Less_Tree_Value',$expressions);
        }
    }

    private function
parseImportant (){
        if(
$this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
            return
' !important';
        }
    }

    private function
parseSub (){

        if(
$this->MatchChar('(') ){
           
$a = $this->parseAddition();
            if(
$a ){
               
$this->expectChar(')');
                return
$this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
           
}
        }
    }


   
/**
     * Parses multiplication operation
     *
     * @return Less_Tree_Operation|null
     */
   
function parseMultiplication(){

       
$return = $m = $this->parseOperand();
        if(
$return ){
            while(
true ){

               
$isSpaced = $this->isWhitespace( -1 );

                if(
$this->PeekReg('/\\G\/[*\/]/') ){
                    break;
                }

               
$op = $this->MatchChar('/');
                if( !
$op ){
                   
$op = $this->MatchChar('*');
                    if( !
$op ){
                        break;
                    }
                }

               
$a = $this->parseOperand();

                if(!
$a) { break; }

               
$m->parensInOp = true;
               
$a->parensInOp = true;
               
$return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
            }
        }
        return
$return;

    }


   
/**
     * Parses an addition operation
     *
     * @return Less_Tree_Operation|null
     */
   
private function parseAddition (){

       
$return = $m = $this->parseMultiplication();
        if(
$return ){
            while(
true ){

               
$isSpaced = $this->isWhitespace( -1 );

               
$op = $this->MatchReg('/\\G[-+]\s+/');
                if(
$op ){
                   
$op = $op[0];
                }else{
                    if( !
$isSpaced ){
                       
$op = $this->match(array('#+','#-'));
                    }
                    if( !
$op ){
                        break;
                    }
                }

               
$a = $this->parseMultiplication();
                if( !
$a ){
                    break;
                }

               
$m->parensInOp = true;
               
$a->parensInOp = true;
               
$return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
            }
        }

        return
$return;
    }


   
/**
     * Parses the conditions
     *
     * @return Less_Tree_Condition|null
     */
   
private function parseConditions() {
       
$index = $this->pos;
       
$return = $a = $this->parseCondition();
        if(
$a ){
            while(
true ){
                if( !
$this->PeekReg('/\\G,\s*(not\s*)?\(/') ||  !$this->MatchChar(',') ){
                    break;
                }
               
$b = $this->parseCondition();
                if( !
$b ){
                    break;
                }

               
$return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
            }
            return
$return;
        }
    }

    private function
parseCondition() {
       
$index = $this->pos;
       
$negate = false;
       
$c = null;

        if (
$this->MatchReg('/\\Gnot/')) $negate = true;
       
$this->expectChar('(');
       
$a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));

        if(
$a ){
           
$op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
            if(
$op ){
               
$b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
                if(
$b ){
                   
$c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
                } else {
                   
$this->Error('Unexpected expression');
                }
            } else {
               
$k = $this->NewObj1('Less_Tree_Keyword','true');
               
$c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
            }
           
$this->expectChar(')');
            return
$this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
        }
    }

   
/**
     * An operand is anything that can be part of an operation,
     * such as a Color, or a Variable
     *
     */
   
private function parseOperand (){

       
$negate = false;
       
$offset = $this->pos+1;
        if(
$offset >= $this->input_len ){
            return;
        }
       
$char = $this->input[$offset];
        if(
$char === '@' || $char === '(' ){
           
$negate = $this->MatchChar('-');
        }

       
$o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));

        if(
$negate ){
           
$o->parensInOp = true;
           
$o = $this->NewObj1('Less_Tree_Negative',$o);
        }

        return
$o;
    }


   
/**
     * Expressions either represent mathematical operations,
     * or white-space delimited Entities.
     *
     *     1px solid black
     *     @var * 2
     *
     * @return Less_Tree_Expression|null
     */
   
private function parseExpression (){
       
$entities = array();

        do{
           
$e = $this->MatchFuncs(array('parseAddition','parseEntity'));
            if(
$e ){
               
$entities[] = $e;
               
// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
               
if( !$this->PeekReg('/\\G\/[\/*]/') ){
                   
$delim = $this->MatchChar('/');
                    if(
$delim ){
                       
$entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
                    }
                }
            }
        }while(
$e);

        if(
$entities ){
            return
$this->NewObj1('Less_Tree_Expression',$entities);
        }
    }


   
/**
     * Parse a property
     * eg: 'min-width', 'orientation', etc
     *
     * @return string
     */
   
private function parseProperty (){
       
$name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
        if(
$name ){
            return
$name[1];
        }
    }


   
/**
     * Parse a rule property
     * eg: 'color', 'width', 'height', etc
     *
     * @return string
     */
   
private function parseRuleProperty(){
       
$offset = $this->pos;
       
$name = array();
       
$index = array();
       
$length = 0;


       
$this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
        while(
$this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !

       
if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
           
// at last, we have the complete match now. move forward,
            // convert name particles to tree objects and return:
           
$this->skipWhitespace($length);

            if(
$name[0] === '' ){
               
array_shift($name);
               
array_shift($index);
            }
            foreach(
$name as $k => $s ){
                if( !
$s || $s[0] !== '@' ){
                   
$name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
                }else{
                   
$name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
                }
            }
            return
$name;
        }


    }

    private function
rulePropertyMatch( $re, &$offset, &$length,  &$index, &$name ){
       
preg_match($re, $this->input, $a, 0, $offset);
        if(
$a ){
           
$index[] = $this->pos + $length;
           
$length += strlen($a[0]);
           
$offset += strlen($a[0]);
           
$name[] = $a[1];
            return
true;
        }
    }

    public static function
serializeVars( $vars ){
       
$s = '';

        foreach(
$vars as $name => $value){
           
$s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
        }

        return
$s;
    }


   
/**
     * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
     *
     * @param string $b
     */
   
public static function is_method($a,$b){
        return
is_object($a) && method_exists($a,$b);
    }


   
/**
     * Round numbers similarly to javascript
     * eg: 1.499999 to 1 instead of 2
     *
     */
   
public static function round($i, $precision = 0){

       
$precision = pow(10,$precision);
       
$i = $i*$precision;

       
$ceil = ceil($i);
       
$floor = floor($i);
        if( (
$ceil - $i) <= ($i - $floor) ){
            return
$ceil/$precision;
        }else{
            return
$floor/$precision;
        }
    }


   
/**
     * Create Less_Tree_* objects and optionally generate a cache string
     *
     * @return mixed
     */
   
public function NewObj0($class){
       
$obj = new $class();
        if(
$this->CacheEnabled() ){
           
$obj->cache_string = ' new '.$class.'()';
        }
        return
$obj;
    }

    public function
NewObj1($class, $arg){
       
$obj = new $class( $arg );
        if(
$this->CacheEnabled() ){
           
$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
        }
        return
$obj;
    }

    public function
NewObj2($class, $args){
       
$obj = new $class( $args[0], $args[1] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

    public function
NewObj3($class, $args){
       
$obj = new $class( $args[0], $args[1], $args[2] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

    public function
NewObj4($class, $args){
       
$obj = new $class( $args[0], $args[1], $args[2], $args[3] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

    public function
NewObj5($class, $args){
       
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

    public function
NewObj6($class, $args){
       
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

    public function
NewObj7($class, $args){
       
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
        if(
$this->CacheEnabled() ){
           
$this->ObjCache( $obj, $class, $args);
        }
        return
$obj;
    }

   
//caching
   
public function ObjCache($obj, $class, $args=array()){
       
$obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
    }

    public function
ArgCache($args){
        return
implode(',',array_map( array('Less_Parser','ArgString'),$args));
    }


   
/**
     * Convert an argument to a string for use in the parser cache
     *
     * @return string
     */
   
public static function ArgString($arg){

       
$type = gettype($arg);

        if(
$type === 'object'){
           
$string = $arg->cache_string;
            unset(
$arg->cache_string);
            return
$string;

        }elseif(
$type === 'array' ){
           
$string = ' Array(';
            foreach(
$arg as $k => $a){
               
$string .= var_export($k,true).' => '.self::ArgString($a).',';
            }
            return
$string . ')';
        }

        return
var_export($arg,true);
    }

    public function
Error($msg){
        throw new
Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
    }

    public static function
WinPath($path){
        return
str_replace('\\', '/', $path);
    }

    public static function
AbsPath($path, $winPath = false){
        if (
strpos($path, '//') !== false && preg_match('_^(https?:)?//\\w+(\\.\\w+)+/\\w+_i', $path)) {
            return
$winPath ? '' : false;
        } else {
           
$path = realpath($path);
            if (
$winPath) {
               
$path = self::WinPath($path);
            }
            return
$path;
        }
    }

    public function
CacheEnabled(){
        return (
Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback')));
    }

}