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