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