[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/libs/ -> lessc.inc.php (source)

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


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1