[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/lessphp/ -> Parser.php (source)

   1  <?php
   2  
   3  require_once( dirname(__FILE__).'/Cache.php');
   4  
   5  /**
   6   * Class for parsing and compiling less files into css
   7   *
   8   * @package Less
   9   * @subpackage parser
  10   *
  11   */
  12  class Less_Parser{
  13  
  14  
  15      /**
  16       * Default parser options
  17       */
  18      public static $default_options = array(
  19          'compress'                => false,            // option - whether to compress
  20          'strictUnits'            => false,            // whether units need to evaluate correctly
  21          'strictMath'            => false,            // whether math has to be within parenthesis
  22          'relativeUrls'            => true,            // option - whether to adjust URL's to be relative
  23          'urlArgs'                => array(),            // whether to add args into url tokens
  24          'numPrecision'            => 8,
  25  
  26          'import_dirs'            => array(),
  27          'import_callback'        => null,
  28          'cache_dir'                => null,
  29          'cache_method'            => 'php',             //false, 'serialize', 'php', 'var_export';
  30  
  31          'sourceMap'                => false,            // whether to output a source map
  32          'sourceMapBasepath'        => null,
  33          'sourceMapWriteTo'        => null,
  34          'sourceMapURL'            => null,
  35  
  36          'plugins'                => array(),
  37  
  38      );
  39  
  40      public static $options = array();
  41  
  42  
  43      private $input;                    // Less input string
  44      private $input_len;                // input string length
  45      private $pos;                    // current index in `input`
  46      private $saveStack = array();    // holds state for backtracking
  47      private $furthest;
  48  
  49      /**
  50       * @var Less_Environment
  51       */
  52      private $env;
  53  
  54      private $rules = array();
  55  
  56      private static $imports = array();
  57  
  58      public static $has_extends = false;
  59  
  60      public static $next_id = 0;
  61  
  62      /**
  63       * Filename to contents of all parsed the files
  64       *
  65       * @var array
  66       */
  67      public static $contentsMap = array();
  68  
  69  
  70      /**
  71       * @param Less_Environment|array|null $env
  72       */
  73  	public function __construct( $env = null ){
  74  
  75          // Top parser on an import tree must be sure there is one "env"
  76          // which will then be passed around by reference.
  77          if( $env instanceof Less_Environment ){
  78              $this->env = $env;
  79          }else{
  80              $this->SetOptions(Less_Parser::$default_options);
  81              $this->Reset( $env );
  82          }
  83  
  84      }
  85  
  86  
  87      /**
  88       * Reset the parser state completely
  89       *
  90       */
  91  	public function Reset( $options = null ){
  92          $this->rules = array();
  93          self::$imports = array();
  94          self::$has_extends = false;
  95          self::$imports = array();
  96          self::$contentsMap = array();
  97  
  98          $this->env = new Less_Environment($options);
  99          $this->env->Init();
 100  
 101          //set new options
 102          if( is_array($options) ){
 103              $this->SetOptions(Less_Parser::$default_options);
 104              $this->SetOptions($options);
 105          }
 106      }
 107  
 108      /**
 109       * Set one or more compiler options
 110       *  options: import_dirs, cache_dir, cache_method
 111       *
 112       */
 113  	public function SetOptions( $options ){
 114          foreach($options as $option => $value){
 115              $this->SetOption($option,$value);
 116          }
 117      }
 118  
 119      /**
 120       * Set one compiler option
 121       *
 122       */
 123  	public function SetOption($option,$value){
 124  
 125          switch($option){
 126  
 127              case 'import_dirs':
 128                  $this->SetImportDirs($value);
 129              return;
 130  
 131              case 'cache_dir':
 132                  if( is_string($value) ){
 133                      Less_Cache::SetCacheDir($value);
 134                      Less_Cache::CheckCacheDir();
 135                  }
 136              return;
 137          }
 138  
 139          Less_Parser::$options[$option] = $value;
 140      }
 141  
 142  
 143  
 144  
 145      /**
 146       * Get the current css buffer
 147       *
 148       * @return string
 149       */
 150  	public function getCss(){
 151  
 152          $precision = ini_get('precision');
 153          @ini_set('precision',16);
 154          $locale = setlocale(LC_NUMERIC, 0);
 155          setlocale(LC_NUMERIC, "C");
 156  
 157  
 158           $root = new Less_Tree_Ruleset(array(), $this->rules );
 159          $root->root = true;
 160          $root->firstRoot = true;
 161  
 162  
 163          $this->PreVisitors($root);
 164  
 165          self::$has_extends = false;
 166          $evaldRoot = $root->compile($this->env);
 167  
 168  
 169  
 170          $this->PostVisitors($evaldRoot);
 171  
 172          if( Less_Parser::$options['sourceMap'] ){
 173              $generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
 174              // will also save file
 175              // FIXME: should happen somewhere else?
 176              $css = $generator->generateCSS();
 177          }else{
 178              $css = $evaldRoot->toCSS();
 179          }
 180  
 181          if( Less_Parser::$options['compress'] ){
 182              $css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css);
 183          }
 184  
 185          //reset php settings
 186          @ini_set('precision',$precision);
 187          setlocale(LC_NUMERIC, $locale);
 188  
 189          return $css;
 190      }
 191  
 192      /**
 193       * Run pre-compile visitors
 194       *
 195       */
 196  	private function PreVisitors($root){
 197  
 198          if( Less_Parser::$options['plugins'] ){
 199              foreach(Less_Parser::$options['plugins'] as $plugin){
 200                  if( !empty($plugin->isPreEvalVisitor) ){
 201                      $plugin->run($root);
 202                  }
 203              }
 204          }
 205      }
 206  
 207  
 208      /**
 209       * Run post-compile visitors
 210       *
 211       */
 212  	private function PostVisitors($evaldRoot){
 213  
 214          $visitors = array();
 215          $visitors[] = new Less_Visitor_joinSelector();
 216          if( self::$has_extends ){
 217              $visitors[] = new Less_Visitor_processExtends();
 218          }
 219          $visitors[] = new Less_Visitor_toCSS();
 220  
 221  
 222          if( Less_Parser::$options['plugins'] ){
 223              foreach(Less_Parser::$options['plugins'] as $plugin){
 224                  if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){
 225                      continue;
 226                  }
 227  
 228                  if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){
 229                      array_unshift( $visitors, $plugin);
 230                  }else{
 231                      $visitors[] = $plugin;
 232                  }
 233              }
 234          }
 235  
 236  
 237          for($i = 0; $i < count($visitors); $i++ ){
 238              $visitors[$i]->run($evaldRoot);
 239          }
 240  
 241      }
 242  
 243  
 244      /**
 245       * Parse a Less string into css
 246       *
 247       * @param string $str The string to convert
 248       * @param string $uri_root The url of the file
 249       * @return Less_Tree_Ruleset|Less_Parser
 250       */
 251  	public function parse( $str, $file_uri = null ){
 252  
 253          if( !$file_uri ){
 254              $uri_root = '';
 255              $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
 256          }else{
 257              $file_uri = self::WinPath($file_uri);
 258              $filename = basename($file_uri);
 259              $uri_root = dirname($file_uri);
 260          }
 261  
 262          $previousFileInfo = $this->env->currentFileInfo;
 263          $uri_root = self::WinPath($uri_root);
 264          $this->SetFileInfo($filename, $uri_root);
 265  
 266          $this->input = $str;
 267          $this->_parse();
 268  
 269          if( $previousFileInfo ){
 270              $this->env->currentFileInfo = $previousFileInfo;
 271          }
 272  
 273          return $this;
 274      }
 275  
 276  
 277      /**
 278       * Parse a Less string from a given file
 279       *
 280       * @throws Less_Exception_Parser
 281       * @param string $filename The file to parse
 282       * @param string $uri_root The url of the file
 283       * @param bool $returnRoot Indicates whether the return value should be a css string a root node
 284       * @return Less_Tree_Ruleset|Less_Parser
 285       */
 286  	public function parseFile( $filename, $uri_root = '', $returnRoot = false){
 287  
 288          if( !file_exists($filename) ){
 289              $this->Error(sprintf('File `%s` not found.', $filename));
 290          }
 291  
 292  
 293          // fix uri_root?
 294          // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
 295          if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){
 296              $uri_root = dirname($uri_root);
 297          }
 298  
 299  
 300          $previousFileInfo = $this->env->currentFileInfo;
 301          $filename = self::WinPath($filename);
 302          $uri_root = self::WinPath($uri_root);
 303          $this->SetFileInfo($filename, $uri_root);
 304  
 305          self::AddParsedFile($filename);
 306  
 307          if( $returnRoot ){
 308              $rules = $this->GetRules( $filename );
 309              $return = new Less_Tree_Ruleset(array(), $rules );
 310          }else{
 311              $this->_parse( $filename );
 312              $return = $this;
 313          }
 314  
 315          if( $previousFileInfo ){
 316              $this->env->currentFileInfo = $previousFileInfo;
 317          }
 318  
 319          return $return;
 320      }
 321  
 322  
 323      /**
 324       * Allows a user to set variables values
 325       * @param array $vars
 326       * @return Less_Parser
 327       */
 328  	public function ModifyVars( $vars ){
 329  
 330          $this->input = $this->serializeVars( $vars );
 331          $this->_parse();
 332  
 333          return $this;
 334      }
 335  
 336  
 337      /**
 338       * @param string $filename
 339       */
 340  	public function SetFileInfo( $filename, $uri_root = ''){
 341  
 342          $filename = Less_Environment::normalizePath($filename);
 343          $dirname = preg_replace('/[^\/\\\\]*$/','',$filename);
 344  
 345          if( !empty($uri_root) ){
 346              $uri_root = rtrim($uri_root,'/').'/';
 347          }
 348  
 349          $currentFileInfo = array();
 350  
 351          //entry info
 352          if( isset($this->env->currentFileInfo) ){
 353              $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
 354              $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
 355              $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
 356  
 357          }else{
 358              $currentFileInfo['entryPath'] = $dirname;
 359              $currentFileInfo['entryUri'] = $uri_root;
 360              $currentFileInfo['rootpath'] = $dirname;
 361          }
 362  
 363          $currentFileInfo['currentDirectory'] = $dirname;
 364          $currentFileInfo['currentUri'] = $uri_root.basename($filename);
 365          $currentFileInfo['filename'] = $filename;
 366          $currentFileInfo['uri_root'] = $uri_root;
 367  
 368  
 369          //inherit reference
 370          if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){
 371              $currentFileInfo['reference'] = true;
 372          }
 373  
 374          $this->env->currentFileInfo = $currentFileInfo;
 375      }
 376  
 377  
 378      /**
 379       * @deprecated 1.5.1.2
 380       *
 381       */
 382  	public function SetCacheDir( $dir ){
 383  
 384          if( !file_exists($dir) ){
 385              if( mkdir($dir) ){
 386                  return true;
 387              }
 388              throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir);
 389  
 390          }elseif( !is_dir($dir) ){
 391              throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir);
 392  
 393          }elseif( !is_writable($dir) ){
 394              throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir);
 395  
 396          }else{
 397              $dir = self::WinPath($dir);
 398              Less_Cache::$cache_dir = rtrim($dir,'/').'/';
 399              return true;
 400          }
 401      }
 402  
 403  
 404      /**
 405       * Set a list of directories or callbacks the parser should use for determining import paths
 406       *
 407       * @param array $dirs
 408       */
 409  	public function SetImportDirs( $dirs ){
 410          Less_Parser::$options['import_dirs'] = array();
 411  
 412          foreach($dirs as $path => $uri_root){
 413  
 414              $path = self::WinPath($path);
 415              if( !empty($path) ){
 416                  $path = rtrim($path,'/').'/';
 417              }
 418  
 419              if ( !is_callable($uri_root) ){
 420                  $uri_root = self::WinPath($uri_root);
 421                  if( !empty($uri_root) ){
 422                      $uri_root = rtrim($uri_root,'/').'/';
 423                  }
 424              }
 425  
 426              Less_Parser::$options['import_dirs'][$path] = $uri_root;
 427          }
 428      }
 429  
 430      /**
 431       * @param string $file_path
 432       */
 433  	private function _parse( $file_path = null ){
 434          $this->rules = array_merge($this->rules, $this->GetRules( $file_path ));
 435      }
 436  
 437  
 438      /**
 439       * Return the results of parsePrimary for $file_path
 440       * Use cache and save cached results if possible
 441       *
 442       * @param string|null $file_path
 443       */
 444  	private function GetRules( $file_path ){
 445  
 446          $this->SetInput($file_path);
 447  
 448          $cache_file = $this->CacheFile( $file_path );
 449          if( $cache_file && file_exists($cache_file) ){
 450              switch(Less_Parser::$options['cache_method']){
 451  
 452                  // Using serialize
 453                  // Faster but uses more memory
 454                  case 'serialize':
 455                      $cache = unserialize(file_get_contents($cache_file));
 456                      if( $cache ){
 457                          touch($cache_file);
 458                          $this->UnsetInput();
 459                          return $cache;
 460                      }
 461                  break;
 462  
 463  
 464                  // Using generated php code
 465                  case 'var_export':
 466                  case 'php':
 467                  $this->UnsetInput();
 468                  return include($cache_file);
 469              }
 470          }
 471  
 472          $rules = $this->parsePrimary();
 473  
 474          if( $this->pos < $this->input_len ){
 475              throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo);
 476          }
 477  
 478          $this->UnsetInput();
 479  
 480  
 481          //save the cache
 482          if( $cache_file ){
 483  
 484              //msg('write cache file');
 485              switch(Less_Parser::$options['cache_method']){
 486                  case 'serialize':
 487                      file_put_contents( $cache_file, serialize($rules) );
 488                  break;
 489                  case 'php':
 490                      file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' );
 491                  break;
 492                  case 'var_export':
 493                      //Requires __set_state()
 494                      file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' );
 495                  break;
 496              }
 497  
 498              Less_Cache::CleanCache();
 499          }
 500  
 501          return $rules;
 502      }
 503  
 504  
 505      /**
 506       * Set up the input buffer
 507       *
 508       */
 509  	public function SetInput( $file_path ){
 510  
 511          if( $file_path ){
 512              $this->input = file_get_contents( $file_path );
 513          }
 514  
 515          $this->pos = $this->furthest = 0;
 516  
 517          // Remove potential UTF Byte Order Mark
 518          $this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input);
 519          $this->input_len = strlen($this->input);
 520  
 521  
 522          if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){
 523              $uri = $this->env->currentFileInfo['currentUri'];
 524              Less_Parser::$contentsMap[$uri] = $this->input;
 525          }
 526  
 527      }
 528  
 529  
 530      /**
 531       * Free up some memory
 532       *
 533       */
 534  	public function UnsetInput(){
 535          unset($this->input, $this->pos, $this->input_len, $this->furthest);
 536          $this->saveStack = array();
 537      }
 538  
 539  
 540  	public function CacheFile( $file_path ){
 541  
 542          if( $file_path && Less_Parser::$options['cache_method'] && Less_Cache::$cache_dir ){
 543  
 544              $env = get_object_vars($this->env);
 545              unset($env['frames']);
 546  
 547              $parts = array();
 548              $parts[] = $file_path;
 549              $parts[] = filesize( $file_path );
 550              $parts[] = filemtime( $file_path );
 551              $parts[] = $env;
 552              $parts[] = Less_Version::cache_version;
 553              $parts[] = Less_Parser::$options['cache_method'];
 554              return Less_Cache::$cache_dir.'lessphp_'.base_convert( sha1(json_encode($parts) ), 16, 36).'.lesscache';
 555          }
 556      }
 557  
 558  
 559  	static function AddParsedFile($file){
 560          self::$imports[] = $file;
 561      }
 562  
 563  	static function AllParsedFiles(){
 564          return self::$imports;
 565      }
 566  
 567      /**
 568       * @param string $file
 569       */
 570  	static function FileParsed($file){
 571          return in_array($file,self::$imports);
 572      }
 573  
 574  
 575  	function save() {
 576          $this->saveStack[] = $this->pos;
 577      }
 578  
 579  	private function restore() {
 580          $this->pos = array_pop($this->saveStack);
 581      }
 582  
 583  	private function forget(){
 584          array_pop($this->saveStack);
 585      }
 586  
 587  
 588  	private function isWhitespace($offset = 0) {
 589          return preg_match('/\s/',$this->input[ $this->pos + $offset]);
 590      }
 591  
 592      /**
 593       * Parse from a token, regexp or string, and move forward if match
 594       *
 595       * @param array $toks
 596       * @return array
 597       */
 598  	private function match($toks){
 599  
 600          // The match is confirmed, add the match length to `this::pos`,
 601          // and consume any extra white-space characters (' ' || '\n')
 602          // which come after that. The reason for this is that LeSS's
 603          // grammar is mostly white-space insensitive.
 604          //
 605  
 606          foreach($toks as $tok){
 607  
 608              $char = $tok[0];
 609  
 610              if( $char === '/' ){
 611                  $match = $this->MatchReg($tok);
 612  
 613                  if( $match ){
 614                      return count($match) === 1 ? $match[0] : $match;
 615                  }
 616  
 617              }elseif( $char === '#' ){
 618                  $match = $this->MatchChar($tok[1]);
 619  
 620              }else{
 621                  // Non-terminal, match using a function call
 622                  $match = $this->$tok();
 623  
 624              }
 625  
 626              if( $match ){
 627                  return $match;
 628              }
 629          }
 630      }
 631  
 632      /**
 633       * @param string[] $toks
 634       *
 635       * @return string
 636       */
 637  	private function MatchFuncs($toks){
 638  
 639          foreach($toks as $tok){
 640              $match = $this->$tok();
 641              if( $match ){
 642                  return $match;
 643              }
 644          }
 645  
 646      }
 647  
 648      // Match a single character in the input,
 649  	private function MatchChar($tok){
 650          if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){
 651              $this->skipWhitespace(1);
 652              return $tok;
 653          }
 654      }
 655  
 656      // Match a regexp from the current start point
 657  	private function MatchReg($tok){
 658  
 659          if( preg_match($tok, $this->input, $match, 0, $this->pos) ){
 660              $this->skipWhitespace(strlen($match[0]));
 661              return $match;
 662          }
 663      }
 664  
 665  
 666      /**
 667       * Same as match(), but don't change the state of the parser,
 668       * just return the match.
 669       *
 670       * @param string $tok
 671       * @return integer
 672       */
 673  	public function PeekReg($tok){
 674          return preg_match($tok, $this->input, $match, 0, $this->pos);
 675      }
 676  
 677      /**
 678       * @param string $tok
 679       */
 680  	public function PeekChar($tok){
 681          //return ($this->input[$this->pos] === $tok );
 682          return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok );
 683      }
 684  
 685  
 686      /**
 687       * @param integer $length
 688       */
 689  	public function skipWhitespace($length){
 690  
 691          $this->pos += $length;
 692  
 693          for(; $this->pos < $this->input_len; $this->pos++ ){
 694              $c = $this->input[$this->pos];
 695  
 696              if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){
 697                  break;
 698              }
 699          }
 700      }
 701  
 702  
 703      /**
 704       * @param string $tok
 705       * @param string|null $msg
 706       */
 707  	public function expect($tok, $msg = NULL) {
 708          $result = $this->match( array($tok) );
 709          if (!$result) {
 710              $this->Error( $msg    ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
 711          } else {
 712              return $result;
 713          }
 714      }
 715  
 716      /**
 717       * @param string $tok
 718       */
 719  	public function expectChar($tok, $msg = null ){
 720          $result = $this->MatchChar($tok);
 721          if( !$result ){
 722              $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
 723          }else{
 724              return $result;
 725          }
 726      }
 727  
 728      //
 729      // Here in, the parsing rules/functions
 730      //
 731      // The basic structure of the syntax tree generated is as follows:
 732      //
 733      //   Ruleset ->  Rule -> Value -> Expression -> Entity
 734      //
 735      // Here's some LESS code:
 736      //
 737      //    .class {
 738      //      color: #fff;
 739      //      border: 1px solid #000;
 740      //      width: @w + 4px;
 741      //      > .child {...}
 742      //    }
 743      //
 744      // And here's what the parse tree might look like:
 745      //
 746      //     Ruleset (Selector '.class', [
 747      //         Rule ("color",  Value ([Expression [Color #fff]]))
 748      //         Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
 749      //         Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
 750      //         Ruleset (Selector [Element '>', '.child'], [...])
 751      //     ])
 752      //
 753      //  In general, most rules will try to parse a token with the `$()` function, and if the return
 754      //  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
 755      //  first, before parsing, that's when we use `peek()`.
 756      //
 757  
 758      //
 759      // The `primary` rule is the *entry* and *exit* point of the parser.
 760      // The rules here can appear at any level of the parse tree.
 761      //
 762      // The recursive nature of the grammar is an interplay between the `block`
 763      // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
 764      // as represented by this simplified grammar:
 765      //
 766      //     primary  →  (ruleset | rule)+
 767      //     ruleset  →  selector+ block
 768      //     block    →  '{' primary '}'
 769      //
 770      // Only at one point is the primary rule not called from the
 771      // block rule: at the root level.
 772      //
 773  	private function parsePrimary(){
 774          $root = array();
 775  
 776          while( true ){
 777  
 778              if( $this->pos >= $this->input_len ){
 779                  break;
 780              }
 781  
 782              $node = $this->parseExtend(true);
 783              if( $node ){
 784                  $root = array_merge($root,$node);
 785                  continue;
 786              }
 787  
 788              //$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
 789              $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective'));
 790  
 791              if( $node ){
 792                  $root[] = $node;
 793              }elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){
 794                  break;
 795              }
 796  
 797              if( $this->PeekChar('}') ){
 798                  break;
 799              }
 800          }
 801  
 802          return $root;
 803      }
 804  
 805  
 806  
 807      // We create a Comment node for CSS comments `/* */`,
 808      // but keep the LeSS comments `//` silent, by just skipping
 809      // over them.
 810  	private function parseComment(){
 811  
 812          if( $this->input[$this->pos] !== '/' ){
 813              return;
 814          }
 815  
 816          if( $this->input[$this->pos+1] === '/' ){
 817              $match = $this->MatchReg('/\\G\/\/.*/');
 818              return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo));
 819          }
 820  
 821          //$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
 822          $comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors
 823          if( $comment ){
 824              return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo));
 825          }
 826      }
 827  
 828  	private function parseComments(){
 829          $comments = array();
 830  
 831          while( $this->pos < $this->input_len ){
 832              $comment = $this->parseComment();
 833              if( !$comment ){
 834                  break;
 835              }
 836  
 837              $comments[] = $comment;
 838          }
 839  
 840          return $comments;
 841      }
 842  
 843  
 844  
 845      //
 846      // A string, which supports escaping " and '
 847      //
 848      //     "milky way" 'he\'s the one!'
 849      //
 850  	private function parseEntitiesQuoted() {
 851          $j = $this->pos;
 852          $e = false;
 853          $index = $this->pos;
 854  
 855          if( $this->input[$this->pos] === '~' ){
 856              $j++;
 857              $e = true; // Escaped strings
 858          }
 859  
 860          if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){
 861              return;
 862          }
 863  
 864          if ($e) {
 865              $this->MatchChar('~');
 866          }
 867          $str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/');
 868          if( $str ){
 869              $result = $str[0][0] == '"' ? $str[1] : $str[2];
 870              return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) );
 871          }
 872          return;
 873      }
 874  
 875  
 876      //
 877      // A catch-all word, such as:
 878      //
 879      //     black border-collapse
 880      //
 881  	private function parseEntitiesKeyword(){
 882  
 883          //$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
 884          $k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/');
 885          if( $k ){
 886              $k = $k[0];
 887              $color = $this->fromKeyword($k);
 888              if( $color ){
 889                  return $color;
 890              }
 891              return $this->NewObj1('Less_Tree_Keyword',$k);
 892          }
 893      }
 894  
 895      // duplicate of Less_Tree_Color::FromKeyword
 896  	private function FromKeyword( $keyword ){
 897          $keyword = strtolower($keyword);
 898  
 899          if( Less_Colors::hasOwnProperty($keyword) ){
 900              // detect named color
 901              return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1));
 902          }
 903  
 904          if( $keyword === 'transparent' ){
 905              return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true));
 906          }
 907      }
 908  
 909      //
 910      // A function call
 911      //
 912      //     rgb(255, 0, 255)
 913      //
 914      // We also try to catch IE's `alpha()`, but let the `alpha` parser
 915      // deal with the details.
 916      //
 917      // The arguments are parsed with the `entities.arguments` parser.
 918      //
 919  	private function parseEntitiesCall(){
 920          $index = $this->pos;
 921  
 922          if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){
 923              return;
 924          }
 925          $name = $name[1];
 926          $nameLC = strtolower($name);
 927  
 928          if ($nameLC === 'url') {
 929              return null;
 930          }
 931  
 932          $this->pos += strlen($name);
 933  
 934          if( $nameLC === 'alpha' ){
 935              $alpha_ret = $this->parseAlpha();
 936              if( $alpha_ret ){
 937                  return $alpha_ret;
 938              }
 939          }
 940  
 941          $this->MatchChar('('); // Parse the '(' and consume whitespace.
 942  
 943          $args = $this->parseEntitiesArguments();
 944  
 945          if( !$this->MatchChar(')') ){
 946              return;
 947          }
 948  
 949          if ($name) {
 950              return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) );
 951          }
 952      }
 953  
 954      /**
 955       * Parse a list of arguments
 956       *
 957       * @return array
 958       */
 959  	private function parseEntitiesArguments(){
 960  
 961          $args = array();
 962          while( true ){
 963              $arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') );
 964              if( !$arg ){
 965                  break;
 966              }
 967  
 968              $args[] = $arg;
 969              if( !$this->MatchChar(',') ){
 970                  break;
 971              }
 972          }
 973          return $args;
 974      }
 975  
 976  	private function parseEntitiesLiteral(){
 977          return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') );
 978      }
 979  
 980      // Assignments are argument entities for calls.
 981      // They are present in ie filter properties as shown below.
 982      //
 983      //     filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
 984      //
 985  	private function parseEntitiesAssignment() {
 986  
 987          $key = $this->MatchReg('/\\G\w+(?=\s?=)/');
 988          if( !$key ){
 989              return;
 990          }
 991  
 992          if( !$this->MatchChar('=') ){
 993              return;
 994          }
 995  
 996          $value = $this->parseEntity();
 997          if( $value ){
 998              return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value));
 999          }
1000      }
1001  
1002      //
1003      // Parse url() tokens
1004      //
1005      // We use a specific rule for urls, because they don't really behave like
1006      // standard function calls. The difference is that the argument doesn't have
1007      // to be enclosed within a string, so it can't be parsed as an Expression.
1008      //
1009  	private function parseEntitiesUrl(){
1010  
1011  
1012          if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){
1013              return;
1014          }
1015  
1016          $value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') );
1017          if( !$value ){
1018              $value = '';
1019          }
1020  
1021  
1022          $this->expectChar(')');
1023  
1024  
1025          if( isset($value->value) || $value instanceof Less_Tree_Variable ){
1026              return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo));
1027          }
1028  
1029          return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) );
1030      }
1031  
1032  
1033      //
1034      // A Variable entity, such as `@fink`, in
1035      //
1036      //     width: @fink + 2px
1037      //
1038      // We use a different parser for variable definitions,
1039      // see `parsers.variable`.
1040      //
1041  	private function parseEntitiesVariable(){
1042          $index = $this->pos;
1043          if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) {
1044              return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo));
1045          }
1046      }
1047  
1048  
1049      // A variable entity useing the protective {} e.g. @{var}
1050  	private function parseEntitiesVariableCurly() {
1051          $index = $this->pos;
1052  
1053          if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){
1054              return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo));
1055          }
1056      }
1057  
1058      //
1059      // A Hexadecimal color
1060      //
1061      //     #4F3C2F
1062      //
1063      // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
1064      //
1065  	private function parseEntitiesColor(){
1066          if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) {
1067              return $this->NewObj1('Less_Tree_Color',$rgb[1]);
1068          }
1069      }
1070  
1071      //
1072      // A Dimension, that is, a number and a unit
1073      //
1074      //     0.5em 95%
1075      //
1076  	private function parseEntitiesDimension(){
1077  
1078          $c = @ord($this->input[$this->pos]);
1079  
1080          //Is the first char of the dimension 0-9, '.', '+' or '-'
1081          if (($c > 57 || $c < 43) || $c === 47 || $c == 44){
1082              return;
1083          }
1084  
1085          $value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/');
1086          if( $value ){
1087  
1088              if( isset($value[2]) ){
1089                  return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2]));
1090              }
1091              return $this->NewObj1('Less_Tree_Dimension',$value[1]);
1092          }
1093      }
1094  
1095  
1096      //
1097      // A unicode descriptor, as is used in unicode-range
1098      //
1099      // U+0?? or U+00A1-00A9
1100      //
1101  	function parseUnicodeDescriptor() {
1102          $ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/');
1103          if( $ud ){
1104              return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]);
1105          }
1106      }
1107  
1108  
1109      //
1110      // JavaScript code to be evaluated
1111      //
1112      //     `window.location.href`
1113      //
1114  	private function parseEntitiesJavascript(){
1115          $e = false;
1116          $j = $this->pos;
1117          if( $this->input[$j] === '~' ){
1118              $j++;
1119              $e = true;
1120          }
1121          if( $this->input[$j] !== '`' ){
1122              return;
1123          }
1124          if( $e ){
1125              $this->MatchChar('~');
1126          }
1127          $str = $this->MatchReg('/\\G`([^`]*)`/');
1128          if( $str ){
1129              return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e));
1130          }
1131      }
1132  
1133  
1134      //
1135      // The variable part of a variable definition. Used in the `rule` parser
1136      //
1137      //     @fink:
1138      //
1139  	private function parseVariable(){
1140          if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) {
1141              return $name[1];
1142          }
1143      }
1144  
1145  
1146      //
1147      // The variable part of a variable definition. Used in the `rule` parser
1148      //
1149      // @fink();
1150      //
1151  	private function parseRulesetCall(){
1152  
1153          if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){
1154              return $this->NewObj1('Less_Tree_RulesetCall', $name[1] );
1155          }
1156      }
1157  
1158  
1159      //
1160      // extend syntax - used to extend selectors
1161      //
1162  	function parseExtend($isRule = false){
1163  
1164          $index = $this->pos;
1165          $extendList = array();
1166  
1167  
1168          if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; }
1169  
1170          do{
1171              $option = null;
1172              $elements = array();
1173              while( true ){
1174                  $option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/');
1175                  if( $option ){ break; }
1176                  $e = $this->parseElement();
1177                  if( !$e ){ break; }
1178                  $elements[] = $e;
1179              }
1180  
1181              if( $option ){
1182                  $option = $option[1];
1183              }
1184  
1185              $extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index ));
1186  
1187          }while( $this->MatchChar(",") );
1188  
1189          $this->expect('/\\G\)/');
1190  
1191          if( $isRule ){
1192              $this->expect('/\\G;/');
1193          }
1194  
1195          return $extendList;
1196      }
1197  
1198  
1199      //
1200      // A Mixin call, with an optional argument list
1201      //
1202      //     #mixins > .square(#fff);
1203      //     .rounded(4px, black);
1204      //     .button;
1205      //
1206      // The `while` loop is there because mixins can be
1207      // namespaced, but we only support the child and descendant
1208      // selector for now.
1209      //
1210  	private function parseMixinCall(){
1211  
1212          $char = $this->input[$this->pos];
1213          if( $char !== '.' && $char !== '#' ){
1214              return;
1215          }
1216  
1217          $index = $this->pos;
1218          $this->save(); // stop us absorbing part of an invalid selector
1219  
1220          $elements = $this->parseMixinCallElements();
1221  
1222          if( $elements ){
1223  
1224              if( $this->MatchChar('(') ){
1225                  $returned = $this->parseMixinArgs(true);
1226                  $args = $returned['args'];
1227                  $this->expectChar(')');
1228              }else{
1229                  $args = array();
1230              }
1231  
1232              $important = $this->parseImportant();
1233  
1234              if( $this->parseEnd() ){
1235                  $this->forget();
1236                  return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important));
1237              }
1238          }
1239  
1240          $this->restore();
1241      }
1242  
1243  
1244  	private function parseMixinCallElements(){
1245          $elements = array();
1246          $c = null;
1247  
1248          while( true ){
1249              $elemIndex = $this->pos;
1250              $e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/');
1251              if( !$e ){
1252                  break;
1253              }
1254              $elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo));
1255              $c = $this->MatchChar('>');
1256          }
1257  
1258          return $elements;
1259      }
1260  
1261  
1262  
1263      /**
1264       * @param boolean $isCall
1265       */
1266  	private function parseMixinArgs( $isCall ){
1267          $expressions = array();
1268          $argsSemiColon = array();
1269          $isSemiColonSeperated = null;
1270          $argsComma = array();
1271          $expressionContainsNamed = null;
1272          $name = null;
1273          $returner = array('args'=>array(), 'variadic'=> false);
1274  
1275          $this->save();
1276  
1277          while( true ){
1278              if( $isCall ){
1279                  $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
1280              } else {
1281                  $this->parseComments();
1282                  if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){
1283                      $returner['variadic'] = true;
1284                      if( $this->MatchChar(";") && !$isSemiColonSeperated ){
1285                          $isSemiColonSeperated = true;
1286                      }
1287  
1288                      if( $isSemiColonSeperated ){
1289                          $argsSemiColon[] = array('variadic'=>true);
1290                      }else{
1291                          $argsComma[] = array('variadic'=>true);
1292                      }
1293                      break;
1294                  }
1295                  $arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') );
1296              }
1297  
1298              if( !$arg ){
1299                  break;
1300              }
1301  
1302  
1303              $nameLoop = null;
1304              if( $arg instanceof Less_Tree_Expression ){
1305                  $arg->throwAwayComments();
1306              }
1307              $value = $arg;
1308              $val = null;
1309  
1310              if( $isCall ){
1311                  // Variable
1312                  if( property_exists($arg,'value') && count($arg->value) == 1 ){
1313                      $val = $arg->value[0];
1314                  }
1315              } else {
1316                  $val = $arg;
1317              }
1318  
1319  
1320              if( $val instanceof Less_Tree_Variable ){
1321  
1322                  if( $this->MatchChar(':') ){
1323                      if( $expressions ){
1324                          if( $isSemiColonSeperated ){
1325                              $this->Error('Cannot mix ; and , as delimiter types');
1326                          }
1327                          $expressionContainsNamed = true;
1328                      }
1329  
1330                      // we do not support setting a ruleset as a default variable - it doesn't make sense
1331                      // However if we do want to add it, there is nothing blocking it, just don't error
1332                      // and remove isCall dependency below
1333                      $value = null;
1334                      if( $isCall ){
1335                          $value = $this->parseDetachedRuleset();
1336                      }
1337                      if( !$value ){
1338                          $value = $this->parseExpression();
1339                      }
1340  
1341                      if( !$value ){
1342                          if( $isCall ){
1343                              $this->Error('could not understand value for named argument');
1344                          } else {
1345                              $this->restore();
1346                              $returner['args'] = array();
1347                              return $returner;
1348                          }
1349                      }
1350  
1351                      $nameLoop = ($name = $val->name);
1352                  }elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){
1353                      $returner['variadic'] = true;
1354                      if( $this->MatchChar(";") && !$isSemiColonSeperated ){
1355                          $isSemiColonSeperated = true;
1356                      }
1357                      if( $isSemiColonSeperated ){
1358                          $argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true);
1359                      }else{
1360                          $argsComma[] = array('name'=> $arg->name, 'variadic' => true);
1361                      }
1362                      break;
1363                  }elseif( !$isCall ){
1364                      $name = $nameLoop = $val->name;
1365                      $value = null;
1366                  }
1367              }
1368  
1369              if( $value ){
1370                  $expressions[] = $value;
1371              }
1372  
1373              $argsComma[] = array('name'=>$nameLoop, 'value'=>$value );
1374  
1375              if( $this->MatchChar(',') ){
1376                  continue;
1377              }
1378  
1379              if( $this->MatchChar(';') || $isSemiColonSeperated ){
1380  
1381                  if( $expressionContainsNamed ){
1382                      $this->Error('Cannot mix ; and , as delimiter types');
1383                  }
1384  
1385                  $isSemiColonSeperated = true;
1386  
1387                  if( count($expressions) > 1 ){
1388                      $value = $this->NewObj1('Less_Tree_Value', $expressions);
1389                  }
1390                  $argsSemiColon[] = array('name'=>$name, 'value'=>$value );
1391  
1392                  $name = null;
1393                  $expressions = array();
1394                  $expressionContainsNamed = false;
1395              }
1396          }
1397  
1398          $this->forget();
1399          $returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma);
1400          return $returner;
1401      }
1402  
1403  
1404  
1405      //
1406      // A Mixin definition, with a list of parameters
1407      //
1408      //     .rounded (@radius: 2px, @color) {
1409      //        ...
1410      //     }
1411      //
1412      // Until we have a finer grained state-machine, we have to
1413      // do a look-ahead, to make sure we don't have a mixin call.
1414      // See the `rule` function for more information.
1415      //
1416      // We start by matching `.rounded (`, and then proceed on to
1417      // the argument list, which has optional default values.
1418      // We store the parameters in `params`, with a `value` key,
1419      // if there is a value, such as in the case of `@radius`.
1420      //
1421      // Once we've got our params list, and a closing `)`, we parse
1422      // the `{...}` block.
1423      //
1424  	private function parseMixinDefinition(){
1425          $cond = null;
1426  
1427          $char = $this->input[$this->pos];
1428          if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){
1429              return;
1430          }
1431  
1432          $this->save();
1433  
1434          $match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/');
1435          if( $match ){
1436              $name = $match[1];
1437  
1438              $argInfo = $this->parseMixinArgs( false );
1439              $params = $argInfo['args'];
1440              $variadic = $argInfo['variadic'];
1441  
1442  
1443              // .mixincall("@{a}");
1444              // looks a bit like a mixin definition..
1445              // also
1446              // .mixincall(@a: {rule: set;});
1447              // so we have to be nice and restore
1448              if( !$this->MatchChar(')') ){
1449                  $this->furthest = $this->pos;
1450                  $this->restore();
1451                  return;
1452              }
1453  
1454  
1455              $this->parseComments();
1456  
1457              if ($this->MatchReg('/\\Gwhen/')) { // Guard
1458                  $cond = $this->expect('parseConditions', 'Expected conditions');
1459              }
1460  
1461              $ruleset = $this->parseBlock();
1462  
1463              if( is_array($ruleset) ){
1464                  $this->forget();
1465                  return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic));
1466              }
1467  
1468              $this->restore();
1469          }else{
1470              $this->forget();
1471          }
1472      }
1473  
1474      //
1475      // Entities are the smallest recognized token,
1476      // and can be found inside a rule's value.
1477      //
1478  	private function parseEntity(){
1479  
1480          return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') );
1481      }
1482  
1483      //
1484      // A Rule terminator. Note that we use `peek()` to check for '}',
1485      // because the `block` rule will be expecting it, but we still need to make sure
1486      // it's there, if ';' was ommitted.
1487      //
1488  	private function parseEnd(){
1489          return $this->MatchChar(';') || $this->PeekChar('}');
1490      }
1491  
1492      //
1493      // IE's alpha function
1494      //
1495      //     alpha(opacity=88)
1496      //
1497  	private function parseAlpha(){
1498  
1499          if ( ! $this->MatchReg('/\\G\(opacity=/i')) {
1500              return;
1501          }
1502  
1503          $value = $this->MatchReg('/\\G[0-9]+/');
1504          if( $value ){
1505              $value = $value[0];
1506          }else{
1507              $value = $this->parseEntitiesVariable();
1508              if( !$value ){
1509                  return;
1510              }
1511          }
1512  
1513          $this->expectChar(')');
1514          return $this->NewObj1('Less_Tree_Alpha',$value);
1515      }
1516  
1517  
1518      //
1519      // A Selector Element
1520      //
1521      //     div
1522      //     + h1
1523      //     #socks
1524      //     input[type="text"]
1525      //
1526      // Elements are the building blocks for Selectors,
1527      // they are made out of a `Combinator` (see combinator rule),
1528      // and an element name, such as a tag a class, or `*`.
1529      //
1530  	private function parseElement(){
1531          $c = $this->parseCombinator();
1532          $index = $this->pos;
1533  
1534          $e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
1535              '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') );
1536  
1537          if( is_null($e) ){
1538              $this->save();
1539              if( $this->MatchChar('(') ){
1540                  if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){
1541                      $e = $this->NewObj1('Less_Tree_Paren',$v);
1542                      $this->forget();
1543                  }else{
1544                      $this->restore();
1545                  }
1546              }else{
1547                  $this->forget();
1548              }
1549          }
1550  
1551          if( !is_null($e) ){
1552              return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo));
1553          }
1554      }
1555  
1556      //
1557      // Combinators combine elements together, in a Selector.
1558      //
1559      // Because our parser isn't white-space sensitive, special care
1560      // has to be taken, when parsing the descendant combinator, ` `,
1561      // as it's an empty space. We have to check the previous character
1562      // in the input, to see if it's a ` ` character.
1563      //
1564  	private function parseCombinator(){
1565          $c = $this->input[$this->pos];
1566          if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){
1567  
1568              $this->pos++;
1569              if( $this->input[$this->pos] === '^' ){
1570                  $c = '^^';
1571                  $this->pos++;
1572              }
1573  
1574              $this->skipWhitespace(0);
1575  
1576              return $c;
1577          }
1578  
1579          if( $this->pos > 0 && $this->isWhitespace(-1) ){
1580              return ' ';
1581          }
1582      }
1583  
1584      //
1585      // A CSS selector (see selector below)
1586      // with less extensions e.g. the ability to extend and guard
1587      //
1588  	private function parseLessSelector(){
1589          return $this->parseSelector(true);
1590      }
1591  
1592      //
1593      // A CSS Selector
1594      //
1595      //     .class > div + h1
1596      //     li a:hover
1597      //
1598      // Selectors are made out of one or more Elements, see above.
1599      //
1600  	private function parseSelector( $isLess = false ){
1601          $elements = array();
1602          $extendList = array();
1603          $condition = null;
1604          $when = false;
1605          $extend = false;
1606          $e = null;
1607          $c = null;
1608          $index = $this->pos;
1609  
1610          while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){
1611              if( $when ){
1612                  $condition = $this->expect('parseConditions', 'expected condition');
1613              }elseif( $condition ){
1614                  //error("CSS guard can only be used at the end of selector");
1615              }elseif( $extend ){
1616                  $extendList = array_merge($extendList,$extend);
1617              }else{
1618                  //if( count($extendList) ){
1619                      //error("Extend can only be used at the end of selector");
1620                  //}
1621                  $c = $this->input[ $this->pos ];
1622                  $elements[] = $e;
1623                  $e = null;
1624              }
1625  
1626              if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; }
1627          }
1628  
1629          if( $elements ){
1630              return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo));
1631          }
1632          if( $extendList ) {
1633              $this->Error('Extend must be used to extend a selector, it cannot be used on its own');
1634          }
1635      }
1636  
1637  	private function parseTag(){
1638          return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*');
1639      }
1640  
1641  	private function parseAttribute(){
1642  
1643          $val = null;
1644  
1645          if( !$this->MatchChar('[') ){
1646              return;
1647          }
1648  
1649          $key = $this->parseEntitiesVariableCurly();
1650          if( !$key ){
1651              $key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/');
1652          }
1653  
1654          $op = $this->MatchReg('/\\G[|~*$^]?=/');
1655          if( $op ){
1656              $val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') );
1657          }
1658  
1659          $this->expectChar(']');
1660  
1661          return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val));
1662      }
1663  
1664      //
1665      // The `block` rule is used by `ruleset` and `mixin.definition`.
1666      // It's a wrapper around the `primary` rule, with added `{}`.
1667      //
1668  	private function parseBlock(){
1669          if( $this->MatchChar('{') ){
1670              $content = $this->parsePrimary();
1671              if( $this->MatchChar('}') ){
1672                  return $content;
1673              }
1674          }
1675      }
1676  
1677  	private function parseBlockRuleset(){
1678          $block = $this->parseBlock();
1679  
1680          if( $block ){
1681              $block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block));
1682          }
1683  
1684          return $block;
1685      }
1686  
1687  	private function parseDetachedRuleset(){
1688          $blockRuleset = $this->parseBlockRuleset();
1689          if( $blockRuleset ){
1690              return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset);
1691          }
1692      }
1693  
1694      //
1695      // div, .class, body > p {...}
1696      //
1697  	private function parseRuleset(){
1698          $selectors = array();
1699  
1700          $this->save();
1701  
1702          while( true ){
1703              $s = $this->parseLessSelector();
1704              if( !$s ){
1705                  break;
1706              }
1707              $selectors[] = $s;
1708              $this->parseComments();
1709  
1710              if( $s->condition && count($selectors) > 1 ){
1711                  $this->Error('Guards are only currently allowed on a single selector.');
1712              }
1713  
1714              if( !$this->MatchChar(',') ){
1715                  break;
1716              }
1717              if( $s->condition ){
1718                  $this->Error('Guards are only currently allowed on a single selector.');
1719              }
1720              $this->parseComments();
1721          }
1722  
1723  
1724          if( $selectors ){
1725              $rules = $this->parseBlock();
1726              if( is_array($rules) ){
1727                  $this->forget();
1728                  return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports
1729              }
1730          }
1731  
1732          // Backtrack
1733          $this->furthest = $this->pos;
1734          $this->restore();
1735      }
1736  
1737      /**
1738       * Custom less.php parse function for finding simple name-value css pairs
1739       * ex: width:100px;
1740       *
1741       */
1742  	private function parseNameValue(){
1743  
1744          $index = $this->pos;
1745          $this->save();
1746  
1747  
1748          //$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
1749          $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/');
1750          if( $match ){
1751  
1752              if( $match[4] == '}' ){
1753                  $this->pos = $index + strlen($match[0])-1;
1754              }
1755  
1756              if( $match[3] ){
1757                  $match[2] .= ' !important';
1758              }
1759  
1760              return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo));
1761          }
1762  
1763          $this->restore();
1764      }
1765  
1766  
1767  	private function parseRule( $tryAnonymous = null ){
1768  
1769          $merge = false;
1770          $startOfRule = $this->pos;
1771  
1772          $c = $this->input[$this->pos];
1773          if( $c === '.' || $c === '#' || $c === '&' ){
1774              return;
1775          }
1776  
1777          $this->save();
1778          $name = $this->MatchFuncs( array('parseVariable','parseRuleProperty'));
1779  
1780          if( $name ){
1781  
1782              $isVariable = is_string($name);
1783  
1784              $value = null;
1785              if( $isVariable ){
1786                  $value = $this->parseDetachedRuleset();
1787              }
1788  
1789              $important = null;
1790              if( !$value ){
1791  
1792                  // prefer to try to parse first if its a variable or we are compressing
1793                  // but always fallback on the other one
1794                  //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
1795                  if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){
1796                      $value = $this->MatchFuncs( array('parseValue','parseAnonymousValue'));
1797                  }else{
1798                      $value = $this->MatchFuncs( array('parseAnonymousValue','parseValue'));
1799                  }
1800  
1801                  $important = $this->parseImportant();
1802  
1803                  // a name returned by this.ruleProperty() is always an array of the form:
1804                  // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1805                  // where each item is a tree.Keyword or tree.Variable
1806                  if( !$isVariable && is_array($name) ){
1807                      $nm = array_pop($name);
1808                      if( $nm->value ){
1809                          $merge = $nm->value;
1810                      }
1811                  }
1812              }
1813  
1814  
1815              if( $value && $this->parseEnd() ){
1816                  $this->forget();
1817                  return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo));
1818              }else{
1819                  $this->furthest = $this->pos;
1820                  $this->restore();
1821                  if( $value && !$tryAnonymous ){
1822                      return $this->parseRule(true);
1823                  }
1824              }
1825          }else{
1826              $this->forget();
1827          }
1828      }
1829  
1830  	function parseAnonymousValue(){
1831  
1832          if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){
1833              $this->pos += strlen($match[1]);
1834              return $this->NewObj1('Less_Tree_Anonymous',$match[1]);
1835          }
1836      }
1837  
1838      //
1839      // An @import directive
1840      //
1841      //     @import "lib";
1842      //
1843      // Depending on our environment, importing is done differently:
1844      // In the browser, it's an XHR request, in Node, it would be a
1845      // file-system operation. The function used for importing is
1846      // stored in `import`, which we pass to the Import constructor.
1847      //
1848  	private function parseImport(){
1849  
1850          $this->save();
1851  
1852          $dir = $this->MatchReg('/\\G@import?\s+/');
1853  
1854          if( $dir ){
1855              $options = $this->parseImportOptions();
1856              $path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl'));
1857  
1858              if( $path ){
1859                  $features = $this->parseMediaFeatures();
1860                  if( $this->MatchChar(';') ){
1861                      if( $features ){
1862                          $features = $this->NewObj1('Less_Tree_Value',$features);
1863                      }
1864  
1865                      $this->forget();
1866                      return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo));
1867                  }
1868              }
1869          }
1870  
1871          $this->restore();
1872      }
1873  
1874  	private function parseImportOptions(){
1875  
1876          $options = array();
1877  
1878          // list of options, surrounded by parens
1879          if( !$this->MatchChar('(') ){
1880              return $options;
1881          }
1882          do{
1883              $optionName = $this->parseImportOption();
1884              if( $optionName ){
1885                  $value = true;
1886                  switch( $optionName ){
1887                      case "css":
1888                          $optionName = "less";
1889                          $value = false;
1890                      break;
1891                      case "once":
1892                          $optionName = "multiple";
1893                          $value = false;
1894                      break;
1895                  }
1896                  $options[$optionName] = $value;
1897                  if( !$this->MatchChar(',') ){ break; }
1898              }
1899          }while( $optionName );
1900          $this->expectChar(')');
1901          return $options;
1902      }
1903  
1904  	private function parseImportOption(){
1905          $opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/');
1906          if( $opt ){
1907              return $opt[1];
1908          }
1909      }
1910  
1911  	private function parseMediaFeature() {
1912          $nodes = array();
1913  
1914          do{
1915              $e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable'));
1916              if( $e ){
1917                  $nodes[] = $e;
1918              } elseif ($this->MatchChar('(')) {
1919                  $p = $this->parseProperty();
1920                  $e = $this->parseValue();
1921                  if ($this->MatchChar(')')) {
1922                      if ($p && $e) {
1923                          $r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true));
1924                          $nodes[] = $this->NewObj1('Less_Tree_Paren',$r);
1925                      } elseif ($e) {
1926                          $nodes[] = $this->NewObj1('Less_Tree_Paren',$e);
1927                      } else {
1928                          return null;
1929                      }
1930                  } else
1931                      return null;
1932              }
1933          } while ($e);
1934  
1935          if ($nodes) {
1936              return $this->NewObj1('Less_Tree_Expression',$nodes);
1937          }
1938      }
1939  
1940  	private function parseMediaFeatures() {
1941          $features = array();
1942  
1943          do{
1944              $e = $this->parseMediaFeature();
1945              if( $e ){
1946                  $features[] = $e;
1947                  if (!$this->MatchChar(',')) break;
1948              }else{
1949                  $e = $this->parseEntitiesVariable();
1950                  if( $e ){
1951                      $features[] = $e;
1952                      if (!$this->MatchChar(',')) break;
1953                  }
1954              }
1955          } while ($e);
1956  
1957          return $features ? $features : null;
1958      }
1959  
1960  	private function parseMedia() {
1961          if( $this->MatchReg('/\\G@media/') ){
1962              $features = $this->parseMediaFeatures();
1963              $rules = $this->parseBlock();
1964  
1965              if( is_array($rules) ){
1966                  return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo));
1967              }
1968          }
1969      }
1970  
1971  
1972      //
1973      // A CSS Directive
1974      //
1975      // @charset "utf-8";
1976      //
1977  	private function parseDirective(){
1978  
1979          if( !$this->PeekChar('@') ){
1980              return;
1981          }
1982  
1983          $rules = null;
1984          $index = $this->pos;
1985          $hasBlock = true;
1986          $hasIdentifier = false;
1987          $hasExpression = false;
1988          $hasUnknown = false;
1989  
1990  
1991          $value = $this->MatchFuncs(array('parseImport','parseMedia'));
1992          if( $value ){
1993              return $value;
1994          }
1995  
1996          $this->save();
1997  
1998          $name = $this->MatchReg('/\\G@[a-z-]+/');
1999  
2000          if( !$name ) return;
2001          $name = $name[0];
2002  
2003  
2004          $nonVendorSpecificName = $name;
2005          $pos = strpos($name,'-', 2);
2006          if( $name[1] == '-' && $pos > 0 ){
2007              $nonVendorSpecificName = "@" . substr($name, $pos + 1);
2008          }
2009  
2010  
2011          switch( $nonVendorSpecificName ){
2012              /*
2013              case "@font-face":
2014              case "@viewport":
2015              case "@top-left":
2016              case "@top-left-corner":
2017              case "@top-center":
2018              case "@top-right":
2019              case "@top-right-corner":
2020              case "@bottom-left":
2021              case "@bottom-left-corner":
2022              case "@bottom-center":
2023              case "@bottom-right":
2024              case "@bottom-right-corner":
2025              case "@left-top":
2026              case "@left-middle":
2027              case "@left-bottom":
2028              case "@right-top":
2029              case "@right-middle":
2030              case "@right-bottom":
2031              hasBlock = true;
2032              break;
2033              */
2034              case "@charset":
2035                  $hasIdentifier = true;
2036                  $hasBlock = false;
2037                  break;
2038              case "@namespace":
2039                  $hasExpression = true;
2040                  $hasBlock = false;
2041                  break;
2042              case "@keyframes":
2043                  $hasIdentifier = true;
2044                  break;
2045              case "@host":
2046              case "@page":
2047              case "@document":
2048              case "@supports":
2049                  $hasUnknown = true;
2050                  break;
2051          }
2052  
2053          if( $hasIdentifier ){
2054              $value = $this->parseEntity();
2055              if( !$value ){
2056                  $this->error("expected " . $name . " identifier");
2057              }
2058          } else if( $hasExpression ){
2059              $value = $this->parseExpression();
2060              if( !$value ){
2061                  $this->error("expected " . $name. " expression");
2062              }
2063          } else if ($hasUnknown) {
2064  
2065              $value = $this->MatchReg('/\\G[^{;]+/');
2066              if( $value ){
2067                  $value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0]));
2068              }
2069          }
2070  
2071          if( $hasBlock ){
2072              $rules = $this->parseBlockRuleset();
2073          }
2074  
2075          if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) {
2076              $this->forget();
2077              return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo));
2078          }
2079  
2080          $this->restore();
2081      }
2082  
2083  
2084      //
2085      // A Value is a comma-delimited list of Expressions
2086      //
2087      //     font-family: Baskerville, Georgia, serif;
2088      //
2089      // In a Rule, a Value represents everything after the `:`,
2090      // and before the `;`.
2091      //
2092  	private function parseValue(){
2093          $expressions = array();
2094  
2095          do{
2096              $e = $this->parseExpression();
2097              if( $e ){
2098                  $expressions[] = $e;
2099                  if (! $this->MatchChar(',')) {
2100                      break;
2101                  }
2102              }
2103          }while($e);
2104  
2105          if( $expressions ){
2106              return $this->NewObj1('Less_Tree_Value',$expressions);
2107          }
2108      }
2109  
2110  	private function parseImportant (){
2111          if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){
2112              return ' !important';
2113          }
2114      }
2115  
2116  	private function parseSub (){
2117  
2118          if( $this->MatchChar('(') ){
2119              $a = $this->parseAddition();
2120              if( $a ){
2121                  $this->expectChar(')');
2122                  return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached
2123              }
2124          }
2125      }
2126  
2127  
2128      /**
2129       * Parses multiplication operation
2130       *
2131       * @return Less_Tree_Operation|null
2132       */
2133  	function parseMultiplication(){
2134  
2135          $return = $m = $this->parseOperand();
2136          if( $return ){
2137              while( true ){
2138  
2139                  $isSpaced = $this->isWhitespace( -1 );
2140  
2141                  if( $this->PeekReg('/\\G\/[*\/]/') ){
2142                      break;
2143                  }
2144  
2145                  $op = $this->MatchChar('/');
2146                  if( !$op ){
2147                      $op = $this->MatchChar('*');
2148                      if( !$op ){
2149                          break;
2150                      }
2151                  }
2152  
2153                  $a = $this->parseOperand();
2154  
2155                  if(!$a) { break; }
2156  
2157                  $m->parensInOp = true;
2158                  $a->parensInOp = true;
2159                  $return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) );
2160              }
2161          }
2162          return $return;
2163  
2164      }
2165  
2166  
2167      /**
2168       * Parses an addition operation
2169       *
2170       * @return Less_Tree_Operation|null
2171       */
2172  	private function parseAddition (){
2173  
2174          $return = $m = $this->parseMultiplication();
2175          if( $return ){
2176              while( true ){
2177  
2178                  $isSpaced = $this->isWhitespace( -1 );
2179  
2180                  $op = $this->MatchReg('/\\G[-+]\s+/');
2181                  if( $op ){
2182                      $op = $op[0];
2183                  }else{
2184                      if( !$isSpaced ){
2185                          $op = $this->match(array('#+','#-'));
2186                      }
2187                      if( !$op ){
2188                          break;
2189                      }
2190                  }
2191  
2192                  $a = $this->parseMultiplication();
2193                  if( !$a ){
2194                      break;
2195                  }
2196  
2197                  $m->parensInOp = true;
2198                  $a->parensInOp = true;
2199                  $return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced));
2200              }
2201          }
2202  
2203          return $return;
2204      }
2205  
2206  
2207      /**
2208       * Parses the conditions
2209       *
2210       * @return Less_Tree_Condition|null
2211       */
2212  	private function parseConditions() {
2213          $index = $this->pos;
2214          $return = $a = $this->parseCondition();
2215          if( $a ){
2216              while( true ){
2217                  if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') ||  !$this->MatchChar(',') ){
2218                      break;
2219                  }
2220                  $b = $this->parseCondition();
2221                  if( !$b ){
2222                      break;
2223                  }
2224  
2225                  $return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index));
2226              }
2227              return $return;
2228          }
2229      }
2230  
2231  	private function parseCondition() {
2232          $index = $this->pos;
2233          $negate = false;
2234          $c = null;
2235  
2236          if ($this->MatchReg('/\\Gnot/')) $negate = true;
2237          $this->expectChar('(');
2238          $a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
2239  
2240          if( $a ){
2241              $op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/');
2242              if( $op ){
2243                  $b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted'));
2244                  if( $b ){
2245                      $c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate));
2246                  } else {
2247                      $this->Error('Unexpected expression');
2248                  }
2249              } else {
2250                  $k = $this->NewObj1('Less_Tree_Keyword','true');
2251                  $c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate));
2252              }
2253              $this->expectChar(')');
2254              return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c;
2255          }
2256      }
2257  
2258      /**
2259       * An operand is anything that can be part of an operation,
2260       * such as a Color, or a Variable
2261       *
2262       */
2263  	private function parseOperand (){
2264  
2265          $negate = false;
2266          $offset = $this->pos+1;
2267          if( $offset >= $this->input_len ){
2268              return;
2269          }
2270          $char = $this->input[$offset];
2271          if( $char === '@' || $char === '(' ){
2272              $negate = $this->MatchChar('-');
2273          }
2274  
2275          $o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall'));
2276  
2277          if( $negate ){
2278              $o->parensInOp = true;
2279              $o = $this->NewObj1('Less_Tree_Negative',$o);
2280          }
2281  
2282          return $o;
2283      }
2284  
2285  
2286      /**
2287       * Expressions either represent mathematical operations,
2288       * or white-space delimited Entities.
2289       *
2290       *     1px solid black
2291       *     @var * 2
2292       *
2293       * @return Less_Tree_Expression|null
2294       */
2295  	private function parseExpression (){
2296          $entities = array();
2297  
2298          do{
2299              $e = $this->MatchFuncs(array('parseAddition','parseEntity'));
2300              if( $e ){
2301                  $entities[] = $e;
2302                  // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
2303                  if( !$this->PeekReg('/\\G\/[\/*]/') ){
2304                      $delim = $this->MatchChar('/');
2305                      if( $delim ){
2306                          $entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim);
2307                      }
2308                  }
2309              }
2310          }while($e);
2311  
2312          if( $entities ){
2313              return $this->NewObj1('Less_Tree_Expression',$entities);
2314          }
2315      }
2316  
2317  
2318      /**
2319       * Parse a property
2320       * eg: 'min-width', 'orientation', etc
2321       *
2322       * @return string
2323       */
2324  	private function parseProperty (){
2325          $name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/');
2326          if( $name ){
2327              return $name[1];
2328          }
2329      }
2330  
2331  
2332      /**
2333       * Parse a rule property
2334       * eg: 'color', 'width', 'height', etc
2335       *
2336       * @return string
2337       */
2338  	private function parseRuleProperty(){
2339          $offset = $this->pos;
2340          $name = array();
2341          $index = array();
2342          $length = 0;
2343  
2344  
2345          $this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name );
2346          while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // !
2347  
2348          if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){
2349              // at last, we have the complete match now. move forward,
2350              // convert name particles to tree objects and return:
2351              $this->skipWhitespace($length);
2352  
2353              if( $name[0] === '' ){
2354                  array_shift($name);
2355                  array_shift($index);
2356              }
2357              foreach($name as $k => $s ){
2358                  if( !$s || $s[0] !== '@' ){
2359                      $name[$k] = $this->NewObj1('Less_Tree_Keyword',$s);
2360                  }else{
2361                      $name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo));
2362                  }
2363              }
2364              return $name;
2365          }
2366  
2367  
2368      }
2369  
2370  	private function rulePropertyMatch( $re, &$offset, &$length,  &$index, &$name ){
2371          preg_match($re, $this->input, $a, 0, $offset);
2372          if( $a ){
2373              $index[] = $this->pos + $length;
2374              $length += strlen($a[0]);
2375              $offset += strlen($a[0]);
2376              $name[] = $a[1];
2377              return true;
2378          }
2379      }
2380  
2381  	public function serializeVars( $vars ){
2382          $s = '';
2383  
2384          foreach($vars as $name => $value){
2385              $s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';');
2386          }
2387  
2388          return $s;
2389      }
2390  
2391  
2392      /**
2393       * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
2394       *
2395       * @param string $b
2396       */
2397  	public static function is_method($a,$b){
2398          return is_object($a) && method_exists($a,$b);
2399      }
2400  
2401  
2402      /**
2403       * Round numbers similarly to javascript
2404       * eg: 1.499999 to 1 instead of 2
2405       *
2406       */
2407  	public static function round($i, $precision = 0){
2408  
2409          $precision = pow(10,$precision);
2410          $i = $i*$precision;
2411  
2412          $ceil = ceil($i);
2413          $floor = floor($i);
2414          if( ($ceil - $i) <= ($i - $floor) ){
2415              return $ceil/$precision;
2416          }else{
2417              return $floor/$precision;
2418          }
2419      }
2420  
2421  
2422      /**
2423       * Create Less_Tree_* objects and optionally generate a cache string
2424       *
2425       * @return mixed
2426       */
2427  	public function NewObj0($class){
2428          $obj = new $class();
2429          if( Less_Cache::$cache_dir ){
2430              $obj->cache_string = ' new '.$class.'()';
2431          }
2432          return $obj;
2433      }
2434  
2435  	public function NewObj1($class, $arg){
2436          $obj = new $class( $arg );
2437          if( Less_Cache::$cache_dir ){
2438              $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')';
2439          }
2440          return $obj;
2441      }
2442  
2443  	public function NewObj2($class, $args){
2444          $obj = new $class( $args[0], $args[1] );
2445          if( Less_Cache::$cache_dir ){
2446              $this->ObjCache( $obj, $class, $args);
2447          }
2448          return $obj;
2449      }
2450  
2451  	public function NewObj3($class, $args){
2452          $obj = new $class( $args[0], $args[1], $args[2] );
2453          if( Less_Cache::$cache_dir ){
2454              $this->ObjCache( $obj, $class, $args);
2455          }
2456          return $obj;
2457      }
2458  
2459  	public function NewObj4($class, $args){
2460          $obj = new $class( $args[0], $args[1], $args[2], $args[3] );
2461          if( Less_Cache::$cache_dir ){
2462              $this->ObjCache( $obj, $class, $args);
2463          }
2464          return $obj;
2465      }
2466  
2467  	public function NewObj5($class, $args){
2468          $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
2469          if( Less_Cache::$cache_dir ){
2470              $this->ObjCache( $obj, $class, $args);
2471          }
2472          return $obj;
2473      }
2474  
2475  	public function NewObj6($class, $args){
2476          $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
2477          if( Less_Cache::$cache_dir ){
2478              $this->ObjCache( $obj, $class, $args);
2479          }
2480          return $obj;
2481      }
2482  
2483  	public function NewObj7($class, $args){
2484          $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
2485          if( Less_Cache::$cache_dir ){
2486              $this->ObjCache( $obj, $class, $args);
2487          }
2488          return $obj;
2489      }
2490  
2491      //caching
2492  	public function ObjCache($obj, $class, $args=array()){
2493          $obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')';
2494      }
2495  
2496  	public function ArgCache($args){
2497          return implode(',',array_map( array('Less_Parser','ArgString'),$args));
2498      }
2499  
2500  
2501      /**
2502       * Convert an argument to a string for use in the parser cache
2503       *
2504       * @return string
2505       */
2506  	public static function ArgString($arg){
2507  
2508          $type = gettype($arg);
2509  
2510          if( $type === 'object'){
2511              $string = $arg->cache_string;
2512              unset($arg->cache_string);
2513              return $string;
2514  
2515          }elseif( $type === 'array' ){
2516              $string = ' Array(';
2517              foreach($arg as $k => $a){
2518                  $string .= var_export($k,true).' => '.self::ArgString($a).',';
2519              }
2520              return $string . ')';
2521          }
2522  
2523          return var_export($arg,true);
2524      }
2525  
2526  	public function Error($msg){
2527          throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo);
2528      }
2529  
2530  	public static function WinPath($path){
2531          return str_replace('\\', '/', $path);
2532      }
2533  
2534  }
2535  
2536  


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1