MediaWiki  1.23.6
lessc.inc.php
Go to the documentation of this file.
1 <?php
2 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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
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 
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 
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 
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 
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 
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 
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 
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 {
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 
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 
3790  public $disableSingle = true;
3791  public $breakSelectors = true;
3792  public $assignSeparator = ": ";
3793  public $selectorSeparator = ",";
3794 }
3795 
3796 
lessc_parser::guardGroup
guardGroup(&$guardGroup)
Definition: lessc.inc.php:3421
lessc::multiplySelectors
multiplySelectors($selectors)
Definition: lessc.inc.php:473
lessc_formatter_classic::$disableSingle
$disableSingle
Definition: lessc.inc.php:3693
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. $title:Title object for the current page $request:WebRequest $ignoreRedirect:boolean to skip redirect check $target:Title/string of redirect target $article:Article object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) $article:article(object) being checked 'IsTrustedProxy':Override the result of wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of User::isValidEmailAddr(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetMagic':DEPRECATED, use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetSpecialPageAliases':DEPRECATED, use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1528
lessc_formatter_lessjs::$assignSeparator
$assignSeparator
Definition: lessc.inc.php:3792
lessc::findClosestSelectors
findClosestSelectors()
Definition: lessc.inc.php:457
lessc::lib_lightness
lib_lightness($color)
Definition: lessc.inc.php:1205
lessc::lib_luma
lib_luma($color)
Definition: lessc.inc.php:1302
lessc::compileNestedBlock
compileNestedBlock($block, $selectors)
Definition: lessc.inc.php:291
lessc::$importDir
$importDir
Definition: lessc.inc.php:83
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
lessc_parser::func
func(&$func)
Definition: lessc.inc.php:3305
lessc_parser::whitespace
whitespace()
Definition: lessc.inc.php:3531
lessc::cexecute
static cexecute($in, $force=false, $less=null)
Definition: lessc.inc.php:2139
$mime
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string $mime
Definition: hooks.txt:2573
lessc::$sourceLoc
$sourceLoc
Definition: lessc.inc.php:92
lessc::lib_saturate
lib_saturate($args)
Definition: lessc.inc.php:1156
lessc::toHSL
toHSL($color)
Definition: lessc.inc.php:1338
lessc_parser::$commentMultiRight
static $commentMultiRight
Definition: lessc.inc.php:2322
lessc_parser::$literalCache
static $literalCache
Definition: lessc.inc.php:2346
lessc_parser::mediaQueryList
mediaQueryList(&$out)
Definition: lessc.inc.php:2812
lessc::addImportDir
addImportDir($dir)
Definition: lessc.inc.php:2107
lessc_parser::expression
expression(&$out)
Attempt to consume an expression.
Definition: lessc.inc.php:2625
lessc_parser::__construct
__construct($lessc, $sourceName=null)
Definition: lessc.inc.php:2348
lessc::toBool
toBool($a)
Definition: lessc.inc.php:1640
lessc::preg_quote
static preg_quote($what)
Definition: lessc.inc.php:117
lessc::lib_isrem
lib_isrem($value)
Definition: lessc.inc.php:996
lessc::mediaParent
mediaParent($scope)
Definition: lessc.inc.php:280
lessc_parser::parseChunk
parseChunk()
Parse a single chunk off the head of the buffer and append it to the current parse environment.
Definition: lessc.inc.php:2437
lessc::compileFile
compileFile($fname, $outFname=null)
Definition: lessc.inc.php:1936
lessc_parser::genericList
genericList(&$out, $parseItem, $delim="", $flatten=true)
Definition: lessc.inc.php:3477
lessc::lib_percentage
lib_percentage($arg)
Definition: lessc.inc.php:1225
$last
$last
Definition: profileinfo.php:365
lessc::pushEnv
pushEnv($block=null)
Definition: lessc.inc.php:1839
lessc::toRGB
toRGB($color)
Converts a hsl array into a color value in rgb.
Definition: lessc.inc.php:1388
lessc::$sourceParser
$sourceParser
Definition: lessc.inc.php:91
lessc_parser::$commentMultiLeft
static $commentMultiLeft
Definition: lessc.inc.php:2321
lessc_formatter_classic::isEmpty
isEmpty($block)
Definition: lessc.inc.php:3710
lessc::makeParser
makeParser($name)
Definition: lessc.inc.php:2061
lessc_parser::tagBracket
tagBracket(&$parts, &$hasExpression)
Definition: lessc.inc.php:3171
lessc::popEnv
popEnv()
Definition: lessc.inc.php:1850
$f
$f
Definition: UtfNormalTest2.php:38
lessc::compileMedia
compileMedia($media)
Definition: lessc.inc.php:255
lessc_parser::$supressDivisionProps
static $supressDivisionProps
Definition: lessc.inc.php:2328
lessc::findBlocks
findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array())
Definition: lessc.inc.php:623
lessc::clamp
clamp($v, $max=1, $min=0)
Definition: lessc.inc.php:1415
lessc::op_number_number
op_number_number($op, $left, $right)
Definition: lessc.inc.php:1789
lessc_formatter_classic::$break
$break
Definition: lessc.inc.php:3684
lessc::checkedCompile
checkedCompile($in, $out)
Definition: lessc.inc.php:1962
lessc::setPreserveComments
setPreserveComments($preserve)
Definition: lessc.inc.php:2083
lessc_parser::pushSpecialBlock
pushSpecialBlock($type)
Definition: lessc.inc.php:3605
lessc_parser::tags
tags(&$tags, $simple=false, $delim=',')
Definition: lessc.inc.php:3145
lessc::lib_mix
lib_mix($args)
Definition: lessc.inc.php:1233
lessc::setImportDir
setImportDir($dirs)
Definition: lessc.inc.php:2103
$n
$n
Definition: RandomTest.php:76
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1530
lessc_formatter_classic::indentStr
indentStr($n=0)
Definition: lessc.inc.php:3702
$from
$from
Definition: importImages.php:90
lessc::lib_alpha
lib_alpha($value)
Definition: lessc.inc.php:1212
lessc_parser::propertyValue
propertyValue(&$value, $keyName=null)
Definition: lessc.inc.php:2697
lessc::lib_iscolor
lib_iscolor($value)
Definition: lessc.inc.php:976
lessc::injectVariables
injectVariables($args)
Definition: lessc.inc.php:1884
lessc::$libFunctions
$libFunctions
Definition: lessc.inc.php:74
$right
return false if a UserGetRights hook might remove the named right $right
Definition: hooks.txt:2798
lessc::lib_asin
lib_asin($num)
Definition: lessc.inc.php:945
lessc::lib_isnumber
lib_isnumber($value)
Definition: lessc.inc.php:968
lessc::compileMediaQuery
compileMediaQuery($queries)
Definition: lessc.inc.php:383
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:35
lessc::$cssColors
static $cssColors
Definition: lessc.inc.php:2146
lessc::lib__sprintf
lib__sprintf($args)
Definition: lessc.inc.php:1066
lessc_parser::string
string(&$out)
Definition: lessc.inc.php:2941
lessc_parser::$nextBlockId
static $nextBlockId
Definition: lessc.inc.php:2301
lessc::lib_lighten
lib_lighten($args)
Definition: lessc.inc.php:1148
$s
$s
Definition: mergeMessageFileList.php:156
lessc::reduce
reduce($value, $forExpression=false)
Definition: lessc.inc.php:1475
lessc_parser::peek
peek($regex, &$out=null, $from=null)
Definition: lessc.inc.php:3550
lessc::flattenList
flattenList($value)
Definition: lessc.inc.php:1633
lessc_formatter_classic::$close
$close
Definition: lessc.inc.php:3686
lessc_parser::interpolation
interpolation(&$out)
Definition: lessc.inc.php:2992
lessc::lib_sqrt
lib_sqrt($num)
Definition: lessc.inc.php:955
lessc::set
set($name, $value)
Definition: lessc.inc.php:1857
Makefile.open
open
Definition: Makefile.py:14
lessc::toRGB_helper
toRGB_helper($comp, $temp1, $temp2)
Definition: lessc.inc.php:1373
lessc_formatter_lessjs::$breakSelectors
$breakSelectors
Definition: lessc.inc.php:3791
lessc::registerFunction
registerFunction($name, $func)
Definition: lessc.inc.php:2087
lessc::funcToColor
funcToColor($func)
Convert the rgb, rgba, hsl color literals of function type as returned by the parser into values of c...
Definition: lessc.inc.php:1423
lessc::lib_fadeout
lib_fadeout($args)
Definition: lessc.inc.php:1183
lessc::fixColor
fixColor($c)
Definition: lessc.inc.php:1708
lessc::lib_darken
lib_darken($args)
Definition: lessc.inc.php:1140
lessc::unregisterFunction
unregisterFunction($name)
Definition: lessc.inc.php:2091
$in
$in
Definition: Utf8Test.php:42
lessc_formatter_classic::$breakSelectors
$breakSelectors
Definition: lessc.inc.php:3694
lessc_parser::openString
openString($end, &$out, $nestingOpen=null, $rejectStrs=null)
Definition: lessc.inc.php:2874
lessc_parser::$inParens
$inParens
if we are in parens we can be more liberal with whitespace around operators because it must evaluate ...
Definition: lessc.inc.php:2343
lessc::$numberPrecision
$numberPrecision
Definition: lessc.inc.php:85
lessc::lib_pi
lib_pi()
Definition: lessc.inc.php:919
lessc::zipSetArgs
zipSetArgs($args, $orderedValues, $keywordValues)
Definition: lessc.inc.php:661
lessc::assertColor
assertColor($value, $error="expected color value")
Definition: lessc.inc.php:1308
lessc_parser::throwError
throwError($msg="parse error", $count=null)
Definition: lessc.inc.php:3567
lessc_parser::assign
assign($name=null)
Consume an assignment operator Can optionally take a name that will be set to the current property na...
Definition: lessc.inc.php:3370
lessc_parser::seek
seek($where=null)
Definition: lessc.inc.php:3559
lessc::stringConcatenate
stringConcatenate($left, $right)
Definition: lessc.inc.php:1691
lessc::compileBlock
compileBlock($block)
Recursively compiles a block.
Definition: lessc.inc.php:217
lessc_parser::argumentDef
argumentDef(&$args, &$isVararg)
Definition: lessc.inc.php:3045
lessc::lib_pow
lib_pow($args)
Definition: lessc.inc.php:914
lessc::eq
eq($left, $right)
Definition: lessc.inc.php:519
lessc::coerceString
coerceString($value)
Definition: lessc.inc.php:1622
lessc::lib_fade
lib_fade($args)
Definition: lessc.inc.php:1219
lessc_parser::match
match($regex, &$out, $eatWhitespace=null)
Definition: lessc.inc.php:3518
lessc::lib_atan
lib_atan($num)
Definition: lessc.inc.php:940
lessc::assertNumber
assertNumber($value, $error="expecting number")
Definition: lessc.inc.php:1314
lessc::setVariables
setVariables($variables)
Definition: lessc.inc.php:2095
lessc::lib_desaturate
lib_desaturate($args)
Definition: lessc.inc.php:1164
lessc::parse
parse($str=null, $initialVariables=null)
Definition: lessc.inc.php:2036
$out
$out
Definition: UtfNormalGenerate.php:167
lessc::patternMatchAll
patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array())
Definition: lessc.inc.php:606
lessc::lib_saturation
lib_saturation($color)
Definition: lessc.inc.php:1200
lessc_formatter_classic::property
property($name, $value)
Definition: lessc.inc.php:3706
lessc::$importDisabled
$importDisabled
Definition: lessc.inc.php:82
lessc::lib_e
lib_e($arg)
Definition: lessc.inc.php:1048
lessc::multiplyMedia
multiplyMedia($env, $childQueries=null)
Definition: lessc.inc.php:419
lessc_parser::unit
unit(&$unit)
Definition: lessc.inc.php:3012
lessc::lib_acos
lib_acos($num)
Definition: lessc.inc.php:950
lessc::lib_iskeyword
lib_iskeyword($value)
Definition: lessc.inc.php:980
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:1956
$pre
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped $pre
Definition: hooks.txt:1105
lessc_parser::expressionList
expressionList(&$exps)
Definition: lessc.inc.php:2608
lessc::evaluate
evaluate($exp)
Definition: lessc.inc.php:1646
lessc::lib_floor
lib_floor($arg)
Definition: lessc.inc.php:1094
lessc::compile
compile($string, $name=null)
Definition: lessc.inc.php:1910
lessc_parser::literal
literal($what, $eatWhitespace=null)
Definition: lessc.inc.php:3454
lessc::newFormatter
newFormatter()
Definition: lessc.inc.php:2072
lessc::compressList
static compressList($items, $delim)
Definition: lessc.inc.php:112
lessc_formatter_compressed
Definition: lessc.inc.php:3776
$lines
$lines
Definition: router.php:65
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
lessc_parser::isDirective
isDirective($dirname, $directives)
Definition: lessc.inc.php:2589
lessc::setFormatter
setFormatter($name)
Definition: lessc.inc.php:2068
$dirs
$dirs
Definition: mergeMessageFileList.php:163
lessc::allParsedFiles
allParsedFiles()
Definition: lessc.inc.php:2112
lessc::op_color_number
op_color_number($op, $lft, $rgt)
Definition: lessc.inc.php:1723
lessc::assertArgs
assertArgs($value, $expectedArgs, $name="")
Definition: lessc.inc.php:1319
lessc_formatter_lessjs::$selectorSeparator
$selectorSeparator
Definition: lessc.inc.php:3793
lessc::cachedCompile
cachedCompile($in, $force=false)
Execute lessphp on a .less file or a lessphp cache structure.
Definition: lessc.inc.php:1990
lessc_formatter_classic::__construct
__construct()
Definition: lessc.inc.php:3698
lessc::lib_tan
lib_tan($num)
Definition: lessc.inc.php:928
lessc_parser::$commentMulti
static $commentMulti
Definition: lessc.inc.php:2318
lessc::deduplicate
deduplicate($lines)
Deduplicate lines in a block.
Definition: lessc.inc.php:321
lessc_parser::expHelper
expHelper($lhs, $minP)
recursively parse infix equation with $lhs at precedence $minP
Definition: lessc.inc.php:2649
lessc_parser::$whitePattern
static $whitePattern
Definition: lessc.inc.php:2317
lessc::compileProp
compileProp($prop, $block, $out)
Definition: lessc.inc.php:703
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
lessc::op_number_color
op_number_color($op, $lft, $rgt)
Definition: lessc.inc.php:1717
lessc_parser::guards
guards(&$guards)
Definition: lessc.inc.php:3395
lessc::compileValue
compileValue($value)
Compiles a primitive value into a CSS property value.
Definition: lessc.inc.php:850
lessc_formatter_compressed::$open
$open
Definition: lessc.inc.php:3778
lessc::makeOutputBlock
makeOutputBlock($type, $selectors=null)
Definition: lessc.inc.php:1828
lessc::fileExists
fileExists($name)
Definition: lessc.inc.php:108
lessc::lib_spin
lib_spin($args)
Definition: lessc.inc.php:1172
lessc_parser::$precedence
static $precedence
Definition: lessc.inc.php:2303
$line
$line
Definition: cdb.php:57
lessc_parser::fixTags
fixTags($tags)
Definition: lessc.inc.php:2598
lessc::throwError
throwError($msg=null)
Uses the current value of $this->count to show line and line number.
Definition: lessc.inc.php:2123
lessc::lib_isem
lib_isem($value)
Definition: lessc.inc.php:992
lessc_parser::pop
pop()
Definition: lessc.inc.php:3616
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
lessc::patternMatch
patternMatch($block, $orderedArgs, $keywordArgs)
Definition: lessc.inc.php:523
$matches
if(!defined( 'MEDIAWIKI')) if(!isset( $wgVersion)) $matches
Definition: NoLocalSettings.php:33
$value
$value
Definition: styleTest.css.php:45
lessc::lib_round
lib_round($arg)
Definition: lessc.inc.php:1104
lessc_parser::$blockDirectives
$blockDirectives
Definition: lessc.inc.php:2331
lessc::compileImportedProps
compileImportedProps($props, $block, $out, $sourceParser, $importDir)
Definition: lessc.inc.php:179
lessc_parser::parse
parse($buffer)
Definition: lessc.inc.php:2371
lessc_parser::$lineDirectives
$lineDirectives
Definition: lessc.inc.php:2332
lessc::expandParentSelectors
expandParentSelectors(&$tag, $replace)
Definition: lessc.inc.php:446
lessc::addParsedFile
addParsedFile($file)
Definition: lessc.inc.php:2116
lessc::lib_ispercentage
lib_ispercentage($value)
Definition: lessc.inc.php:988
lessc_formatter_classic::$selectorSeparator
$selectorSeparator
Definition: lessc.inc.php:3687
lessc::$mPrefix
$mPrefix
Definition: lessc.inc.php:79
lessc_parser::guard
guard(&$guard)
Definition: lessc.inc.php:3438
lessc::$preserveComments
$preserveComments
Definition: lessc.inc.php:76
lessc::compileRoot
compileRoot($root)
Definition: lessc.inc.php:302
lessc_parser::mediaExpression
mediaExpression(&$out)
Definition: lessc.inc.php:2853
lessc_parser::$commentSingle
static $commentSingle
Definition: lessc.inc.php:2320
lessc_parser::pushBlock
pushBlock($selectors=null, $type=null)
Definition: lessc.inc.php:3587
lessc_parser::removeComments
removeComments($text)
Definition: lessc.inc.php:3624
lessc_formatter_classic::$openSingle
$openSingle
Definition: lessc.inc.php:3690
lessc_formatter_classic::$open
$open
Definition: lessc.inc.php:3685
lessc_parser::append
append($prop, $pos=null)
Definition: lessc.inc.php:3610
lessc_parser::$operatorString
static $operatorString
Definition: lessc.inc.php:2325
lessc::$registeredVars
$registeredVars
Definition: lessc.inc.php:75
lessc::colorArgs
colorArgs($args)
Helper function to get arguments for color manipulation functions.
Definition: lessc.inc.php:1129
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
$count
$count
Definition: UtfNormalTest2.php:96
lessc_formatter_compressed::$assignSeparator
$assignSeparator
Definition: lessc.inc.php:3780
lessc::lib_blue
lib_blue($color)
Definition: lessc.inc.php:1778
lessc::lib_data_uri
lib_data_uri($value)
Given an url, decide whether to output a regular link or the base64-encoded contents of the file.
Definition: lessc.inc.php:1020
lessc::op_color_color
op_color_color($op, $left, $right)
Definition: lessc.inc.php:1730
lessc_formatter_classic::$indentChar
$indentChar
Definition: lessc.inc.php:3682
$args
if( $line===false) $args
Definition: cdb.php:62
lessc::sortProps
sortProps($props, $split=false)
Definition: lessc.inc.php:339
lessc::$FALSE
static $FALSE
Definition: lessc.inc.php:72
lessc::lib_hue
lib_hue($color)
Definition: lessc.inc.php:1195
lessc_parser::mediaQuery
mediaQuery(&$out)
Definition: lessc.inc.php:2820
lessc::__construct
__construct($fname=null)
Initialize any static state, can initialize parser for a file $opts isn't used yet.
Definition: lessc.inc.php:1903
$dir
if(count( $args)==0) $dir
Definition: importImages.php:49
lessc::lib_fadein
lib_fadein($args)
Definition: lessc.inc.php:1189
lessc_formatter_compressed::$selectorSeparator
$selectorSeparator
Definition: lessc.inc.php:3779
lessc_formatter_classic::block
block($block)
Definition: lessc.inc.php:3721
lessc::compileSelectors
compileSelectors($selectors)
Definition: lessc.inc.php:504
lessc::lib_rgbahex
lib_rgbahex($color)
Definition: lessc.inc.php:1000
lessc::$TRUE
static $TRUE
Definition: lessc.inc.php:71
lessc::ccompile
static ccompile($in, $out, $less=null)
Definition: lessc.inc.php:2132
lessc_formatter_compressed::$disableSingle
$disableSingle
Definition: lessc.inc.php:3777
lessc::$parentSelector
$parentSelector
Definition: lessc.inc.php:80
$path
$path
Definition: NoLocalSettings.php:35
lessc_formatter_lessjs
Definition: lessc.inc.php:3789
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
lessc::lib_extract
lib_extract($value)
Definition: lessc.inc.php:959
lessc::lib_ispixel
lib_ispixel($value)
Definition: lessc.inc.php:984
lessc::lib_red
lib_red($color)
Definition: lessc.inc.php:1760
lessc_parser
Definition: lessc.inc.php:2300
lessc::$vPrefix
$vPrefix
Definition: lessc.inc.php:78
lessc::$allParsedFiles
$allParsedFiles
Definition: lessc.inc.php:87
lessc::compileProps
compileProps($block, $out)
Definition: lessc.inc.php:309
lessc_formatter_compressed::$compressColors
$compressColors
Definition: lessc.inc.php:3782
lessc_parser::parenValue
parenValue(&$out)
Definition: lessc.inc.php:2719
lessc_formatter_compressed::$break
$break
Definition: lessc.inc.php:3781
lessc_formatter_classic::$compressColors
$compressColors
Definition: lessc.inc.php:3696
lessc
lessphp v0.4.0@2cc77e3c7b http://leafo.net/lessphp
Definition: lessc.inc.php:68
$t
$t
Definition: testCompression.php:65
lessc::get
get($name)
Definition: lessc.inc.php:1863
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:1679
lessc::lib_ceil
lib_ceil($arg)
Definition: lessc.inc.php:1099
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2573
$e
if( $useReadline) $e
Definition: eval.php:66
lessc::$nextImportId
static $nextImportId
Definition: lessc.inc.php:94
lessc_parser::to
to($what, &$out, $until=false, $allowNewline=false)
Definition: lessc.inc.php:3505
$query
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1105
lessc::coerceColor
coerceColor($value)
Definition: lessc.inc.php:1590
lessc_parser::end
end()
Definition: lessc.inc.php:3385
lessc::lib_contrast
lib_contrast($args)
Definition: lessc.inc.php:1269
lessc::$VERSION
static $VERSION
Definition: lessc.inc.php:69
lessc::lib_sin
lib_sin($num)
Definition: lessc.inc.php:932
lessc_parser::value
value(&$value)
Definition: lessc.inc.php:2744
lessc::lib_mod
lib_mod($args)
Definition: lessc.inc.php:923
lessc::lib_isstring
lib_isstring($value)
Definition: lessc.inc.php:972
$res
$res
Definition: database.txt:21
lessc::findImport
findImport($url)
Definition: lessc.inc.php:97
lessc::lib_green
lib_green($color)
Definition: lessc.inc.php:1769
lessc::lib_argb
lib_argb($color)
Definition: lessc.inc.php:1010
$queries
$queries
Definition: profileinfo.php:362
lessc_formatter_lessjs::$disableSingle
$disableSingle
Definition: lessc.inc.php:3790
lessc::lib_unit
lib_unit($arg)
Definition: lessc.inc.php:1115
lessc_parser::color
color(&$out)
Definition: lessc.inc.php:3027
lessc::unsetVariable
unsetVariable($name)
Definition: lessc.inc.php:2099
lessc_parser::keyword
keyword(&$word)
Definition: lessc.inc.php:3376
lessc_formatter_classic::$closeSingle
$closeSingle
Definition: lessc.inc.php:3691
lessc_parser::variable
variable(&$name)
Definition: lessc.inc.php:3348
lessc::lib_cos
lib_cos($num)
Definition: lessc.inc.php:936
line
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
lessc::tryImport
tryImport($importPath, $parentBlock, $out)
Definition: lessc.inc.php:121
lessc_parser::mixinTags
mixinTags(&$tags)
Definition: lessc.inc.php:3158
lessc_formatter_classic::$assignSeparator
$assignSeparator
Definition: lessc.inc.php:3688
lessc_parser::tag
tag(&$tag, $simple=false)
Definition: lessc.inc.php:3241
lessc_formatter_compressed::indentStr
indentStr($n=0)
Definition: lessc.inc.php:3784
lessc::compileCSSBlock
compileCSSBlock($block)
Definition: lessc.inc.php:241
lessc_formatter_classic
Definition: lessc.inc.php:3681
$type
$type
Definition: testCompression.php:46