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