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