18ba21522SJames Collins<?php 28ba21522SJames Collins 38ba21522SJames Collins/** 4*a92686aaSJames Collins * lessphp v0.7.1 58ba21522SJames Collins * http://leafo.net/lessphp 68ba21522SJames Collins * 78ba21522SJames Collins * LESS CSS compiler, adapted from http://lesscss.org 88ba21522SJames Collins * 98ba21522SJames Collins * Copyright 2013, Leaf Corcoran <leafot@gmail.com> 108ba21522SJames Collins * Copyright 2016, Marcus Schwarz <github@maswaba.de> 118ba21522SJames Collins * Copyright 2024, James Collins <james@stemmechanics.com.au> 128ba21522SJames Collins * Licensed under MIT or GPLv3, see LICENSE 138ba21522SJames Collins */ 148ba21522SJames Collins 158ba21522SJames Collins 168ba21522SJames Collins/** 178ba21522SJames Collins * The LESS compiler and parser. 188ba21522SJames Collins * 198ba21522SJames Collins * Converting LESS to CSS is a three stage process. The incoming file is parsed 208ba21522SJames Collins * by `lessc_parser` into a syntax tree, then it is compiled into another tree 218ba21522SJames Collins * representing the CSS structure by `lessc`. The CSS tree is fed into a 228ba21522SJames Collins * formatter, like `lessc_formatter` which then outputs CSS as a string. 238ba21522SJames Collins * 248ba21522SJames Collins * During the first compile, all values are *reduced*, which means that their 258ba21522SJames Collins * types are brought to the lowest form before being dump as strings. This 268ba21522SJames Collins * handles math equations, variable dereferences, and the like. 278ba21522SJames Collins * 288ba21522SJames Collins * The `parse` function of `lessc` is the entry point. 298ba21522SJames Collins * 308ba21522SJames Collins * In summary: 318ba21522SJames Collins * 328ba21522SJames Collins * The `lessc` class creates an instance of the parser, feeds it LESS code, 338ba21522SJames Collins * then transforms the resulting tree to a CSS tree. This class also holds the 348ba21522SJames Collins * evaluation context, such as all available mixins and variables at any given 358ba21522SJames Collins * time. 368ba21522SJames Collins * 378ba21522SJames Collins * The `lessc_parser` class is only concerned with parsing its input. 388ba21522SJames Collins * 398ba21522SJames Collins * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string, 408ba21522SJames Collins * handling things like indentation. 418ba21522SJames Collins */ 428ba21522SJames Collinsclass lessc { 43*a92686aaSJames Collins static public $VERSION = "v0.7.1"; 448ba21522SJames Collins 458ba21522SJames Collins static public $TRUE = array("keyword", "true"); 468ba21522SJames Collins static public $FALSE = array("keyword", "false"); 478ba21522SJames Collins 488ba21522SJames Collins protected $libFunctions = array(); 498ba21522SJames Collins protected $registeredVars = array(); 508ba21522SJames Collins protected $preserveComments = false; 518ba21522SJames Collins 528ba21522SJames Collins public $vPrefix = '@'; // prefix of abstract properties 538ba21522SJames Collins public $mPrefix = '$'; // prefix of abstract blocks 548ba21522SJames Collins public $parentSelector = '&'; 558ba21522SJames Collins 568ba21522SJames Collins static public $lengths = array( "px", "m", "cm", "mm", "in", "pt", "pc" ); 578ba21522SJames Collins static public $times = array( "s", "ms" ); 588ba21522SJames Collins static public $angles = array( "rad", "deg", "grad", "turn" ); 598ba21522SJames Collins 608ba21522SJames Collins static public $lengths_to_base = array( 1, 3779.52755906, 37.79527559, 3.77952756, 96, 1.33333333, 16 ); 618ba21522SJames Collins public $importDisabled = false; 628ba21522SJames Collins public $importDir = array(); 638ba21522SJames Collins 648ba21522SJames Collins protected $numberPrecision = null; 658ba21522SJames Collins 668ba21522SJames Collins protected $allParsedFiles = array(); 678ba21522SJames Collins 688ba21522SJames Collins // set to the parser that generated the current line when compiling 698ba21522SJames Collins // so we know how to create error messages 708ba21522SJames Collins protected $sourceParser = null; 718ba21522SJames Collins protected $sourceLoc = null; 728ba21522SJames Collins 738ba21522SJames Collins static protected $nextImportId = 0; // uniquely identify imports 748ba21522SJames Collins 75*a92686aaSJames Collins protected $parser; 76*a92686aaSJames Collins protected $env; 77*a92686aaSJames Collins protected $scope; 78*a92686aaSJames Collins protected $formatter; 79*a92686aaSJames Collins 808ba21522SJames Collins // attempts to find the path of an import url, returns null for css files 818ba21522SJames Collins protected function findImport($url) { 828ba21522SJames Collins foreach ((array)$this->importDir as $dir) { 838ba21522SJames Collins $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; 848ba21522SJames Collins if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { 858ba21522SJames Collins return $file; 868ba21522SJames Collins } 878ba21522SJames Collins } 888ba21522SJames Collins 898ba21522SJames Collins return null; 908ba21522SJames Collins } 918ba21522SJames Collins 928ba21522SJames Collins protected function fileExists($name) { 938ba21522SJames Collins return is_file($name); 948ba21522SJames Collins } 958ba21522SJames Collins 968ba21522SJames Collins static public function compressList($items, $delim) { 978ba21522SJames Collins if (!isset($items[1]) && isset($items[0])) return $items[0]; 988ba21522SJames Collins else return array('list', $delim, $items); 998ba21522SJames Collins } 1008ba21522SJames Collins 1018ba21522SJames Collins static public function preg_quote($what) { 1028ba21522SJames Collins return preg_quote($what, '/'); 1038ba21522SJames Collins } 1048ba21522SJames Collins 1058ba21522SJames Collins protected function tryImport($importPath, $parentBlock, $out) { 1068ba21522SJames Collins if ($importPath[0] == "function" && $importPath[1] == "url") { 1078ba21522SJames Collins $importPath = $this->flattenList($importPath[2]); 1088ba21522SJames Collins } 1098ba21522SJames Collins 1108ba21522SJames Collins $str = $this->coerceString($importPath); 1118ba21522SJames Collins if ($str === null) return false; 1128ba21522SJames Collins 1138ba21522SJames Collins $url = $this->compileValue($this->lib_e($str)); 1148ba21522SJames Collins 1158ba21522SJames Collins // don't import if it ends in css 1168ba21522SJames Collins if (substr_compare($url, '.css', -4, 4) === 0) return false; 1178ba21522SJames Collins 1188ba21522SJames Collins $realPath = $this->findImport($url); 1198ba21522SJames Collins 1208ba21522SJames Collins if ($realPath === null) return false; 1218ba21522SJames Collins 1228ba21522SJames Collins if ($this->importDisabled) { 1238ba21522SJames Collins return array(false, "/* import disabled */"); 1248ba21522SJames Collins } 1258ba21522SJames Collins 1268ba21522SJames Collins if (isset($this->allParsedFiles[realpath($realPath)])) { 1278ba21522SJames Collins return array(false, null); 1288ba21522SJames Collins } 1298ba21522SJames Collins 1308ba21522SJames Collins $this->addParsedFile($realPath); 1318ba21522SJames Collins $parser = $this->makeParser($realPath); 1328ba21522SJames Collins $root = $parser->parse(file_get_contents($realPath)); 1338ba21522SJames Collins 1348ba21522SJames Collins // set the parents of all the block props 1358ba21522SJames Collins foreach ($root->props as $prop) { 1368ba21522SJames Collins if ($prop[0] == "block") { 1378ba21522SJames Collins $prop[1]->parent = $parentBlock; 1388ba21522SJames Collins } 1398ba21522SJames Collins } 1408ba21522SJames Collins 1418ba21522SJames Collins // copy mixins into scope, set their parents 1428ba21522SJames Collins // bring blocks from import into current block 1438ba21522SJames Collins // TODO: need to mark the source parser these came from this file 1448ba21522SJames Collins foreach ($root->children as $childName => $child) { 1458ba21522SJames Collins if (isset($parentBlock->children[$childName])) { 1468ba21522SJames Collins $parentBlock->children[$childName] = array_merge( 1478ba21522SJames Collins $parentBlock->children[$childName], 1488ba21522SJames Collins $child); 1498ba21522SJames Collins } else { 1508ba21522SJames Collins $parentBlock->children[$childName] = $child; 1518ba21522SJames Collins } 1528ba21522SJames Collins } 1538ba21522SJames Collins 1548ba21522SJames Collins $pi = pathinfo($realPath); 1558ba21522SJames Collins $dir = $pi["dirname"]; 1568ba21522SJames Collins 1578ba21522SJames Collins [$top, $bottom] = $this->sortProps($root->props, true); 1588ba21522SJames Collins $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); 1598ba21522SJames Collins 1608ba21522SJames Collins return array(true, $bottom, $parser, $dir); 1618ba21522SJames Collins } 1628ba21522SJames Collins 1638ba21522SJames Collins protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { 1648ba21522SJames Collins $oldSourceParser = $this->sourceParser; 1658ba21522SJames Collins 1668ba21522SJames Collins $oldImport = $this->importDir; 1678ba21522SJames Collins 1688ba21522SJames Collins // TODO: this is because the importDir api is stupid 1698ba21522SJames Collins $this->importDir = (array)$this->importDir; 1708ba21522SJames Collins array_unshift($this->importDir, $importDir); 1718ba21522SJames Collins 1728ba21522SJames Collins foreach ($props as $prop) { 1738ba21522SJames Collins $this->compileProp($prop, $block, $out); 1748ba21522SJames Collins } 1758ba21522SJames Collins 1768ba21522SJames Collins $this->importDir = $oldImport; 1778ba21522SJames Collins $this->sourceParser = $oldSourceParser; 1788ba21522SJames Collins } 1798ba21522SJames Collins 1808ba21522SJames Collins /** 1818ba21522SJames Collins * Recursively compiles a block. 1828ba21522SJames Collins * 1838ba21522SJames Collins * A block is analogous to a CSS block in most cases. A single LESS document 1848ba21522SJames Collins * is encapsulated in a block when parsed, but it does not have parent tags 1858ba21522SJames Collins * so all of it's children appear on the root level when compiled. 1868ba21522SJames Collins * 1878ba21522SJames Collins * Blocks are made up of props and children. 1888ba21522SJames Collins * 1898ba21522SJames Collins * Props are property instructions, array tuples which describe an action 1908ba21522SJames Collins * to be taken, eg. write a property, set a variable, mixin a block. 1918ba21522SJames Collins * 1928ba21522SJames Collins * The children of a block are just all the blocks that are defined within. 1938ba21522SJames Collins * This is used to look up mixins when performing a mixin. 1948ba21522SJames Collins * 1958ba21522SJames Collins * Compiling the block involves pushing a fresh environment on the stack, 1968ba21522SJames Collins * and iterating through the props, compiling each one. 1978ba21522SJames Collins * 1988ba21522SJames Collins * See lessc::compileProp() 1998ba21522SJames Collins * 2008ba21522SJames Collins */ 2018ba21522SJames Collins protected function compileBlock($block) { 2028ba21522SJames Collins switch ($block->type) { 2038ba21522SJames Collins case "root": 2048ba21522SJames Collins $this->compileRoot($block); 2058ba21522SJames Collins break; 2068ba21522SJames Collins case null: 2078ba21522SJames Collins $this->compileCSSBlock($block); 2088ba21522SJames Collins break; 2098ba21522SJames Collins case "media": 2108ba21522SJames Collins $this->compileMedia($block); 2118ba21522SJames Collins break; 2128ba21522SJames Collins case "directive": 2138ba21522SJames Collins $name = "@" . $block->name; 2148ba21522SJames Collins if (!empty($block->value)) { 2158ba21522SJames Collins $name .= " " . $this->compileValue($this->reduce($block->value)); 2168ba21522SJames Collins } 2178ba21522SJames Collins 2188ba21522SJames Collins $this->compileNestedBlock($block, array($name)); 2198ba21522SJames Collins break; 2208ba21522SJames Collins default: 2218ba21522SJames Collins $block->parser->throwError("unknown block type: $block->type\n", $block->count); 2228ba21522SJames Collins } 2238ba21522SJames Collins } 2248ba21522SJames Collins 2258ba21522SJames Collins protected function compileCSSBlock($block) { 2268ba21522SJames Collins $env = $this->pushEnv(); 2278ba21522SJames Collins 2288ba21522SJames Collins $selectors = $this->compileSelectors($block->tags); 2298ba21522SJames Collins $env->selectors = $this->multiplySelectors($selectors); 2308ba21522SJames Collins $out = $this->makeOutputBlock(null, $env->selectors); 2318ba21522SJames Collins 2328ba21522SJames Collins $this->scope->children[] = $out; 2338ba21522SJames Collins $this->compileProps($block, $out); 2348ba21522SJames Collins 2358ba21522SJames Collins $block->scope = $env; // mixins carry scope with them! 2368ba21522SJames Collins $this->popEnv(); 2378ba21522SJames Collins } 2388ba21522SJames Collins 2398ba21522SJames Collins protected function compileMedia($media) { 2408ba21522SJames Collins $env = $this->pushEnv($media); 2418ba21522SJames Collins $parentScope = $this->mediaParent($this->scope); 2428ba21522SJames Collins 2438ba21522SJames Collins $query = $this->compileMediaQuery($this->multiplyMedia($env)); 2448ba21522SJames Collins 2458ba21522SJames Collins $this->scope = $this->makeOutputBlock($media->type, array($query)); 2468ba21522SJames Collins $parentScope->children[] = $this->scope; 2478ba21522SJames Collins 2488ba21522SJames Collins $this->compileProps($media, $this->scope); 2498ba21522SJames Collins 2508ba21522SJames Collins if (count($this->scope->lines) > 0) { 2518ba21522SJames Collins $orphanSelelectors = $this->findClosestSelectors(); 2528ba21522SJames Collins if (!is_null($orphanSelelectors)) { 2538ba21522SJames Collins $orphan = $this->makeOutputBlock(null, $orphanSelelectors); 2548ba21522SJames Collins $orphan->lines = $this->scope->lines; 2558ba21522SJames Collins array_unshift($this->scope->children, $orphan); 2568ba21522SJames Collins $this->scope->lines = array(); 2578ba21522SJames Collins } 2588ba21522SJames Collins } 2598ba21522SJames Collins 2608ba21522SJames Collins $this->scope = $this->scope->parent; 2618ba21522SJames Collins $this->popEnv(); 2628ba21522SJames Collins } 2638ba21522SJames Collins 2648ba21522SJames Collins protected function mediaParent($scope) { 2658ba21522SJames Collins while (!empty($scope->parent)) { 2668ba21522SJames Collins if (!empty($scope->type) && $scope->type != "media") { 2678ba21522SJames Collins break; 2688ba21522SJames Collins } 2698ba21522SJames Collins $scope = $scope->parent; 2708ba21522SJames Collins } 2718ba21522SJames Collins 2728ba21522SJames Collins return $scope; 2738ba21522SJames Collins } 2748ba21522SJames Collins 2758ba21522SJames Collins protected function compileNestedBlock($block, $selectors) { 2768ba21522SJames Collins $this->pushEnv($block); 2778ba21522SJames Collins $this->scope = $this->makeOutputBlock($block->type, $selectors); 2788ba21522SJames Collins $this->scope->parent->children[] = $this->scope; 2798ba21522SJames Collins 2808ba21522SJames Collins $this->compileProps($block, $this->scope); 2818ba21522SJames Collins 2828ba21522SJames Collins $this->scope = $this->scope->parent; 2838ba21522SJames Collins $this->popEnv(); 2848ba21522SJames Collins } 2858ba21522SJames Collins 2868ba21522SJames Collins protected function compileRoot($root) { 2878ba21522SJames Collins $this->pushEnv(); 2888ba21522SJames Collins $this->scope = $this->makeOutputBlock($root->type); 2898ba21522SJames Collins $this->compileProps($root, $this->scope); 2908ba21522SJames Collins $this->popEnv(); 2918ba21522SJames Collins } 2928ba21522SJames Collins 2938ba21522SJames Collins protected function compileProps($block, $out) { 2948ba21522SJames Collins foreach ($this->sortProps($block->props) as $prop) { 2958ba21522SJames Collins $this->compileProp($prop, $block, $out); 2968ba21522SJames Collins } 2978ba21522SJames Collins $out->lines = $this->deduplicate($out->lines); 2988ba21522SJames Collins } 2998ba21522SJames Collins 3008ba21522SJames Collins /** 3018ba21522SJames Collins * Deduplicate lines in a block. Comments are not deduplicated. If a 3028ba21522SJames Collins * duplicate rule is detected, the comments immediately preceding each 3038ba21522SJames Collins * occurence are consolidated. 3048ba21522SJames Collins */ 3058ba21522SJames Collins protected function deduplicate($lines) { 3068ba21522SJames Collins $unique = array(); 3078ba21522SJames Collins $comments = array(); 3088ba21522SJames Collins 3098ba21522SJames Collins foreach($lines as $line) { 3108ba21522SJames Collins if (strpos($line, '/*') === 0) { 3118ba21522SJames Collins $comments[] = $line; 3128ba21522SJames Collins continue; 3138ba21522SJames Collins } 3148ba21522SJames Collins if (!in_array($line, $unique)) { 3158ba21522SJames Collins $unique[] = $line; 3168ba21522SJames Collins } 3178ba21522SJames Collins array_splice($unique, array_search($line, $unique), 0, $comments); 3188ba21522SJames Collins $comments = array(); 3198ba21522SJames Collins } 3208ba21522SJames Collins return array_merge($unique, $comments); 3218ba21522SJames Collins } 3228ba21522SJames Collins 3238ba21522SJames Collins protected function sortProps($props, $split = false) { 3248ba21522SJames Collins $vars = array(); 3258ba21522SJames Collins $imports = array(); 3268ba21522SJames Collins $other = array(); 3278ba21522SJames Collins $stack = array(); 3288ba21522SJames Collins 3298ba21522SJames Collins foreach ($props as $prop) { 3308ba21522SJames Collins switch ($prop[0]) { 3318ba21522SJames Collins case "comment": 3328ba21522SJames Collins $stack[] = $prop; 3338ba21522SJames Collins break; 3348ba21522SJames Collins case "assign": 3358ba21522SJames Collins $stack[] = $prop; 3368ba21522SJames Collins if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { 3378ba21522SJames Collins $vars = array_merge($vars, $stack); 3388ba21522SJames Collins } else { 3398ba21522SJames Collins $other = array_merge($other, $stack); 3408ba21522SJames Collins } 3418ba21522SJames Collins $stack = array(); 3428ba21522SJames Collins break; 3438ba21522SJames Collins case "import": 3448ba21522SJames Collins $id = self::$nextImportId++; 3458ba21522SJames Collins $prop[] = $id; 3468ba21522SJames Collins $stack[] = $prop; 3478ba21522SJames Collins $imports = array_merge($imports, $stack); 3488ba21522SJames Collins $other[] = array("import_mixin", $id); 3498ba21522SJames Collins $stack = array(); 3508ba21522SJames Collins break; 3518ba21522SJames Collins default: 3528ba21522SJames Collins $stack[] = $prop; 3538ba21522SJames Collins $other = array_merge($other, $stack); 3548ba21522SJames Collins $stack = array(); 3558ba21522SJames Collins break; 3568ba21522SJames Collins } 3578ba21522SJames Collins } 3588ba21522SJames Collins $other = array_merge($other, $stack); 3598ba21522SJames Collins 3608ba21522SJames Collins if ($split) { 3618ba21522SJames Collins return array(array_merge($vars, $imports, $vars), $other); 3628ba21522SJames Collins } else { 3638ba21522SJames Collins return array_merge($vars, $imports, $vars, $other); 3648ba21522SJames Collins } 3658ba21522SJames Collins } 3668ba21522SJames Collins 3678ba21522SJames Collins protected function compileMediaQuery($queries) { 3688ba21522SJames Collins $compiledQueries = array(); 3698ba21522SJames Collins foreach ($queries as $query) { 3708ba21522SJames Collins $parts = array(); 3718ba21522SJames Collins foreach ($query as $q) { 3728ba21522SJames Collins switch ($q[0]) { 3738ba21522SJames Collins case "mediaType": 3748ba21522SJames Collins $parts[] = implode(" ", array_slice($q, 1)); 3758ba21522SJames Collins break; 3768ba21522SJames Collins case "mediaExp": 3778ba21522SJames Collins if (isset($q[2])) { 3788ba21522SJames Collins $parts[] = "($q[1]: " . 3798ba21522SJames Collins $this->compileValue($this->reduce($q[2])) . ")"; 3808ba21522SJames Collins } else { 3818ba21522SJames Collins $parts[] = "($q[1])"; 3828ba21522SJames Collins } 3838ba21522SJames Collins break; 3848ba21522SJames Collins case "variable": 3858ba21522SJames Collins $parts[] = $this->compileValue($this->reduce($q)); 3868ba21522SJames Collins break; 3878ba21522SJames Collins } 3888ba21522SJames Collins } 3898ba21522SJames Collins 3908ba21522SJames Collins if (count($parts) > 0) { 3918ba21522SJames Collins $compiledQueries[] = implode(" and ", $parts); 3928ba21522SJames Collins } 3938ba21522SJames Collins } 3948ba21522SJames Collins 3958ba21522SJames Collins $out = "@media"; 3968ba21522SJames Collins if (!empty($parts)) { 3978ba21522SJames Collins $out .= " " . 3988ba21522SJames Collins implode($this->formatter->selectorSeparator, $compiledQueries); 3998ba21522SJames Collins } 4008ba21522SJames Collins return $out; 4018ba21522SJames Collins } 4028ba21522SJames Collins 4038ba21522SJames Collins protected function multiplyMedia($env, $childQueries = null) { 4048ba21522SJames Collins if (is_null($env) || 4058ba21522SJames Collins !empty($env->block->type) && $env->block->type != "media") 4068ba21522SJames Collins { 4078ba21522SJames Collins return $childQueries; 4088ba21522SJames Collins } 4098ba21522SJames Collins 4108ba21522SJames Collins // plain old block, skip 4118ba21522SJames Collins if (empty($env->block->type)) { 4128ba21522SJames Collins return $this->multiplyMedia($env->parent, $childQueries); 4138ba21522SJames Collins } 4148ba21522SJames Collins 4158ba21522SJames Collins $out = array(); 4168ba21522SJames Collins $queries = $env->block->queries; 4178ba21522SJames Collins if (is_null($childQueries)) { 4188ba21522SJames Collins $out = $queries; 4198ba21522SJames Collins } else { 4208ba21522SJames Collins foreach ($queries as $parent) { 4218ba21522SJames Collins foreach ($childQueries as $child) { 4228ba21522SJames Collins $out[] = array_merge($parent, $child); 4238ba21522SJames Collins } 4248ba21522SJames Collins } 4258ba21522SJames Collins } 4268ba21522SJames Collins 4278ba21522SJames Collins return $this->multiplyMedia($env->parent, $out); 4288ba21522SJames Collins } 4298ba21522SJames Collins 4308ba21522SJames Collins protected function expandParentSelectors(&$tag, $replace) { 4318ba21522SJames Collins $parts = explode("$&$", $tag); 4328ba21522SJames Collins $count = 0; 4338ba21522SJames Collins foreach ($parts as &$part) { 4348ba21522SJames Collins $part = str_replace($this->parentSelector, $replace, $part, $c); 4358ba21522SJames Collins $count += $c; 4368ba21522SJames Collins } 4378ba21522SJames Collins $tag = implode($this->parentSelector, $parts); 4388ba21522SJames Collins return $count; 4398ba21522SJames Collins } 4408ba21522SJames Collins 4418ba21522SJames Collins protected function findClosestSelectors() { 4428ba21522SJames Collins $env = $this->env; 4438ba21522SJames Collins $selectors = null; 4448ba21522SJames Collins while ($env !== null) { 4458ba21522SJames Collins if (isset($env->selectors)) { 4468ba21522SJames Collins $selectors = $env->selectors; 4478ba21522SJames Collins break; 4488ba21522SJames Collins } 4498ba21522SJames Collins $env = $env->parent; 4508ba21522SJames Collins } 4518ba21522SJames Collins 4528ba21522SJames Collins return $selectors; 4538ba21522SJames Collins } 4548ba21522SJames Collins 4558ba21522SJames Collins 4568ba21522SJames Collins // multiply $selectors against the nearest selectors in env 4578ba21522SJames Collins protected function multiplySelectors($selectors) { 4588ba21522SJames Collins // find parent selectors 4598ba21522SJames Collins 4608ba21522SJames Collins $parentSelectors = $this->findClosestSelectors(); 4618ba21522SJames Collins if (is_null($parentSelectors)) { 4628ba21522SJames Collins // kill parent reference in top level selector 4638ba21522SJames Collins foreach ($selectors as &$s) { 4648ba21522SJames Collins $this->expandParentSelectors($s, ""); 4658ba21522SJames Collins } 4668ba21522SJames Collins 4678ba21522SJames Collins return $selectors; 4688ba21522SJames Collins } 4698ba21522SJames Collins 4708ba21522SJames Collins $out = array(); 4718ba21522SJames Collins foreach ($parentSelectors as $parent) { 4728ba21522SJames Collins foreach ($selectors as $child) { 4738ba21522SJames Collins $count = $this->expandParentSelectors($child, $parent); 4748ba21522SJames Collins 4758ba21522SJames Collins // don't prepend the parent tag if & was used 4768ba21522SJames Collins if ($count > 0) { 4778ba21522SJames Collins $out[] = trim($child); 4788ba21522SJames Collins } else { 4798ba21522SJames Collins $out[] = trim($parent . ' ' . $child); 4808ba21522SJames Collins } 4818ba21522SJames Collins } 4828ba21522SJames Collins } 4838ba21522SJames Collins 4848ba21522SJames Collins return $out; 4858ba21522SJames Collins } 4868ba21522SJames Collins 4878ba21522SJames Collins // reduces selector expressions 4888ba21522SJames Collins protected function compileSelectors($selectors) { 4898ba21522SJames Collins $out = array(); 4908ba21522SJames Collins 4918ba21522SJames Collins foreach ($selectors as $s) { 4928ba21522SJames Collins if (is_array($s)) { 4938ba21522SJames Collins [, $value] = $s; 4948ba21522SJames Collins $out[] = trim($this->compileValue($this->reduce($value))); 4958ba21522SJames Collins } else { 4968ba21522SJames Collins $out[] = $s; 4978ba21522SJames Collins } 4988ba21522SJames Collins } 4998ba21522SJames Collins 5008ba21522SJames Collins return $out; 5018ba21522SJames Collins } 5028ba21522SJames Collins 5038ba21522SJames Collins protected function eq($left, $right) { 5048ba21522SJames Collins return $left == $right; 5058ba21522SJames Collins } 5068ba21522SJames Collins 5078ba21522SJames Collins protected function patternMatch($block, $orderedArgs, $keywordArgs) { 5088ba21522SJames Collins // match the guards if it has them 5098ba21522SJames Collins // any one of the groups must have all its guards pass for a match 5108ba21522SJames Collins if (!empty($block->guards)) { 5118ba21522SJames Collins $groupPassed = false; 5128ba21522SJames Collins foreach ($block->guards as $guardGroup) { 5138ba21522SJames Collins foreach ($guardGroup as $guard) { 5148ba21522SJames Collins $this->pushEnv(); 5158ba21522SJames Collins $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs); 5168ba21522SJames Collins 5178ba21522SJames Collins $negate = false; 5188ba21522SJames Collins if ($guard[0] == "negate") { 5198ba21522SJames Collins $guard = $guard[1]; 5208ba21522SJames Collins $negate = true; 5218ba21522SJames Collins } 5228ba21522SJames Collins 5238ba21522SJames Collins $passed = $this->reduce($guard) == self::$TRUE; 5248ba21522SJames Collins if ($negate) $passed = !$passed; 5258ba21522SJames Collins 5268ba21522SJames Collins $this->popEnv(); 5278ba21522SJames Collins 5288ba21522SJames Collins if ($passed) { 5298ba21522SJames Collins $groupPassed = true; 5308ba21522SJames Collins } else { 5318ba21522SJames Collins $groupPassed = false; 5328ba21522SJames Collins break; 5338ba21522SJames Collins } 5348ba21522SJames Collins } 5358ba21522SJames Collins 5368ba21522SJames Collins if ($groupPassed) break; 5378ba21522SJames Collins } 5388ba21522SJames Collins 5398ba21522SJames Collins if (!$groupPassed) { 5408ba21522SJames Collins return false; 5418ba21522SJames Collins } 5428ba21522SJames Collins } 5438ba21522SJames Collins 5448ba21522SJames Collins if (empty($block->args)) { 5458ba21522SJames Collins return $block->isVararg || empty($orderedArgs) && empty($keywordArgs); 5468ba21522SJames Collins } 5478ba21522SJames Collins 5488ba21522SJames Collins $remainingArgs = $block->args; 5498ba21522SJames Collins if ($keywordArgs) { 5508ba21522SJames Collins $remainingArgs = array(); 5518ba21522SJames Collins foreach ($block->args as $arg) { 5528ba21522SJames Collins if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) { 5538ba21522SJames Collins continue; 5548ba21522SJames Collins } 5558ba21522SJames Collins 5568ba21522SJames Collins $remainingArgs[] = $arg; 5578ba21522SJames Collins } 5588ba21522SJames Collins } 5598ba21522SJames Collins 5608ba21522SJames Collins $i = -1; // no args 5618ba21522SJames Collins // try to match by arity or by argument literal 5628ba21522SJames Collins foreach ($remainingArgs as $i => $arg) { 5638ba21522SJames Collins switch ($arg[0]) { 5648ba21522SJames Collins case "lit": 5658ba21522SJames Collins if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) { 5668ba21522SJames Collins return false; 5678ba21522SJames Collins } 5688ba21522SJames Collins break; 5698ba21522SJames Collins case "arg": 5708ba21522SJames Collins // no arg and no default value 5718ba21522SJames Collins if (!isset($orderedArgs[$i]) && !isset($arg[2])) { 5728ba21522SJames Collins return false; 5738ba21522SJames Collins } 5748ba21522SJames Collins break; 5758ba21522SJames Collins case "rest": 5768ba21522SJames Collins $i--; // rest can be empty 5778ba21522SJames Collins break 2; 5788ba21522SJames Collins } 5798ba21522SJames Collins } 5808ba21522SJames Collins 5818ba21522SJames Collins if ($block->isVararg) { 5828ba21522SJames Collins return true; // not having enough is handled above 5838ba21522SJames Collins } else { 5848ba21522SJames Collins $numMatched = $i + 1; 5858ba21522SJames Collins // greater than because default values always match 5868ba21522SJames Collins return $numMatched >= count($orderedArgs); 5878ba21522SJames Collins } 5888ba21522SJames Collins } 5898ba21522SJames Collins 5908ba21522SJames Collins protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) { 5918ba21522SJames Collins $matches = null; 5928ba21522SJames Collins foreach ($blocks as $block) { 5938ba21522SJames Collins // skip seen blocks that don't have arguments 5948ba21522SJames Collins if (isset($skip[$block->id]) && !isset($block->args)) { 5958ba21522SJames Collins continue; 5968ba21522SJames Collins } 5978ba21522SJames Collins 5988ba21522SJames Collins if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) { 5998ba21522SJames Collins $matches[] = $block; 6008ba21522SJames Collins } 6018ba21522SJames Collins } 6028ba21522SJames Collins 6038ba21522SJames Collins return $matches; 6048ba21522SJames Collins } 6058ba21522SJames Collins 6068ba21522SJames Collins // attempt to find blocks matched by path and args 6078ba21522SJames Collins protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) { 6088ba21522SJames Collins if ($searchIn == null) return null; 6098ba21522SJames Collins if (isset($seen[$searchIn->id])) return null; 6108ba21522SJames Collins $seen[$searchIn->id] = true; 6118ba21522SJames Collins 6128ba21522SJames Collins $name = $path[0]; 6138ba21522SJames Collins 6148ba21522SJames Collins if (isset($searchIn->children[$name])) { 6158ba21522SJames Collins $blocks = $searchIn->children[$name]; 6168ba21522SJames Collins if (count($path) == 1) { 6178ba21522SJames Collins $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen); 6188ba21522SJames Collins if (!empty($matches)) { 6198ba21522SJames Collins // This will return all blocks that match in the closest 6208ba21522SJames Collins // scope that has any matching block, like lessjs 6218ba21522SJames Collins return $matches; 6228ba21522SJames Collins } 6238ba21522SJames Collins } else { 6248ba21522SJames Collins $matches = array(); 6258ba21522SJames Collins foreach ($blocks as $subBlock) { 6268ba21522SJames Collins $subMatches = $this->findBlocks($subBlock, 6278ba21522SJames Collins array_slice($path, 1), $orderedArgs, $keywordArgs, $seen); 6288ba21522SJames Collins 6298ba21522SJames Collins if (!is_null($subMatches)) { 6308ba21522SJames Collins foreach ($subMatches as $sm) { 6318ba21522SJames Collins $matches[] = $sm; 6328ba21522SJames Collins } 6338ba21522SJames Collins } 6348ba21522SJames Collins } 6358ba21522SJames Collins 6368ba21522SJames Collins return count($matches) > 0 ? $matches : null; 6378ba21522SJames Collins } 6388ba21522SJames Collins } 6398ba21522SJames Collins if ($searchIn->parent === $searchIn) return null; 6408ba21522SJames Collins return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen); 6418ba21522SJames Collins } 6428ba21522SJames Collins 6438ba21522SJames Collins // sets all argument names in $args to either the default value 6448ba21522SJames Collins // or the one passed in through $values 6458ba21522SJames Collins protected function zipSetArgs($args, $orderedValues, $keywordValues) { 6468ba21522SJames Collins $assignedValues = array(); 6478ba21522SJames Collins 6488ba21522SJames Collins $i = 0; 6498ba21522SJames Collins foreach ($args as $a) { 6508ba21522SJames Collins if ($a[0] == "arg") { 6518ba21522SJames Collins if (isset($keywordValues[$a[1]])) { 6528ba21522SJames Collins // has keyword arg 6538ba21522SJames Collins $value = $keywordValues[$a[1]]; 6548ba21522SJames Collins } elseif (isset($orderedValues[$i])) { 6558ba21522SJames Collins // has ordered arg 6568ba21522SJames Collins $value = $orderedValues[$i]; 6578ba21522SJames Collins $i++; 6588ba21522SJames Collins } elseif (isset($a[2])) { 6598ba21522SJames Collins // has default value 6608ba21522SJames Collins $value = $a[2]; 6618ba21522SJames Collins } else { 6628ba21522SJames Collins $this->throwError("Failed to assign arg " . $a[1]); 6638ba21522SJames Collins $value = null; // :( 6648ba21522SJames Collins } 6658ba21522SJames Collins 6668ba21522SJames Collins $value = $this->reduce($value); 6678ba21522SJames Collins $this->set($a[1], $value); 6688ba21522SJames Collins $assignedValues[] = $value; 6698ba21522SJames Collins } else { 6708ba21522SJames Collins // a lit 6718ba21522SJames Collins $i++; 6728ba21522SJames Collins } 6738ba21522SJames Collins } 6748ba21522SJames Collins 6758ba21522SJames Collins // check for a rest 6768ba21522SJames Collins $last = end($args); 6778ba21522SJames Collins if ($last !== false && $last[0] === "rest") { 6788ba21522SJames Collins $rest = array_slice($orderedValues, count($args) - 1); 6798ba21522SJames Collins $this->set($last[1], $this->reduce(array("list", " ", $rest))); 6808ba21522SJames Collins } 6818ba21522SJames Collins 6828ba21522SJames Collins // wow is this the only true use of PHP's + operator for arrays? 6838ba21522SJames Collins $this->env->arguments = $assignedValues + $orderedValues; 6848ba21522SJames Collins } 6858ba21522SJames Collins 6868ba21522SJames Collins // compile a prop and update $lines or $blocks appropriately 6878ba21522SJames Collins protected function compileProp($prop, $block, $out) { 6888ba21522SJames Collins // set error position context 6898ba21522SJames Collins $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; 6908ba21522SJames Collins 6918ba21522SJames Collins switch ($prop[0]) { 6928ba21522SJames Collins case 'assign': 6938ba21522SJames Collins [, $name, $value] = $prop; 6948ba21522SJames Collins if ($name[0] == $this->vPrefix) { 6958ba21522SJames Collins $this->set($name, $value); 6968ba21522SJames Collins } else { 6978ba21522SJames Collins $out->lines[] = $this->formatter->property($name, 6988ba21522SJames Collins $this->compileValue($this->reduce($value))); 6998ba21522SJames Collins } 7008ba21522SJames Collins break; 7018ba21522SJames Collins case 'block': 7028ba21522SJames Collins [, $child] = $prop; 7038ba21522SJames Collins $this->compileBlock($child); 7048ba21522SJames Collins break; 7058ba21522SJames Collins case 'ruleset': 7068ba21522SJames Collins case 'mixin': 7078ba21522SJames Collins [, $path, $args, $suffix] = $prop; 7088ba21522SJames Collins 7098ba21522SJames Collins $orderedArgs = array(); 7108ba21522SJames Collins $keywordArgs = array(); 7118ba21522SJames Collins foreach ((array)$args as $arg) { 7128ba21522SJames Collins $argval = null; 7138ba21522SJames Collins switch ($arg[0]) { 7148ba21522SJames Collins case "arg": 7158ba21522SJames Collins if (!isset($arg[2])) { 7168ba21522SJames Collins $orderedArgs[] = $this->reduce(array("variable", $arg[1])); 7178ba21522SJames Collins } else { 7188ba21522SJames Collins $keywordArgs[$arg[1]] = $this->reduce($arg[2]); 7198ba21522SJames Collins } 7208ba21522SJames Collins break; 7218ba21522SJames Collins 7228ba21522SJames Collins case "lit": 7238ba21522SJames Collins $orderedArgs[] = $this->reduce($arg[1]); 7248ba21522SJames Collins break; 7258ba21522SJames Collins default: 7268ba21522SJames Collins $this->throwError("Unknown arg type: " . $arg[0]); 7278ba21522SJames Collins } 7288ba21522SJames Collins } 7298ba21522SJames Collins 7308ba21522SJames Collins $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs); 7318ba21522SJames Collins 7328ba21522SJames Collins if ($mixins === null) { 7338ba21522SJames Collins $block->parser->throwError("{$prop[1][0]} is undefined", $block->count); 7348ba21522SJames Collins } 7358ba21522SJames Collins 7368ba21522SJames Collins if(strpos($prop[1][0], "$") === 0) { 7378ba21522SJames Collins //Use Ruleset Logic - Only last element 7388ba21522SJames Collins $mixins = array(array_pop($mixins)); 7398ba21522SJames Collins } 7408ba21522SJames Collins 7418ba21522SJames Collins foreach ($mixins as $mixin) { 7428ba21522SJames Collins if ($mixin === $block && !$orderedArgs) { 7438ba21522SJames Collins continue; 7448ba21522SJames Collins } 7458ba21522SJames Collins 7468ba21522SJames Collins $haveScope = false; 7478ba21522SJames Collins if (isset($mixin->parent->scope)) { 7488ba21522SJames Collins $haveScope = true; 7498ba21522SJames Collins $mixinParentEnv = $this->pushEnv(); 7508ba21522SJames Collins $mixinParentEnv->storeParent = $mixin->parent->scope; 7518ba21522SJames Collins } 7528ba21522SJames Collins 7538ba21522SJames Collins $haveArgs = false; 7548ba21522SJames Collins if (isset($mixin->args)) { 7558ba21522SJames Collins $haveArgs = true; 7568ba21522SJames Collins $this->pushEnv(); 7578ba21522SJames Collins $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs); 7588ba21522SJames Collins } 7598ba21522SJames Collins 7608ba21522SJames Collins $oldParent = $mixin->parent; 7618ba21522SJames Collins if ($mixin != $block) $mixin->parent = $block; 7628ba21522SJames Collins 7638ba21522SJames Collins foreach ($this->sortProps($mixin->props) as $subProp) { 7648ba21522SJames Collins if ($suffix !== null && 7658ba21522SJames Collins $subProp[0] == "assign" && 7668ba21522SJames Collins is_string($subProp[1]) && 7678ba21522SJames Collins $subProp[1][0] != $this->vPrefix) 7688ba21522SJames Collins { 7698ba21522SJames Collins $subProp[2] = array( 7708ba21522SJames Collins 'list', ' ', 7718ba21522SJames Collins array($subProp[2], array('keyword', $suffix)) 7728ba21522SJames Collins ); 7738ba21522SJames Collins } 7748ba21522SJames Collins 7758ba21522SJames Collins $this->compileProp($subProp, $mixin, $out); 7768ba21522SJames Collins } 7778ba21522SJames Collins 7788ba21522SJames Collins $mixin->parent = $oldParent; 7798ba21522SJames Collins 7808ba21522SJames Collins if ($haveArgs) $this->popEnv(); 7818ba21522SJames Collins if ($haveScope) $this->popEnv(); 7828ba21522SJames Collins } 7838ba21522SJames Collins 7848ba21522SJames Collins break; 7858ba21522SJames Collins case 'raw': 7868ba21522SJames Collins $out->lines[] = $prop[1]; 7878ba21522SJames Collins break; 7888ba21522SJames Collins case "directive": 7898ba21522SJames Collins [, $name, $value] = $prop; 7908ba21522SJames Collins $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';'; 7918ba21522SJames Collins break; 7928ba21522SJames Collins case "comment": 7938ba21522SJames Collins $out->lines[] = $prop[1]; 7948ba21522SJames Collins break; 7958ba21522SJames Collins case "import"; 7968ba21522SJames Collins [, $importPath, $importId] = $prop; 7978ba21522SJames Collins $importPath = $this->reduce($importPath); 7988ba21522SJames Collins 7998ba21522SJames Collins if (!isset($this->env->imports)) { 8008ba21522SJames Collins $this->env->imports = array(); 8018ba21522SJames Collins } 8028ba21522SJames Collins 8038ba21522SJames Collins $result = $this->tryImport($importPath, $block, $out); 8048ba21522SJames Collins 8058ba21522SJames Collins $this->env->imports[$importId] = $result === false ? 8068ba21522SJames Collins array(false, "@import " . $this->compileValue($importPath).";") : 8078ba21522SJames Collins $result; 8088ba21522SJames Collins 8098ba21522SJames Collins break; 8108ba21522SJames Collins case "import_mixin": 8118ba21522SJames Collins [,$importId] = $prop; 8128ba21522SJames Collins $import = $this->env->imports[$importId]; 8138ba21522SJames Collins if ($import[0] === false) { 8148ba21522SJames Collins if (isset($import[1])) { 8158ba21522SJames Collins $out->lines[] = $import[1]; 8168ba21522SJames Collins } 8178ba21522SJames Collins } else { 8188ba21522SJames Collins [, $bottom, $parser, $importDir] = $import; 8198ba21522SJames Collins $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); 8208ba21522SJames Collins } 8218ba21522SJames Collins 8228ba21522SJames Collins break; 8238ba21522SJames Collins default: 8248ba21522SJames Collins $block->parser->throwError("unknown op: {$prop[0]}\n", $block->count); 8258ba21522SJames Collins } 8268ba21522SJames Collins } 8278ba21522SJames Collins 8288ba21522SJames Collins 8298ba21522SJames Collins /** 8308ba21522SJames Collins * Compiles a primitive value into a CSS property value. 8318ba21522SJames Collins * 8328ba21522SJames Collins * Values in lessphp are typed by being wrapped in arrays, their format is 8338ba21522SJames Collins * typically: 8348ba21522SJames Collins * 8358ba21522SJames Collins * array(type, contents [, additional_contents]*) 8368ba21522SJames Collins * 8378ba21522SJames Collins * The input is expected to be reduced. This function will not work on 8388ba21522SJames Collins * things like expressions and variables. 8398ba21522SJames Collins */ 8408ba21522SJames Collins public function compileValue($value) { 8418ba21522SJames Collins switch ($value[0]) { 8428ba21522SJames Collins case 'list': 8438ba21522SJames Collins // [1] - delimiter 8448ba21522SJames Collins // [2] - array of values 8458ba21522SJames Collins return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); 8468ba21522SJames Collins case 'raw_color': 8478ba21522SJames Collins if (!empty($this->formatter->compressColors)) { 8488ba21522SJames Collins return $this->compileValue($this->coerceColor($value)); 8498ba21522SJames Collins } 8508ba21522SJames Collins return $value[1]; 8518ba21522SJames Collins case 'keyword': 8528ba21522SJames Collins // [1] - the keyword 8538ba21522SJames Collins return $value[1]; 8548ba21522SJames Collins case 'number': 8558ba21522SJames Collins [, $num, $unit] = $value; 8568ba21522SJames Collins // [1] - the number 8578ba21522SJames Collins // [2] - the unit 8588ba21522SJames Collins if ($this->numberPrecision !== null) { 8598ba21522SJames Collins $num = round($num, $this->numberPrecision); 8608ba21522SJames Collins } 8618ba21522SJames Collins return $num . $unit; 8628ba21522SJames Collins case 'string': 8638ba21522SJames Collins // [1] - contents of string (includes quotes) 8648ba21522SJames Collins [, $delim, $content] = $value; 8658ba21522SJames Collins foreach ($content as &$part) { 8668ba21522SJames Collins if (is_array($part)) { 8678ba21522SJames Collins $part = $this->compileValue($part); 8688ba21522SJames Collins } 8698ba21522SJames Collins } 8708ba21522SJames Collins return $delim . implode($content) . $delim; 8718ba21522SJames Collins case 'color': 8728ba21522SJames Collins // [1] - red component (either number or a %) 8738ba21522SJames Collins // [2] - green component 8748ba21522SJames Collins // [3] - blue component 8758ba21522SJames Collins // [4] - optional alpha component 8768ba21522SJames Collins [, $r, $g, $b] = $value; 8778ba21522SJames Collins $r = round($r); 8788ba21522SJames Collins $g = round($g); 8798ba21522SJames Collins $b = round($b); 8808ba21522SJames Collins 8818ba21522SJames Collins if (count($value) == 5 && $value[4] != 1) { // rgba 8828ba21522SJames Collins return 'rgba('.$r.','.$g.','.$b.','.$value[4].')'; 8838ba21522SJames Collins } 8848ba21522SJames Collins 8858ba21522SJames Collins $h = sprintf("#%02x%02x%02x", $r, $g, $b); 8868ba21522SJames Collins 8878ba21522SJames Collins if (!empty($this->formatter->compressColors)) { 8888ba21522SJames Collins // Converting hex color to short notation (e.g. #003399 to #039) 8898ba21522SJames Collins if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { 8908ba21522SJames Collins $h = '#' . $h[1] . $h[3] . $h[5]; 8918ba21522SJames Collins } 8928ba21522SJames Collins } 8938ba21522SJames Collins 8948ba21522SJames Collins return $h; 8958ba21522SJames Collins 8968ba21522SJames Collins case 'function': 8978ba21522SJames Collins [, $name, $args] = $value; 8988ba21522SJames Collins return $name.'('.$this->compileValue($args).')'; 8998ba21522SJames Collins default: // assumed to be unit 9008ba21522SJames Collins $this->throwError("unknown value type: $value[0]"); 9018ba21522SJames Collins } 9028ba21522SJames Collins } 9038ba21522SJames Collins 9048ba21522SJames Collins protected function lib_pow($args) { 9058ba21522SJames Collins [$base, $exp] = $this->assertArgs($args, 2, "pow"); 9068ba21522SJames Collins return array( "number", pow($this->assertNumber($base), $this->assertNumber($exp)), $args[2][0][2] ); 9078ba21522SJames Collins } 9088ba21522SJames Collins 9098ba21522SJames Collins protected function lib_pi() { 9108ba21522SJames Collins return pi(); 9118ba21522SJames Collins } 9128ba21522SJames Collins 9138ba21522SJames Collins protected function lib_mod($args) { 9148ba21522SJames Collins [$a, $b] = $this->assertArgs($args, 2, "mod"); 9158ba21522SJames Collins return array( "number", $this->assertNumber($a) % $this->assertNumber($b), $args[2][0][2] ); 9168ba21522SJames Collins } 9178ba21522SJames Collins 9188ba21522SJames Collins protected function lib_convert($args) { 9198ba21522SJames Collins [$value, $to] = $this->assertArgs($args, 2, "convert"); 9208ba21522SJames Collins 9218ba21522SJames Collins // If it's a keyword, grab the string version instead 9228ba21522SJames Collins if( is_array( $to ) && $to[0] == "keyword" ) 9238ba21522SJames Collins $to = $to[1]; 9248ba21522SJames Collins 9258ba21522SJames Collins return $this->convert( $value, $to ); 9268ba21522SJames Collins } 9278ba21522SJames Collins 9288ba21522SJames Collins protected function lib_abs($num) { 9298ba21522SJames Collins return array( "number", abs($this->assertNumber($num)), $num[2] ); 9308ba21522SJames Collins } 9318ba21522SJames Collins 9328ba21522SJames Collins protected function lib_min($args) { 9338ba21522SJames Collins $values = $this->assertMinArgs($args, 1, "min"); 9348ba21522SJames Collins 9358ba21522SJames Collins $first_format = $values[0][2]; 9368ba21522SJames Collins 9378ba21522SJames Collins $min_index = 0; 9388ba21522SJames Collins $min_value = $values[0][1]; 9398ba21522SJames Collins 9408ba21522SJames Collins for( $a = 0; $a < sizeof( $values ); $a++ ) 9418ba21522SJames Collins { 9428ba21522SJames Collins $converted = $this->convert( $values[$a], $first_format ); 9438ba21522SJames Collins 9448ba21522SJames Collins if( $converted[1] < $min_value ) 9458ba21522SJames Collins { 9468ba21522SJames Collins $min_index = $a; 9478ba21522SJames Collins $min_value = $values[$a][1]; 9488ba21522SJames Collins } 9498ba21522SJames Collins } 9508ba21522SJames Collins 9518ba21522SJames Collins return $values[ $min_index ]; 9528ba21522SJames Collins } 9538ba21522SJames Collins 9548ba21522SJames Collins protected function lib_max($args) { 9558ba21522SJames Collins $values = $this->assertMinArgs($args, 1, "max"); 9568ba21522SJames Collins 9578ba21522SJames Collins $first_format = $values[0][2]; 9588ba21522SJames Collins 9598ba21522SJames Collins $max_index = 0; 9608ba21522SJames Collins $max_value = $values[0][1]; 9618ba21522SJames Collins 9628ba21522SJames Collins for( $a = 0; $a < sizeof( $values ); $a++ ) 9638ba21522SJames Collins { 9648ba21522SJames Collins $converted = $this->convert( $values[$a], $first_format ); 9658ba21522SJames Collins 9668ba21522SJames Collins if( $converted[1] > $max_value ) 9678ba21522SJames Collins { 9688ba21522SJames Collins $max_index = $a; 9698ba21522SJames Collins $max_value = $values[$a][1]; 9708ba21522SJames Collins } 9718ba21522SJames Collins } 9728ba21522SJames Collins 9738ba21522SJames Collins return $values[ $max_index ]; 9748ba21522SJames Collins } 9758ba21522SJames Collins 9768ba21522SJames Collins protected function lib_tan($num) { 9778ba21522SJames Collins return tan($this->assertNumber($num)); 9788ba21522SJames Collins } 9798ba21522SJames Collins 9808ba21522SJames Collins protected function lib_sin($num) { 9818ba21522SJames Collins return sin($this->assertNumber($num)); 9828ba21522SJames Collins } 9838ba21522SJames Collins 9848ba21522SJames Collins protected function lib_cos($num) { 9858ba21522SJames Collins return cos($this->assertNumber($num)); 9868ba21522SJames Collins } 9878ba21522SJames Collins 9888ba21522SJames Collins protected function lib_atan($num) { 9898ba21522SJames Collins $num = atan($this->assertNumber($num)); 9908ba21522SJames Collins return array("number", $num, "rad"); 9918ba21522SJames Collins } 9928ba21522SJames Collins 9938ba21522SJames Collins protected function lib_asin($num) { 9948ba21522SJames Collins $num = asin($this->assertNumber($num)); 9958ba21522SJames Collins return array("number", $num, "rad"); 9968ba21522SJames Collins } 9978ba21522SJames Collins 9988ba21522SJames Collins protected function lib_acos($num) { 9998ba21522SJames Collins $num = acos($this->assertNumber($num)); 10008ba21522SJames Collins return array("number", $num, "rad"); 10018ba21522SJames Collins } 10028ba21522SJames Collins 10038ba21522SJames Collins protected function lib_sqrt($num) { 10048ba21522SJames Collins return sqrt($this->assertNumber($num)); 10058ba21522SJames Collins } 10068ba21522SJames Collins 10078ba21522SJames Collins protected function lib_extract($value) { 10088ba21522SJames Collins [$list, $idx] = $this->assertArgs($value, 2, "extract"); 10098ba21522SJames Collins $idx = $this->assertNumber($idx); 10108ba21522SJames Collins // 1 indexed 10118ba21522SJames Collins if ($list[0] == "list" && isset($list[2][$idx - 1])) { 10128ba21522SJames Collins return $list[2][$idx - 1]; 10138ba21522SJames Collins } 10148ba21522SJames Collins } 10158ba21522SJames Collins 10168ba21522SJames Collins protected function lib_isnumber($value) { 10178ba21522SJames Collins return $this->toBool($value[0] == "number"); 10188ba21522SJames Collins } 10198ba21522SJames Collins 10208ba21522SJames Collins protected function lib_isstring($value) { 10218ba21522SJames Collins return $this->toBool($value[0] == "string"); 10228ba21522SJames Collins } 10238ba21522SJames Collins 10248ba21522SJames Collins protected function lib_iscolor($value) { 10258ba21522SJames Collins return $this->toBool($this->coerceColor($value)); 10268ba21522SJames Collins } 10278ba21522SJames Collins 10288ba21522SJames Collins protected function lib_iskeyword($value) { 10298ba21522SJames Collins return $this->toBool($value[0] == "keyword"); 10308ba21522SJames Collins } 10318ba21522SJames Collins 10328ba21522SJames Collins protected function lib_ispixel($value) { 10338ba21522SJames Collins return $this->toBool($value[0] == "number" && $value[2] == "px"); 10348ba21522SJames Collins } 10358ba21522SJames Collins 10368ba21522SJames Collins protected function lib_ispercentage($value) { 10378ba21522SJames Collins return $this->toBool($value[0] == "number" && $value[2] == "%"); 10388ba21522SJames Collins } 10398ba21522SJames Collins 10408ba21522SJames Collins protected function lib_isem($value) { 10418ba21522SJames Collins return $this->toBool($value[0] == "number" && $value[2] == "em"); 10428ba21522SJames Collins } 10438ba21522SJames Collins 10448ba21522SJames Collins protected function lib_isrem($value) { 10458ba21522SJames Collins return $this->toBool($value[0] == "number" && $value[2] == "rem"); 10468ba21522SJames Collins } 10478ba21522SJames Collins 10488ba21522SJames Collins protected function lib_rgbahex($color) { 10498ba21522SJames Collins $color = $this->coerceColor($color); 10508ba21522SJames Collins if (is_null($color)) 10518ba21522SJames Collins $this->throwError("color expected for rgbahex"); 10528ba21522SJames Collins 10538ba21522SJames Collins return sprintf("#%02x%02x%02x%02x", 10548ba21522SJames Collins isset($color[4]) ? $color[4]*255 : 255, 10558ba21522SJames Collins $color[1],$color[2], $color[3]); 10568ba21522SJames Collins } 10578ba21522SJames Collins 10588ba21522SJames Collins protected function lib_argb($color){ 10598ba21522SJames Collins return $this->lib_rgbahex($color); 10608ba21522SJames Collins } 10618ba21522SJames Collins 10628ba21522SJames Collins /** 10638ba21522SJames Collins * Given an url, decide whether to output a regular link or the base64-encoded contents of the file 10648ba21522SJames Collins * 10658ba21522SJames Collins * @param array $value either an argument list (two strings) or a single string 10668ba21522SJames Collins * @return string formatted url(), either as a link or base64-encoded 10678ba21522SJames Collins */ 10688ba21522SJames Collins protected function lib_data_uri($value) { 10698ba21522SJames Collins $mime = ($value[0] === 'list') ? $value[2][0][2] : null; 10708ba21522SJames Collins $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0]; 10718ba21522SJames Collins 10728ba21522SJames Collins $fullpath = $this->findImport($url); 10738ba21522SJames Collins 10748ba21522SJames Collins if($fullpath && ($fsize = filesize($fullpath)) !== false) { 10758ba21522SJames Collins // IE8 can't handle data uris larger than 32KB 10768ba21522SJames Collins if($fsize/1024 < 32) { 10778ba21522SJames Collins if(is_null($mime)) { 10788ba21522SJames Collins if(class_exists('finfo')) { // php 5.3+ 10798ba21522SJames Collins $finfo = new finfo(FILEINFO_MIME); 10808ba21522SJames Collins $mime = explode('; ', $finfo->file($fullpath)); 10818ba21522SJames Collins $mime = $mime[0]; 10828ba21522SJames Collins } elseif(function_exists('mime_content_type')) { // PHP 5.2 10838ba21522SJames Collins $mime = mime_content_type($fullpath); 10848ba21522SJames Collins } 10858ba21522SJames Collins } 10868ba21522SJames Collins 10878ba21522SJames Collins if(!is_null($mime)) // fallback if the mime type is still unknown 10888ba21522SJames Collins $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath))); 10898ba21522SJames Collins } 10908ba21522SJames Collins } 10918ba21522SJames Collins 10928ba21522SJames Collins return 'url("'.$url.'")'; 10938ba21522SJames Collins } 10948ba21522SJames Collins 10958ba21522SJames Collins // utility func to unquote a string 10968ba21522SJames Collins protected function lib_e($arg) { 10978ba21522SJames Collins switch ($arg[0]) { 10988ba21522SJames Collins case "list": 10998ba21522SJames Collins $items = $arg[2]; 11008ba21522SJames Collins if (isset($items[0])) { 11018ba21522SJames Collins return $this->lib_e($items[0]); 11028ba21522SJames Collins } 11038ba21522SJames Collins $this->throwError("unrecognised input"); 11048ba21522SJames Collins case "string": 11058ba21522SJames Collins $arg[1] = ""; 11068ba21522SJames Collins return $arg; 11078ba21522SJames Collins case "keyword": 11088ba21522SJames Collins return $arg; 11098ba21522SJames Collins default: 11108ba21522SJames Collins return array("keyword", $this->compileValue($arg)); 11118ba21522SJames Collins } 11128ba21522SJames Collins } 11138ba21522SJames Collins 11148ba21522SJames Collins protected function lib__sprintf($args) { 11158ba21522SJames Collins if ($args[0] != "list") return $args; 11168ba21522SJames Collins $values = $args[2]; 11178ba21522SJames Collins $string = array_shift($values); 11188ba21522SJames Collins $template = $this->compileValue($this->lib_e($string)); 11198ba21522SJames Collins 11208ba21522SJames Collins $i = 0; 11218ba21522SJames Collins if (preg_match_all('/%[dsa]/', $template, $m)) { 11228ba21522SJames Collins foreach ($m[0] as $match) { 11238ba21522SJames Collins $val = isset($values[$i]) ? 11248ba21522SJames Collins $this->reduce($values[$i]) : array('keyword', ''); 11258ba21522SJames Collins 11268ba21522SJames Collins // lessjs compat, renders fully expanded color, not raw color 11278ba21522SJames Collins if ($color = $this->coerceColor($val)) { 11288ba21522SJames Collins $val = $color; 11298ba21522SJames Collins } 11308ba21522SJames Collins 11318ba21522SJames Collins $i++; 11328ba21522SJames Collins $rep = $this->compileValue($this->lib_e($val)); 11338ba21522SJames Collins $template = preg_replace('/'.self::preg_quote($match).'/', 11348ba21522SJames Collins $rep, $template, 1); 11358ba21522SJames Collins } 11368ba21522SJames Collins } 11378ba21522SJames Collins 11388ba21522SJames Collins $d = $string[0] == "string" ? $string[1] : '"'; 11398ba21522SJames Collins return array("string", $d, array($template)); 11408ba21522SJames Collins } 11418ba21522SJames Collins 11428ba21522SJames Collins protected function lib_floor($arg) { 11438ba21522SJames Collins $value = $this->assertNumber($arg); 11448ba21522SJames Collins return array("number", floor($value), $arg[2]); 11458ba21522SJames Collins } 11468ba21522SJames Collins 11478ba21522SJames Collins protected function lib_ceil($arg) { 11488ba21522SJames Collins $value = $this->assertNumber($arg); 11498ba21522SJames Collins return array("number", ceil($value), $arg[2]); 11508ba21522SJames Collins } 11518ba21522SJames Collins 11528ba21522SJames Collins protected function lib_round($arg) { 11538ba21522SJames Collins if($arg[0] != "list") { 11548ba21522SJames Collins $value = $this->assertNumber($arg); 11558ba21522SJames Collins return array("number", round($value), $arg[2]); 11568ba21522SJames Collins } else { 11578ba21522SJames Collins $value = $this->assertNumber($arg[2][0]); 11588ba21522SJames Collins $precision = $this->assertNumber($arg[2][1]); 11598ba21522SJames Collins return array("number", round($value, $precision), $arg[2][0][2]); 11608ba21522SJames Collins } 11618ba21522SJames Collins } 11628ba21522SJames Collins 11638ba21522SJames Collins protected function lib_unit($arg) { 11648ba21522SJames Collins if ($arg[0] == "list") { 11658ba21522SJames Collins [$number, $newUnit] = $arg[2]; 11668ba21522SJames Collins return array("number", $this->assertNumber($number), 11678ba21522SJames Collins $this->compileValue($this->lib_e($newUnit))); 11688ba21522SJames Collins } else { 11698ba21522SJames Collins return array("number", $this->assertNumber($arg), ""); 11708ba21522SJames Collins } 11718ba21522SJames Collins } 11728ba21522SJames Collins 11738ba21522SJames Collins /** 11748ba21522SJames Collins * Helper function to get arguments for color manipulation functions. 11758ba21522SJames Collins * takes a list that contains a color like thing and a percentage 11768ba21522SJames Collins */ 11778ba21522SJames Collins public function colorArgs($args) { 11788ba21522SJames Collins if ($args[0] != 'list' || count($args[2]) < 2) { 11798ba21522SJames Collins return array(array('color', 0, 0, 0), 0); 11808ba21522SJames Collins } 11818ba21522SJames Collins [$color, $delta] = $args[2]; 11828ba21522SJames Collins $color = $this->assertColor($color); 11838ba21522SJames Collins $delta = floatval($delta[1]); 11848ba21522SJames Collins 11858ba21522SJames Collins return array($color, $delta); 11868ba21522SJames Collins } 11878ba21522SJames Collins 11888ba21522SJames Collins protected function lib_darken($args) { 11898ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 11908ba21522SJames Collins 11918ba21522SJames Collins $hsl = $this->toHSL($color); 11928ba21522SJames Collins $hsl[3] = $this->clamp($hsl[3] - $delta, 100); 11938ba21522SJames Collins return $this->toRGB($hsl); 11948ba21522SJames Collins } 11958ba21522SJames Collins 11968ba21522SJames Collins protected function lib_lighten($args) { 11978ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 11988ba21522SJames Collins 11998ba21522SJames Collins $hsl = $this->toHSL($color); 12008ba21522SJames Collins $hsl[3] = $this->clamp($hsl[3] + $delta, 100); 12018ba21522SJames Collins return $this->toRGB($hsl); 12028ba21522SJames Collins } 12038ba21522SJames Collins 12048ba21522SJames Collins protected function lib_saturate($args) { 12058ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 12068ba21522SJames Collins 12078ba21522SJames Collins $hsl = $this->toHSL($color); 12088ba21522SJames Collins $hsl[2] = $this->clamp($hsl[2] + $delta, 100); 12098ba21522SJames Collins return $this->toRGB($hsl); 12108ba21522SJames Collins } 12118ba21522SJames Collins 12128ba21522SJames Collins protected function lib_desaturate($args) { 12138ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 12148ba21522SJames Collins 12158ba21522SJames Collins $hsl = $this->toHSL($color); 12168ba21522SJames Collins $hsl[2] = $this->clamp($hsl[2] - $delta, 100); 12178ba21522SJames Collins return $this->toRGB($hsl); 12188ba21522SJames Collins } 12198ba21522SJames Collins 12208ba21522SJames Collins protected function lib_spin($args) { 12218ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 12228ba21522SJames Collins 12238ba21522SJames Collins $hsl = $this->toHSL($color); 12248ba21522SJames Collins 12258ba21522SJames Collins $hsl[1] = $hsl[1] + $delta % 360; 12268ba21522SJames Collins if ($hsl[1] < 0) $hsl[1] += 360; 12278ba21522SJames Collins 12288ba21522SJames Collins return $this->toRGB($hsl); 12298ba21522SJames Collins } 12308ba21522SJames Collins 12318ba21522SJames Collins protected function lib_fadeout($args) { 12328ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 12338ba21522SJames Collins $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); 12348ba21522SJames Collins return $color; 12358ba21522SJames Collins } 12368ba21522SJames Collins 12378ba21522SJames Collins protected function lib_fadein($args) { 12388ba21522SJames Collins [$color, $delta] = $this->colorArgs($args); 12398ba21522SJames Collins $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); 12408ba21522SJames Collins return $color; 12418ba21522SJames Collins } 12428ba21522SJames Collins 12438ba21522SJames Collins protected function lib_hue($color) { 12448ba21522SJames Collins $hsl = $this->toHSL($this->assertColor($color)); 12458ba21522SJames Collins return round($hsl[1]); 12468ba21522SJames Collins } 12478ba21522SJames Collins 12488ba21522SJames Collins protected function lib_saturation($color) { 12498ba21522SJames Collins $hsl = $this->toHSL($this->assertColor($color)); 12508ba21522SJames Collins return round($hsl[2]); 12518ba21522SJames Collins } 12528ba21522SJames Collins 12538ba21522SJames Collins protected function lib_lightness($color) { 12548ba21522SJames Collins $hsl = $this->toHSL($this->assertColor($color)); 12558ba21522SJames Collins return round($hsl[3]); 12568ba21522SJames Collins } 12578ba21522SJames Collins 12588ba21522SJames Collins // get the alpha of a color 12598ba21522SJames Collins // defaults to 1 for non-colors or colors without an alpha 12608ba21522SJames Collins protected function lib_alpha($value) { 12618ba21522SJames Collins if (!is_null($color = $this->coerceColor($value))) { 12628ba21522SJames Collins return isset($color[4]) ? $color[4] : 1; 12638ba21522SJames Collins } 12648ba21522SJames Collins } 12658ba21522SJames Collins 12668ba21522SJames Collins // set the alpha of the color 12678ba21522SJames Collins protected function lib_fade($args) { 12688ba21522SJames Collins [$color, $alpha] = $this->colorArgs($args); 12698ba21522SJames Collins $color[4] = $this->clamp($alpha / 100.0); 12708ba21522SJames Collins return $color; 12718ba21522SJames Collins } 12728ba21522SJames Collins 12738ba21522SJames Collins protected function lib_percentage($arg) { 12748ba21522SJames Collins $num = $this->assertNumber($arg); 12758ba21522SJames Collins return array("number", $num*100, "%"); 12768ba21522SJames Collins } 12778ba21522SJames Collins 12788ba21522SJames Collins // mixes two colors by weight 12798ba21522SJames Collins // mix(@color1, @color2, [@weight: 50%]); 12808ba21522SJames Collins // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method 12818ba21522SJames Collins protected function lib_mix($args) { 12828ba21522SJames Collins if ($args[0] != "list" || count($args[2]) < 2) 12838ba21522SJames Collins $this->throwError("mix expects (color1, color2, weight)"); 12848ba21522SJames Collins 12858ba21522SJames Collins [$first, $second] = $args[2]; 12868ba21522SJames Collins $first = $this->assertColor($first); 12878ba21522SJames Collins $second = $this->assertColor($second); 12888ba21522SJames Collins 12898ba21522SJames Collins $first_a = $this->lib_alpha($first); 12908ba21522SJames Collins $second_a = $this->lib_alpha($second); 12918ba21522SJames Collins 12928ba21522SJames Collins if (isset($args[2][2])) { 12938ba21522SJames Collins $weight = $args[2][2][1] / 100.0; 12948ba21522SJames Collins } else { 12958ba21522SJames Collins $weight = 0.5; 12968ba21522SJames Collins } 12978ba21522SJames Collins 12988ba21522SJames Collins $w = $weight * 2 - 1; 12998ba21522SJames Collins $a = $first_a - $second_a; 13008ba21522SJames Collins 13018ba21522SJames Collins $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; 13028ba21522SJames Collins $w2 = 1.0 - $w1; 13038ba21522SJames Collins 13048ba21522SJames Collins $new = array('color', 13058ba21522SJames Collins $w1 * $first[1] + $w2 * $second[1], 13068ba21522SJames Collins $w1 * $first[2] + $w2 * $second[2], 13078ba21522SJames Collins $w1 * $first[3] + $w2 * $second[3], 13088ba21522SJames Collins ); 13098ba21522SJames Collins 13108ba21522SJames Collins if ($first_a != 1.0 || $second_a != 1.0) { 13118ba21522SJames Collins $new[] = $first_a * $weight + $second_a * ($weight - 1); 13128ba21522SJames Collins } 13138ba21522SJames Collins 13148ba21522SJames Collins return $this->fixColor($new); 13158ba21522SJames Collins } 13168ba21522SJames Collins 13178ba21522SJames Collins protected function lib_contrast($args) { 13188ba21522SJames Collins $darkColor = array('color', 0, 0, 0); 13198ba21522SJames Collins $lightColor = array('color', 255, 255, 255); 13208ba21522SJames Collins $threshold = 0.43; 13218ba21522SJames Collins 13228ba21522SJames Collins if ( $args[0] == 'list' ) { 13238ba21522SJames Collins $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor; 13248ba21522SJames Collins $darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor; 13258ba21522SJames Collins $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor; 13268ba21522SJames Collins if( isset($args[2][3]) ) { 13278ba21522SJames Collins if( isset($args[2][3][2]) && $args[2][3][2] == '%' ) { 13288ba21522SJames Collins $args[2][3][1] /= 100; 13298ba21522SJames Collins unset($args[2][3][2]); 13308ba21522SJames Collins } 13318ba21522SJames Collins $threshold = $this->assertNumber($args[2][3]); 13328ba21522SJames Collins } 13338ba21522SJames Collins } 13348ba21522SJames Collins else { 13358ba21522SJames Collins $inputColor = $this->assertColor($args); 13368ba21522SJames Collins } 13378ba21522SJames Collins 13388ba21522SJames Collins $inputColor = $this->coerceColor($inputColor); 13398ba21522SJames Collins $darkColor = $this->coerceColor($darkColor); 13408ba21522SJames Collins $lightColor = $this->coerceColor($lightColor); 13418ba21522SJames Collins 13428ba21522SJames Collins //Figure out which is actually light and dark! 13438ba21522SJames Collins if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) { 13448ba21522SJames Collins $t = $lightColor; 13458ba21522SJames Collins $lightColor = $darkColor; 13468ba21522SJames Collins $darkColor = $t; 13478ba21522SJames Collins } 13488ba21522SJames Collins 13498ba21522SJames Collins $inputColor_alpha = $this->lib_alpha($inputColor); 13508ba21522SJames Collins if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) { 13518ba21522SJames Collins return $lightColor; 13528ba21522SJames Collins } 13538ba21522SJames Collins return $darkColor; 13548ba21522SJames Collins } 13558ba21522SJames Collins 13568ba21522SJames Collins protected function lib_luma($color) { 13578ba21522SJames Collins $color = $this->coerceColor($color); 13588ba21522SJames Collins return (0.2126 * $color[1] / 255) + (0.7152 * $color[2] / 255) + (0.0722 * $color[3] / 255); 13598ba21522SJames Collins } 13608ba21522SJames Collins 13618ba21522SJames Collins 13628ba21522SJames Collins public function assertColor($value, $error = "expected color value") { 13638ba21522SJames Collins $color = $this->coerceColor($value); 13648ba21522SJames Collins if (is_null($color)) $this->throwError($error); 13658ba21522SJames Collins return $color; 13668ba21522SJames Collins } 13678ba21522SJames Collins 13688ba21522SJames Collins public function assertNumber($value, $error = "expecting number") { 13698ba21522SJames Collins if ($value[0] == "number") return $value[1]; 13708ba21522SJames Collins $this->throwError($error); 13718ba21522SJames Collins } 13728ba21522SJames Collins 13738ba21522SJames Collins public function assertArgs($value, $expectedArgs, $name="") { 13748ba21522SJames Collins if ($expectedArgs == 1) { 13758ba21522SJames Collins return $value; 13768ba21522SJames Collins } else { 13778ba21522SJames Collins if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list"); 13788ba21522SJames Collins $values = $value[2]; 13798ba21522SJames Collins $numValues = count($values); 13808ba21522SJames Collins if ($expectedArgs != $numValues) { 13818ba21522SJames Collins if ($name) { 13828ba21522SJames Collins $name = $name . ": "; 13838ba21522SJames Collins } 13848ba21522SJames Collins 1385*a92686aaSJames Collins $this->throwError("{$name}expecting $expectedArgs arguments, got $numValues"); 13868ba21522SJames Collins } 13878ba21522SJames Collins 13888ba21522SJames Collins return $values; 13898ba21522SJames Collins } 13908ba21522SJames Collins } 13918ba21522SJames Collins 13928ba21522SJames Collins public function assertMinArgs($value, $expectedMinArgs, $name="") { 13938ba21522SJames Collins if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list"); 13948ba21522SJames Collins $values = $value[2]; 13958ba21522SJames Collins $numValues = count($values); 13968ba21522SJames Collins if ($expectedMinArgs > $numValues) { 13978ba21522SJames Collins if ($name) { 13988ba21522SJames Collins $name = $name . ": "; 13998ba21522SJames Collins } 14008ba21522SJames Collins 1401*a92686aaSJames Collins $this->throwError("{$name}expecting at least $expectedMinArgs arguments, got $numValues"); 14028ba21522SJames Collins } 14038ba21522SJames Collins 14048ba21522SJames Collins return $values; 14058ba21522SJames Collins } 14068ba21522SJames Collins 14078ba21522SJames Collins protected function toHSL($color) { 14088ba21522SJames Collins if ($color[0] == 'hsl') return $color; 14098ba21522SJames Collins 14108ba21522SJames Collins $r = $color[1] / 255; 14118ba21522SJames Collins $g = $color[2] / 255; 14128ba21522SJames Collins $b = $color[3] / 255; 14138ba21522SJames Collins 14148ba21522SJames Collins $min = min($r, $g, $b); 14158ba21522SJames Collins $max = max($r, $g, $b); 14168ba21522SJames Collins 14178ba21522SJames Collins $L = ($min + $max) / 2; 14188ba21522SJames Collins if ($min == $max) { 14198ba21522SJames Collins $S = $H = 0; 14208ba21522SJames Collins } else { 14218ba21522SJames Collins if ($L < 0.5) 14228ba21522SJames Collins $S = ($max - $min)/($max + $min); 14238ba21522SJames Collins else 14248ba21522SJames Collins $S = ($max - $min)/(2.0 - $max - $min); 14258ba21522SJames Collins 14268ba21522SJames Collins if ($r == $max) $H = ($g - $b)/($max - $min); 14278ba21522SJames Collins elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); 14288ba21522SJames Collins elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); 14298ba21522SJames Collins 14308ba21522SJames Collins } 14318ba21522SJames Collins 14328ba21522SJames Collins $out = array('hsl', 14338ba21522SJames Collins ($H < 0 ? $H + 6 : $H)*60, 14348ba21522SJames Collins $S*100, 14358ba21522SJames Collins $L*100, 14368ba21522SJames Collins ); 14378ba21522SJames Collins 14388ba21522SJames Collins if (count($color) > 4) $out[] = $color[4]; // copy alpha 14398ba21522SJames Collins return $out; 14408ba21522SJames Collins } 14418ba21522SJames Collins 14428ba21522SJames Collins protected function toRGB_helper($comp, $temp1, $temp2) { 14438ba21522SJames Collins if ($comp < 0) $comp += 1.0; 14448ba21522SJames Collins elseif ($comp > 1) $comp -= 1.0; 14458ba21522SJames Collins 14468ba21522SJames Collins if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; 14478ba21522SJames Collins if (2 * $comp < 1) return $temp2; 14488ba21522SJames Collins if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; 14498ba21522SJames Collins 14508ba21522SJames Collins return $temp1; 14518ba21522SJames Collins } 14528ba21522SJames Collins 14538ba21522SJames Collins /** 14548ba21522SJames Collins * Converts a hsl array into a color value in rgb. 14558ba21522SJames Collins * Expects H to be in range of 0 to 360, S and L in 0 to 100 14568ba21522SJames Collins */ 14578ba21522SJames Collins protected function toRGB($color) { 14588ba21522SJames Collins if ($color[0] == 'color') return $color; 14598ba21522SJames Collins 14608ba21522SJames Collins $H = $color[1] / 360; 14618ba21522SJames Collins $S = $color[2] / 100; 14628ba21522SJames Collins $L = $color[3] / 100; 14638ba21522SJames Collins 14648ba21522SJames Collins if ($S == 0) { 14658ba21522SJames Collins $r = $g = $b = $L; 14668ba21522SJames Collins } else { 14678ba21522SJames Collins $temp2 = $L < 0.5 ? 14688ba21522SJames Collins $L*(1.0 + $S) : 14698ba21522SJames Collins $L + $S - $L * $S; 14708ba21522SJames Collins 14718ba21522SJames Collins $temp1 = 2.0 * $L - $temp2; 14728ba21522SJames Collins 14738ba21522SJames Collins $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); 14748ba21522SJames Collins $g = $this->toRGB_helper($H, $temp1, $temp2); 14758ba21522SJames Collins $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); 14768ba21522SJames Collins } 14778ba21522SJames Collins 14788ba21522SJames Collins // $out = array('color', round($r*255), round($g*255), round($b*255)); 14798ba21522SJames Collins $out = array('color', $r*255, $g*255, $b*255); 14808ba21522SJames Collins if (count($color) > 4) $out[] = $color[4]; // copy alpha 14818ba21522SJames Collins return $out; 14828ba21522SJames Collins } 14838ba21522SJames Collins 14848ba21522SJames Collins protected function clamp($v, $max = 1, $min = 0) { 14858ba21522SJames Collins return min($max, max($min, $v)); 14868ba21522SJames Collins } 14878ba21522SJames Collins 14888ba21522SJames Collins /** 14898ba21522SJames Collins * Convert the rgb, rgba, hsl color literals of function type 14908ba21522SJames Collins * as returned by the parser into values of color type. 14918ba21522SJames Collins */ 14928ba21522SJames Collins protected function funcToColor($func) { 14938ba21522SJames Collins $fname = $func[1]; 14948ba21522SJames Collins if ($func[2][0] != 'list') return false; // need a list of arguments 14958ba21522SJames Collins $rawComponents = $func[2][2]; 14968ba21522SJames Collins 14978ba21522SJames Collins if ($fname == 'hsl' || $fname == 'hsla') { 14988ba21522SJames Collins $hsl = array('hsl'); 14998ba21522SJames Collins $i = 0; 15008ba21522SJames Collins foreach ($rawComponents as $c) { 15018ba21522SJames Collins $val = $this->reduce($c); 15028ba21522SJames Collins $val = isset($val[1]) ? floatval($val[1]) : 0; 15038ba21522SJames Collins 15048ba21522SJames Collins if ($i == 0) $clamp = 360; 15058ba21522SJames Collins elseif ($i < 3) $clamp = 100; 15068ba21522SJames Collins else $clamp = 1; 15078ba21522SJames Collins 15088ba21522SJames Collins $hsl[] = $this->clamp($val, $clamp); 15098ba21522SJames Collins $i++; 15108ba21522SJames Collins } 15118ba21522SJames Collins 15128ba21522SJames Collins while (count($hsl) < 4) $hsl[] = 0; 15138ba21522SJames Collins return $this->toRGB($hsl); 15148ba21522SJames Collins 15158ba21522SJames Collins } elseif ($fname == 'rgb' || $fname == 'rgba') { 15168ba21522SJames Collins $components = array(); 15178ba21522SJames Collins $i = 1; 15188ba21522SJames Collins foreach ($rawComponents as $c) { 15198ba21522SJames Collins $c = $this->reduce($c); 15208ba21522SJames Collins if ($i < 4) { 15218ba21522SJames Collins if ($c[0] == "number" && $c[2] == "%") { 15228ba21522SJames Collins $components[] = 255 * ($c[1] / 100); 15238ba21522SJames Collins } else { 15248ba21522SJames Collins $components[] = floatval($c[1]); 15258ba21522SJames Collins } 15268ba21522SJames Collins } elseif ($i == 4) { 15278ba21522SJames Collins if ($c[0] == "number" && $c[2] == "%") { 15288ba21522SJames Collins $components[] = 1.0 * ($c[1] / 100); 15298ba21522SJames Collins } else { 15308ba21522SJames Collins $components[] = floatval($c[1]); 15318ba21522SJames Collins } 15328ba21522SJames Collins } else break; 15338ba21522SJames Collins 15348ba21522SJames Collins $i++; 15358ba21522SJames Collins } 15368ba21522SJames Collins while (count($components) < 3) $components[] = 0; 15378ba21522SJames Collins array_unshift($components, 'color'); 15388ba21522SJames Collins return $this->fixColor($components); 15398ba21522SJames Collins } 15408ba21522SJames Collins 15418ba21522SJames Collins return false; 15428ba21522SJames Collins } 15438ba21522SJames Collins 15448ba21522SJames Collins protected function reduce($value, $forExpression = false) { 15458ba21522SJames Collins switch ($value[0]) { 15468ba21522SJames Collins case "interpolate": 15478ba21522SJames Collins $reduced = $this->reduce($value[1]); 15488ba21522SJames Collins $var = $this->compileValue($reduced); 15498ba21522SJames Collins $res = $this->reduce(array("variable", $this->vPrefix . $var)); 15508ba21522SJames Collins 15518ba21522SJames Collins if ($res[0] == "raw_color") { 15528ba21522SJames Collins $res = $this->coerceColor($res); 15538ba21522SJames Collins } 15548ba21522SJames Collins 15558ba21522SJames Collins if (empty($value[2])) $res = $this->lib_e($res); 15568ba21522SJames Collins 15578ba21522SJames Collins return $res; 15588ba21522SJames Collins case "variable": 15598ba21522SJames Collins $key = $value[1]; 15608ba21522SJames Collins if (is_array($key)) { 15618ba21522SJames Collins $key = $this->reduce($key); 15628ba21522SJames Collins $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); 15638ba21522SJames Collins } 15648ba21522SJames Collins 15658ba21522SJames Collins $seen =& $this->env->seenNames; 15668ba21522SJames Collins 15678ba21522SJames Collins if (!empty($seen[$key])) { 15688ba21522SJames Collins $this->throwError("infinite loop detected: $key"); 15698ba21522SJames Collins } 15708ba21522SJames Collins 15718ba21522SJames Collins $seen[$key] = true; 15728ba21522SJames Collins $out = $this->reduce($this->get($key)); 15738ba21522SJames Collins $seen[$key] = false; 15748ba21522SJames Collins return $out; 15758ba21522SJames Collins case "list": 15768ba21522SJames Collins foreach ($value[2] as &$item) { 15778ba21522SJames Collins $item = $this->reduce($item, $forExpression); 15788ba21522SJames Collins } 15798ba21522SJames Collins return $value; 15808ba21522SJames Collins case "expression": 15818ba21522SJames Collins return $this->evaluate($value); 15828ba21522SJames Collins case "string": 15838ba21522SJames Collins foreach ($value[2] as &$part) { 15848ba21522SJames Collins if (is_array($part)) { 15858ba21522SJames Collins $strip = $part[0] == "variable"; 15868ba21522SJames Collins $part = $this->reduce($part); 15878ba21522SJames Collins if ($strip) $part = $this->lib_e($part); 15888ba21522SJames Collins } 15898ba21522SJames Collins } 15908ba21522SJames Collins return $value; 15918ba21522SJames Collins case "escape": 15928ba21522SJames Collins [,$inner] = $value; 15938ba21522SJames Collins return $this->lib_e($this->reduce($inner)); 15948ba21522SJames Collins case "function": 15958ba21522SJames Collins $color = $this->funcToColor($value); 15968ba21522SJames Collins if ($color) return $color; 15978ba21522SJames Collins 15988ba21522SJames Collins [, $name, $args] = $value; 15998ba21522SJames Collins if ($name == "%") $name = "_sprintf"; 16008ba21522SJames Collins 16018ba21522SJames Collins $f = isset($this->libFunctions[$name]) ? 16028ba21522SJames Collins $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name)); 16038ba21522SJames Collins 16048ba21522SJames Collins if (is_callable($f)) { 16058ba21522SJames Collins if ($args[0] == 'list') 16068ba21522SJames Collins $args = self::compressList($args[2], $args[1]); 16078ba21522SJames Collins 16088ba21522SJames Collins $ret = call_user_func($f, $this->reduce($args, true), $this); 16098ba21522SJames Collins 16108ba21522SJames Collins if (is_null($ret)) { 16118ba21522SJames Collins return array("string", "", array( 16128ba21522SJames Collins $name, "(", $args, ")" 16138ba21522SJames Collins )); 16148ba21522SJames Collins } 16158ba21522SJames Collins 16168ba21522SJames Collins // convert to a typed value if the result is a php primitive 16178ba21522SJames Collins if (is_numeric($ret)) $ret = array('number', $ret, ""); 16188ba21522SJames Collins elseif (!is_array($ret)) $ret = array('keyword', $ret); 16198ba21522SJames Collins 16208ba21522SJames Collins return $ret; 16218ba21522SJames Collins } 16228ba21522SJames Collins 16238ba21522SJames Collins // plain function, reduce args 16248ba21522SJames Collins $value[2] = $this->reduce($value[2]); 16258ba21522SJames Collins return $value; 16268ba21522SJames Collins case "unary": 16278ba21522SJames Collins [, $op, $exp] = $value; 16288ba21522SJames Collins $exp = $this->reduce($exp); 16298ba21522SJames Collins 16308ba21522SJames Collins if ($exp[0] == "number") { 16318ba21522SJames Collins switch ($op) { 16328ba21522SJames Collins case "+": 16338ba21522SJames Collins return $exp; 16348ba21522SJames Collins case "-": 16358ba21522SJames Collins $exp[1] *= -1; 16368ba21522SJames Collins return $exp; 16378ba21522SJames Collins } 16388ba21522SJames Collins } 16398ba21522SJames Collins return array("string", "", array($op, $exp)); 16408ba21522SJames Collins } 16418ba21522SJames Collins 16428ba21522SJames Collins if ($forExpression) { 16438ba21522SJames Collins switch ($value[0]) { 16448ba21522SJames Collins case "keyword": 16458ba21522SJames Collins if ($color = $this->coerceColor($value)) { 16468ba21522SJames Collins return $color; 16478ba21522SJames Collins } 16488ba21522SJames Collins break; 16498ba21522SJames Collins case "raw_color": 16508ba21522SJames Collins return $this->coerceColor($value); 16518ba21522SJames Collins } 16528ba21522SJames Collins } 16538ba21522SJames Collins 16548ba21522SJames Collins return $value; 16558ba21522SJames Collins } 16568ba21522SJames Collins 16578ba21522SJames Collins 16588ba21522SJames Collins // coerce a value for use in color operation 16598ba21522SJames Collins protected function coerceColor($value) { 16608ba21522SJames Collins switch($value[0]) { 16618ba21522SJames Collins case 'color': return $value; 16628ba21522SJames Collins case 'raw_color': 16638ba21522SJames Collins $c = array("color", 0, 0, 0); 16648ba21522SJames Collins $colorStr = substr($value[1], 1); 16658ba21522SJames Collins $num = hexdec($colorStr); 16668ba21522SJames Collins $width = strlen($colorStr) == 3 ? 16 : 256; 16678ba21522SJames Collins 16688ba21522SJames Collins for ($i = 3; $i > 0; $i--) { // 3 2 1 16698ba21522SJames Collins $t = $num % $width; 16708ba21522SJames Collins $num /= $width; 16718ba21522SJames Collins 16728ba21522SJames Collins $c[$i] = $t * (256/$width) + $t * floor(16/$width); 16738ba21522SJames Collins } 16748ba21522SJames Collins 16758ba21522SJames Collins return $c; 16768ba21522SJames Collins case 'keyword': 16778ba21522SJames Collins $name = $value[1]; 16788ba21522SJames Collins if (isset(self::$cssColors[$name])) { 16798ba21522SJames Collins $rgba = explode(',', self::$cssColors[$name]); 16808ba21522SJames Collins 16818ba21522SJames Collins if(isset($rgba[3])) 16828ba21522SJames Collins return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]); 16838ba21522SJames Collins 16848ba21522SJames Collins return array('color', $rgba[0], $rgba[1], $rgba[2]); 16858ba21522SJames Collins } 16868ba21522SJames Collins return null; 16878ba21522SJames Collins } 16888ba21522SJames Collins } 16898ba21522SJames Collins 16908ba21522SJames Collins // make something string like into a string 16918ba21522SJames Collins protected function coerceString($value) { 16928ba21522SJames Collins switch ($value[0]) { 16938ba21522SJames Collins case "string": 16948ba21522SJames Collins return $value; 16958ba21522SJames Collins case "keyword": 16968ba21522SJames Collins return array("string", "", array($value[1])); 16978ba21522SJames Collins } 16988ba21522SJames Collins return null; 16998ba21522SJames Collins } 17008ba21522SJames Collins 17018ba21522SJames Collins // turn list of length 1 into value type 17028ba21522SJames Collins protected function flattenList($value) { 17038ba21522SJames Collins if ($value[0] == "list" && count($value[2]) == 1) { 17048ba21522SJames Collins return $this->flattenList($value[2][0]); 17058ba21522SJames Collins } 17068ba21522SJames Collins return $value; 17078ba21522SJames Collins } 17088ba21522SJames Collins 17098ba21522SJames Collins public function toBool($a) { 17108ba21522SJames Collins if ($a) return self::$TRUE; 17118ba21522SJames Collins else return self::$FALSE; 17128ba21522SJames Collins } 17138ba21522SJames Collins 17148ba21522SJames Collins // evaluate an expression 17158ba21522SJames Collins protected function evaluate($exp) { 17168ba21522SJames Collins [, $op, $left, $right, $whiteBefore, $whiteAfter] = $exp; 17178ba21522SJames Collins 17188ba21522SJames Collins $left = $this->reduce($left, true); 17198ba21522SJames Collins $right = $this->reduce($right, true); 17208ba21522SJames Collins 17218ba21522SJames Collins if ($leftColor = $this->coerceColor($left)) { 17228ba21522SJames Collins $left = $leftColor; 17238ba21522SJames Collins } 17248ba21522SJames Collins 17258ba21522SJames Collins if ($rightColor = $this->coerceColor($right)) { 17268ba21522SJames Collins $right = $rightColor; 17278ba21522SJames Collins } 17288ba21522SJames Collins 17298ba21522SJames Collins $ltype = $left[0]; 17308ba21522SJames Collins $rtype = $right[0]; 17318ba21522SJames Collins 17328ba21522SJames Collins // operators that work on all types 17338ba21522SJames Collins if ($op == "and") { 17348ba21522SJames Collins return $this->toBool($left == self::$TRUE && $right == self::$TRUE); 17358ba21522SJames Collins } 17368ba21522SJames Collins 17378ba21522SJames Collins if ($op == "=") { 17388ba21522SJames Collins return $this->toBool($this->eq($left, $right) ); 17398ba21522SJames Collins } 17408ba21522SJames Collins 17418ba21522SJames Collins if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { 17428ba21522SJames Collins return $str; 17438ba21522SJames Collins } 17448ba21522SJames Collins 17458ba21522SJames Collins // type based operators 1746*a92686aaSJames Collins $fname = "op_{$ltype}_{$rtype}"; 17478ba21522SJames Collins if (is_callable(array($this, $fname))) { 17488ba21522SJames Collins $out = $this->$fname($op, $left, $right); 17498ba21522SJames Collins if (!is_null($out)) return $out; 17508ba21522SJames Collins } 17518ba21522SJames Collins 17528ba21522SJames Collins // make the expression look it did before being parsed 17538ba21522SJames Collins $paddedOp = $op; 17548ba21522SJames Collins if ($whiteBefore) $paddedOp = " " . $paddedOp; 17558ba21522SJames Collins if ($whiteAfter) $paddedOp .= " "; 17568ba21522SJames Collins 17578ba21522SJames Collins return array("string", "", array($left, $paddedOp, $right)); 17588ba21522SJames Collins } 17598ba21522SJames Collins 17608ba21522SJames Collins protected function stringConcatenate($left, $right) { 17618ba21522SJames Collins if ($strLeft = $this->coerceString($left)) { 17628ba21522SJames Collins if ($right[0] == "string") { 17638ba21522SJames Collins $right[1] = ""; 17648ba21522SJames Collins } 17658ba21522SJames Collins $strLeft[2][] = $right; 17668ba21522SJames Collins return $strLeft; 17678ba21522SJames Collins } 17688ba21522SJames Collins 17698ba21522SJames Collins if ($strRight = $this->coerceString($right)) { 17708ba21522SJames Collins array_unshift($strRight[2], $left); 17718ba21522SJames Collins return $strRight; 17728ba21522SJames Collins } 17738ba21522SJames Collins } 17748ba21522SJames Collins 17758ba21522SJames Collins protected function convert( $number, $to ) 17768ba21522SJames Collins { 17778ba21522SJames Collins $value = $this->assertNumber( $number ); 17788ba21522SJames Collins $from = $number[2]; 17798ba21522SJames Collins 17808ba21522SJames Collins // easy out 17818ba21522SJames Collins if( $from == $to ) 17828ba21522SJames Collins return $number; 17838ba21522SJames Collins 17848ba21522SJames Collins // check if the from value is a length 17858ba21522SJames Collins if( ( $from_index = array_search( $from, self::$lengths ) ) !== false ) { 17868ba21522SJames Collins // make sure to value is too 17878ba21522SJames Collins if( in_array( $to, self::$lengths ) ) { 17888ba21522SJames Collins // do the actual conversion 17898ba21522SJames Collins $to_index = array_search( $to, self::$lengths ); 17908ba21522SJames Collins $px = $value * self::$lengths_to_base[ $from_index ]; 17918ba21522SJames Collins $result = $px * ( 1 / self::$lengths_to_base[ $to_index ] ); 17928ba21522SJames Collins 17938ba21522SJames Collins $result = round( $result, 8 ); 17948ba21522SJames Collins return array( "number", $result, $to ); 17958ba21522SJames Collins } 17968ba21522SJames Collins } 17978ba21522SJames Collins 17988ba21522SJames Collins // do the same check for times 17998ba21522SJames Collins if( in_array( $from, self::$times ) ) { 18008ba21522SJames Collins if( in_array( $to, self::$times ) ) { 18018ba21522SJames Collins // currently only ms and s are valid 18028ba21522SJames Collins if( $to == "ms" ) 18038ba21522SJames Collins $result = $value * 1000; 18048ba21522SJames Collins else 18058ba21522SJames Collins $result = $value / 1000; 18068ba21522SJames Collins 18078ba21522SJames Collins $result = round( $result, 8 ); 18088ba21522SJames Collins return array( "number", $result, $to ); 18098ba21522SJames Collins } 18108ba21522SJames Collins } 18118ba21522SJames Collins 18128ba21522SJames Collins // lastly check for an angle 18138ba21522SJames Collins if( in_array( $from, self::$angles ) ) { 18148ba21522SJames Collins // convert whatever angle it is into degrees 18158ba21522SJames Collins if( $from == "rad" ) 18168ba21522SJames Collins $deg = rad2deg( $value ); 18178ba21522SJames Collins 18188ba21522SJames Collins else if( $from == "turn" ) 18198ba21522SJames Collins $deg = $value * 360; 18208ba21522SJames Collins 18218ba21522SJames Collins else if( $from == "grad" ) 18228ba21522SJames Collins $deg = $value / (400 / 360); 18238ba21522SJames Collins 18248ba21522SJames Collins else 18258ba21522SJames Collins $deg = $value; 18268ba21522SJames Collins 18278ba21522SJames Collins // Then convert it from degrees into desired unit 18288ba21522SJames Collins if( $to == "deg" ) 18298ba21522SJames Collins $result = $deg; 18308ba21522SJames Collins 18318ba21522SJames Collins if( $to == "rad" ) 18328ba21522SJames Collins $result = deg2rad( $deg ); 18338ba21522SJames Collins 18348ba21522SJames Collins if( $to == "turn" ) 18358ba21522SJames Collins $result = $value / 360; 18368ba21522SJames Collins 18378ba21522SJames Collins if( $to == "grad" ) 18388ba21522SJames Collins $result = $value * (400 / 360); 18398ba21522SJames Collins 18408ba21522SJames Collins $result = round( $result, 8 ); 18418ba21522SJames Collins return array( "number", $result, $to ); 18428ba21522SJames Collins } 18438ba21522SJames Collins 18448ba21522SJames Collins // we don't know how to convert these 18458ba21522SJames Collins $this->throwError( "Cannot convert {$from} to {$to}" ); 18468ba21522SJames Collins } 18478ba21522SJames Collins 18488ba21522SJames Collins // make sure a color's components don't go out of bounds 18498ba21522SJames Collins protected function fixColor($c) { 18508ba21522SJames Collins foreach (range(1, 3) as $i) { 18518ba21522SJames Collins if ($c[$i] < 0) $c[$i] = 0; 18528ba21522SJames Collins if ($c[$i] > 255) $c[$i] = 255; 18538ba21522SJames Collins } 18548ba21522SJames Collins 18558ba21522SJames Collins return $c; 18568ba21522SJames Collins } 18578ba21522SJames Collins 18588ba21522SJames Collins protected function op_number_color($op, $lft, $rgt) { 18598ba21522SJames Collins if ($op == '+' || $op == '*') { 18608ba21522SJames Collins return $this->op_color_number($op, $rgt, $lft); 18618ba21522SJames Collins } 18628ba21522SJames Collins } 18638ba21522SJames Collins 18648ba21522SJames Collins protected function op_color_number($op, $lft, $rgt) { 18658ba21522SJames Collins if ($rgt[0] == '%') $rgt[1] /= 100; 18668ba21522SJames Collins 18678ba21522SJames Collins return $this->op_color_color($op, $lft, 18688ba21522SJames Collins array_fill(1, count($lft) - 1, $rgt[1])); 18698ba21522SJames Collins } 18708ba21522SJames Collins 18718ba21522SJames Collins protected function op_color_color($op, $left, $right) { 18728ba21522SJames Collins $out = array('color'); 18738ba21522SJames Collins $max = count($left) > count($right) ? count($left) : count($right); 18748ba21522SJames Collins foreach (range(1, $max - 1) as $i) { 18758ba21522SJames Collins $lval = isset($left[$i]) ? $left[$i] : 0; 18768ba21522SJames Collins $rval = isset($right[$i]) ? $right[$i] : 0; 18778ba21522SJames Collins switch ($op) { 18788ba21522SJames Collins case '+': 18798ba21522SJames Collins $out[] = $lval + $rval; 18808ba21522SJames Collins break; 18818ba21522SJames Collins case '-': 18828ba21522SJames Collins $out[] = $lval - $rval; 18838ba21522SJames Collins break; 18848ba21522SJames Collins case '*': 18858ba21522SJames Collins $out[] = $lval * $rval; 18868ba21522SJames Collins break; 18878ba21522SJames Collins case '%': 18888ba21522SJames Collins $out[] = $lval % $rval; 18898ba21522SJames Collins break; 18908ba21522SJames Collins case '/': 18918ba21522SJames Collins if ($rval == 0) $this->throwError("evaluate error: can't divide by zero"); 18928ba21522SJames Collins $out[] = $lval / $rval; 18938ba21522SJames Collins break; 18948ba21522SJames Collins default: 18958ba21522SJames Collins $this->throwError('evaluate error: color op number failed on op '.$op); 18968ba21522SJames Collins } 18978ba21522SJames Collins } 18988ba21522SJames Collins return $this->fixColor($out); 18998ba21522SJames Collins } 19008ba21522SJames Collins 19018ba21522SJames Collins function lib_red($color){ 19028ba21522SJames Collins $color = $this->coerceColor($color); 19038ba21522SJames Collins if (is_null($color)) { 19048ba21522SJames Collins $this->throwError('color expected for red()'); 19058ba21522SJames Collins } 19068ba21522SJames Collins 19078ba21522SJames Collins return $color[1]; 19088ba21522SJames Collins } 19098ba21522SJames Collins 19108ba21522SJames Collins function lib_green($color){ 19118ba21522SJames Collins $color = $this->coerceColor($color); 19128ba21522SJames Collins if (is_null($color)) { 19138ba21522SJames Collins $this->throwError('color expected for green()'); 19148ba21522SJames Collins } 19158ba21522SJames Collins 19168ba21522SJames Collins return $color[2]; 19178ba21522SJames Collins } 19188ba21522SJames Collins 19198ba21522SJames Collins function lib_blue($color){ 19208ba21522SJames Collins $color = $this->coerceColor($color); 19218ba21522SJames Collins if (is_null($color)) { 19228ba21522SJames Collins $this->throwError('color expected for blue()'); 19238ba21522SJames Collins } 19248ba21522SJames Collins 19258ba21522SJames Collins return $color[3]; 19268ba21522SJames Collins } 19278ba21522SJames Collins 19288ba21522SJames Collins 19298ba21522SJames Collins // operator on two numbers 19308ba21522SJames Collins protected function op_number_number($op, $left, $right) { 19318ba21522SJames Collins $unit = empty($left[2]) ? $right[2] : $left[2]; 19328ba21522SJames Collins 19338ba21522SJames Collins $value = 0; 19348ba21522SJames Collins switch ($op) { 19358ba21522SJames Collins case '+': 19368ba21522SJames Collins $value = $left[1] + $right[1]; 19378ba21522SJames Collins break; 19388ba21522SJames Collins case '*': 19398ba21522SJames Collins $value = $left[1] * $right[1]; 19408ba21522SJames Collins break; 19418ba21522SJames Collins case '-': 19428ba21522SJames Collins $value = $left[1] - $right[1]; 19438ba21522SJames Collins break; 19448ba21522SJames Collins case '%': 19458ba21522SJames Collins $value = $left[1] % $right[1]; 19468ba21522SJames Collins break; 19478ba21522SJames Collins case '/': 19488ba21522SJames Collins if ($right[1] == 0) $this->throwError('parse error: divide by zero'); 19498ba21522SJames Collins $value = $left[1] / $right[1]; 19508ba21522SJames Collins break; 19518ba21522SJames Collins case '<': 19528ba21522SJames Collins return $this->toBool($left[1] < $right[1]); 19538ba21522SJames Collins case '>': 19548ba21522SJames Collins return $this->toBool($left[1] > $right[1]); 19558ba21522SJames Collins case '>=': 19568ba21522SJames Collins return $this->toBool($left[1] >= $right[1]); 19578ba21522SJames Collins case '=<': 19588ba21522SJames Collins return $this->toBool($left[1] <= $right[1]); 19598ba21522SJames Collins default: 19608ba21522SJames Collins $this->throwError('parse error: unknown number operator: '.$op); 19618ba21522SJames Collins } 19628ba21522SJames Collins 19638ba21522SJames Collins return array("number", $value, $unit); 19648ba21522SJames Collins } 19658ba21522SJames Collins 19668ba21522SJames Collins 19678ba21522SJames Collins /* environment functions */ 19688ba21522SJames Collins 19698ba21522SJames Collins protected function makeOutputBlock($type, $selectors = null) { 19708ba21522SJames Collins $b = new stdclass; 19718ba21522SJames Collins $b->lines = array(); 19728ba21522SJames Collins $b->children = array(); 19738ba21522SJames Collins $b->selectors = $selectors; 19748ba21522SJames Collins $b->type = $type; 19758ba21522SJames Collins $b->parent = $this->scope; 19768ba21522SJames Collins return $b; 19778ba21522SJames Collins } 19788ba21522SJames Collins 19798ba21522SJames Collins // the state of execution 19808ba21522SJames Collins protected function pushEnv($block = null) { 19818ba21522SJames Collins $e = new stdclass; 19828ba21522SJames Collins $e->parent = $this->env; 19838ba21522SJames Collins $e->store = array(); 19848ba21522SJames Collins $e->block = $block; 19858ba21522SJames Collins 19868ba21522SJames Collins $this->env = $e; 19878ba21522SJames Collins return $e; 19888ba21522SJames Collins } 19898ba21522SJames Collins 19908ba21522SJames Collins // pop something off the stack 19918ba21522SJames Collins protected function popEnv() { 19928ba21522SJames Collins $old = $this->env; 19938ba21522SJames Collins $this->env = $this->env->parent; 19948ba21522SJames Collins return $old; 19958ba21522SJames Collins } 19968ba21522SJames Collins 19978ba21522SJames Collins // set something in the current env 19988ba21522SJames Collins protected function set($name, $value) { 19998ba21522SJames Collins $this->env->store[$name] = $value; 20008ba21522SJames Collins } 20018ba21522SJames Collins 20028ba21522SJames Collins 20038ba21522SJames Collins // get the highest occurrence entry for a name 20048ba21522SJames Collins protected function get($name) { 20058ba21522SJames Collins $current = $this->env; 20068ba21522SJames Collins 20078ba21522SJames Collins // track scope to evaluate 20088ba21522SJames Collins $scope_secondary = array(); 20098ba21522SJames Collins 20108ba21522SJames Collins $isArguments = $name == $this->vPrefix . 'arguments'; 20118ba21522SJames Collins while ($current) { 20128ba21522SJames Collins if ($isArguments && isset($current->arguments)) { 20138ba21522SJames Collins return array('list', ' ', $current->arguments); 20148ba21522SJames Collins } 20158ba21522SJames Collins 20168ba21522SJames Collins if (isset($current->store[$name])) 20178ba21522SJames Collins return $current->store[$name]; 20188ba21522SJames Collins // has secondary scope? 20198ba21522SJames Collins if (isset($current->storeParent)) 20208ba21522SJames Collins $scope_secondary[] = $current->storeParent; 20218ba21522SJames Collins 20228ba21522SJames Collins if (isset($current->parent)) 20238ba21522SJames Collins $current = $current->parent; 20248ba21522SJames Collins else 20258ba21522SJames Collins $current = null; 20268ba21522SJames Collins } 20278ba21522SJames Collins 20288ba21522SJames Collins while (count($scope_secondary)) { 20298ba21522SJames Collins // pop one off 20308ba21522SJames Collins $current = array_shift($scope_secondary); 20318ba21522SJames Collins while ($current) { 20328ba21522SJames Collins if ($isArguments && isset($current->arguments)) { 20338ba21522SJames Collins return array('list', ' ', $current->arguments); 20348ba21522SJames Collins } 20358ba21522SJames Collins 20368ba21522SJames Collins if (isset($current->store[$name])) { 20378ba21522SJames Collins return $current->store[$name]; 20388ba21522SJames Collins } 20398ba21522SJames Collins 20408ba21522SJames Collins // has secondary scope? 20418ba21522SJames Collins if (isset($current->storeParent)) { 20428ba21522SJames Collins $scope_secondary[] = $current->storeParent; 20438ba21522SJames Collins } 20448ba21522SJames Collins 20458ba21522SJames Collins if (isset($current->parent)) { 20468ba21522SJames Collins $current = $current->parent; 20478ba21522SJames Collins } else { 20488ba21522SJames Collins $current = null; 20498ba21522SJames Collins } 20508ba21522SJames Collins } 20518ba21522SJames Collins } 20528ba21522SJames Collins 20538ba21522SJames Collins $this->throwError("variable $name is undefined"); 20548ba21522SJames Collins } 20558ba21522SJames Collins 20568ba21522SJames Collins // inject array of unparsed strings into environment as variables 20578ba21522SJames Collins protected function injectVariables($args) { 20588ba21522SJames Collins $this->pushEnv(); 20598ba21522SJames Collins $parser = new lessc_parser($this, __METHOD__); 20608ba21522SJames Collins foreach ($args as $name => $strValue) { 20618ba21522SJames Collins if ($name[0] != '@') $name = '@'.$name; 20628ba21522SJames Collins $parser->count = 0; 20638ba21522SJames Collins $parser->buffer = (string)$strValue; 20648ba21522SJames Collins if (!$parser->propertyValue($value)) { 20658ba21522SJames Collins throw new \Exception("failed to parse passed in variable $name: $strValue"); 20668ba21522SJames Collins } 20678ba21522SJames Collins 20688ba21522SJames Collins $this->set($name, $value); 20698ba21522SJames Collins } 20708ba21522SJames Collins } 20718ba21522SJames Collins 20728ba21522SJames Collins /** 20738ba21522SJames Collins * Initialize any static state, can initialize parser for a file 20748ba21522SJames Collins * $opts isn't used yet 20758ba21522SJames Collins */ 20768ba21522SJames Collins public function __construct($fname = null) { 20778ba21522SJames Collins if ($fname !== null) { 20788ba21522SJames Collins // used for deprecated parse method 20798ba21522SJames Collins $this->_parseFile = $fname; 20808ba21522SJames Collins } 20818ba21522SJames Collins } 20828ba21522SJames Collins 20838ba21522SJames Collins public function compile($string, $name = null) { 20848ba21522SJames Collins $locale = setlocale(LC_NUMERIC, 0); 20858ba21522SJames Collins setlocale(LC_NUMERIC, "C"); 20868ba21522SJames Collins 20878ba21522SJames Collins $this->parser = $this->makeParser($name); 20888ba21522SJames Collins $root = $this->parser->parse($string); 20898ba21522SJames Collins 20908ba21522SJames Collins $this->env = null; 20918ba21522SJames Collins $this->scope = null; 20928ba21522SJames Collins $this->allParsedFiles = array(); 20938ba21522SJames Collins 20948ba21522SJames Collins $this->formatter = $this->newFormatter(); 20958ba21522SJames Collins 20968ba21522SJames Collins if (!empty($this->registeredVars)) { 20978ba21522SJames Collins $this->injectVariables($this->registeredVars); 20988ba21522SJames Collins } 20998ba21522SJames Collins 21008ba21522SJames Collins $this->sourceParser = $this->parser; // used for error messages 21018ba21522SJames Collins $this->compileBlock($root); 21028ba21522SJames Collins 21038ba21522SJames Collins ob_start(); 21048ba21522SJames Collins $this->formatter->block($this->scope); 21058ba21522SJames Collins $out = ob_get_clean(); 21068ba21522SJames Collins setlocale(LC_NUMERIC, $locale); 21078ba21522SJames Collins return $out; 21088ba21522SJames Collins } 21098ba21522SJames Collins 21108ba21522SJames Collins public function compileFile($fname, $outFname = null) { 21118ba21522SJames Collins if (!is_readable($fname)) { 21128ba21522SJames Collins throw new \Exception('load error: failed to find '.$fname); 21138ba21522SJames Collins } 21148ba21522SJames Collins 21158ba21522SJames Collins $pi = pathinfo($fname); 21168ba21522SJames Collins 21178ba21522SJames Collins $oldImport = $this->importDir; 21188ba21522SJames Collins 21198ba21522SJames Collins $this->importDir = (array)$this->importDir; 21208ba21522SJames Collins $this->importDir[] = $pi['dirname'].'/'; 21218ba21522SJames Collins 21228ba21522SJames Collins $this->addParsedFile($fname); 21238ba21522SJames Collins 21248ba21522SJames Collins $out = $this->compile(file_get_contents($fname), $fname); 21258ba21522SJames Collins 21268ba21522SJames Collins $this->importDir = $oldImport; 21278ba21522SJames Collins 21288ba21522SJames Collins if ($outFname !== null) { 21298ba21522SJames Collins return file_put_contents($outFname, $out); 21308ba21522SJames Collins } 21318ba21522SJames Collins 21328ba21522SJames Collins return $out; 21338ba21522SJames Collins } 21348ba21522SJames Collins 21358ba21522SJames Collins /** 21368ba21522SJames Collins * Based on explicit input/output files does a full change check on cache before compiling. 21378ba21522SJames Collins * 21388ba21522SJames Collins * @param string $in 21398ba21522SJames Collins * @param string $out 21408ba21522SJames Collins * @param boolean $force 21418ba21522SJames Collins * @return string Compiled CSS results 21428ba21522SJames Collins * @throws Exception 21438ba21522SJames Collins */ 21448ba21522SJames Collins public function checkedCachedCompile($in, $out, $force = false) { 21458ba21522SJames Collins if (!is_file($in) || !is_readable($in)) { 21468ba21522SJames Collins throw new Exception('Invalid or unreadable input file specified.'); 21478ba21522SJames Collins } 21488ba21522SJames Collins if (is_dir($out) || !is_writable(file_exists($out) ? $out : dirname($out))) { 21498ba21522SJames Collins throw new Exception('Invalid or unwritable output file specified.'); 21508ba21522SJames Collins } 21518ba21522SJames Collins 21528ba21522SJames Collins $outMeta = $out . '.meta'; 21538ba21522SJames Collins $metadata = null; 21548ba21522SJames Collins if (!$force && is_file($outMeta)) { 21558ba21522SJames Collins $metadata = unserialize(file_get_contents($outMeta)); 21568ba21522SJames Collins } 21578ba21522SJames Collins 21588ba21522SJames Collins $output = $this->cachedCompile($metadata ? $metadata : $in); 21598ba21522SJames Collins 21608ba21522SJames Collins if (!$metadata || $metadata['updated'] != $output['updated']) { 21618ba21522SJames Collins $css = $output['compiled']; 21628ba21522SJames Collins unset($output['compiled']); 21638ba21522SJames Collins file_put_contents($out, $css); 21648ba21522SJames Collins file_put_contents($outMeta, serialize($output)); 21658ba21522SJames Collins } else { 21668ba21522SJames Collins $css = file_get_contents($out); 21678ba21522SJames Collins } 21688ba21522SJames Collins 21698ba21522SJames Collins return $css; 21708ba21522SJames Collins } 21718ba21522SJames Collins 21728ba21522SJames Collins // compile only if changed input has changed or output doesn't exist 21738ba21522SJames Collins public function checkedCompile($in, $out) { 21748ba21522SJames Collins if (!is_file($out) || filemtime($in) > filemtime($out)) { 21758ba21522SJames Collins $this->compileFile($in, $out); 21768ba21522SJames Collins return true; 21778ba21522SJames Collins } 21788ba21522SJames Collins return false; 21798ba21522SJames Collins } 21808ba21522SJames Collins 21818ba21522SJames Collins /** 21828ba21522SJames Collins * Execute lessphp on a .less file or a lessphp cache structure 21838ba21522SJames Collins * 21848ba21522SJames Collins * The lessphp cache structure contains information about a specific 21858ba21522SJames Collins * less file having been parsed. It can be used as a hint for future 21868ba21522SJames Collins * calls to determine whether or not a rebuild is required. 21878ba21522SJames Collins * 21888ba21522SJames Collins * The cache structure contains two important keys that may be used 21898ba21522SJames Collins * externally: 21908ba21522SJames Collins * 21918ba21522SJames Collins * compiled: The final compiled CSS 21928ba21522SJames Collins * updated: The time (in seconds) the CSS was last compiled 21938ba21522SJames Collins * 21948ba21522SJames Collins * The cache structure is a plain-ol' PHP associative array and can 21958ba21522SJames Collins * be serialized and unserialized without a hitch. 21968ba21522SJames Collins * 21978ba21522SJames Collins * @param mixed $in Input 21988ba21522SJames Collins * @param bool $force Force rebuild? 21998ba21522SJames Collins * @return array lessphp cache structure 22008ba21522SJames Collins */ 22018ba21522SJames Collins public function cachedCompile($in, $force = false) { 22028ba21522SJames Collins // assume no root 22038ba21522SJames Collins $root = null; 22048ba21522SJames Collins 22058ba21522SJames Collins if (is_string($in)) { 22068ba21522SJames Collins $root = $in; 22078ba21522SJames Collins } elseif (is_array($in) and isset($in['root'])) { 22088ba21522SJames Collins if ($force or ! isset($in['files'])) { 22098ba21522SJames Collins // If we are forcing a recompile or if for some reason the 22108ba21522SJames Collins // structure does not contain any file information we should 22118ba21522SJames Collins // specify the root to trigger a rebuild. 22128ba21522SJames Collins $root = $in['root']; 22138ba21522SJames Collins } elseif (isset($in['files']) and is_array($in['files'])) { 22148ba21522SJames Collins foreach ($in['files'] as $fname => $ftime ) { 22158ba21522SJames Collins if (!file_exists($fname) or filemtime($fname) > $ftime) { 22168ba21522SJames Collins // One of the files we knew about previously has changed 22178ba21522SJames Collins // so we should look at our incoming root again. 22188ba21522SJames Collins $root = $in['root']; 22198ba21522SJames Collins break; 22208ba21522SJames Collins } 22218ba21522SJames Collins } 22228ba21522SJames Collins } 22238ba21522SJames Collins } else { 22248ba21522SJames Collins // TODO: Throw an exception? We got neither a string nor something 22258ba21522SJames Collins // that looks like a compatible lessphp cache structure. 22268ba21522SJames Collins return null; 22278ba21522SJames Collins } 22288ba21522SJames Collins 22298ba21522SJames Collins if ($root !== null) { 22308ba21522SJames Collins // If we have a root value which means we should rebuild. 22318ba21522SJames Collins $out = array(); 22328ba21522SJames Collins $out['root'] = $root; 22338ba21522SJames Collins $out['compiled'] = $this->compileFile($root); 22348ba21522SJames Collins $out['files'] = $this->allParsedFiles(); 22358ba21522SJames Collins $out['updated'] = time(); 22368ba21522SJames Collins return $out; 22378ba21522SJames Collins } else { 22388ba21522SJames Collins // No changes, pass back the structure 22398ba21522SJames Collins // we were given initially. 22408ba21522SJames Collins return $in; 22418ba21522SJames Collins } 22428ba21522SJames Collins 22438ba21522SJames Collins } 22448ba21522SJames Collins 22458ba21522SJames Collins // parse and compile buffer 22468ba21522SJames Collins // This is deprecated 22478ba21522SJames Collins public function parse($str = null, $initialVariables = null) { 22488ba21522SJames Collins if (is_array($str)) { 22498ba21522SJames Collins $initialVariables = $str; 22508ba21522SJames Collins $str = null; 22518ba21522SJames Collins } 22528ba21522SJames Collins 22538ba21522SJames Collins $oldVars = $this->registeredVars; 22548ba21522SJames Collins if ($initialVariables !== null) { 22558ba21522SJames Collins $this->setVariables($initialVariables); 22568ba21522SJames Collins } 22578ba21522SJames Collins 22588ba21522SJames Collins if ($str == null) { 22598ba21522SJames Collins if (empty($this->_parseFile)) { 22608ba21522SJames Collins throw new \Exception("nothing to parse"); 22618ba21522SJames Collins } 22628ba21522SJames Collins 22638ba21522SJames Collins $out = $this->compileFile($this->_parseFile); 22648ba21522SJames Collins } else { 22658ba21522SJames Collins $out = $this->compile($str); 22668ba21522SJames Collins } 22678ba21522SJames Collins 22688ba21522SJames Collins $this->registeredVars = $oldVars; 22698ba21522SJames Collins return $out; 22708ba21522SJames Collins } 22718ba21522SJames Collins 22728ba21522SJames Collins protected function makeParser($name) { 22738ba21522SJames Collins $parser = new lessc_parser($this, $name); 22748ba21522SJames Collins $parser->writeComments = $this->preserveComments; 22758ba21522SJames Collins 22768ba21522SJames Collins return $parser; 22778ba21522SJames Collins } 22788ba21522SJames Collins 22798ba21522SJames Collins public function setFormatter($name) { 22808ba21522SJames Collins $this->formatterName = $name; 22818ba21522SJames Collins } 22828ba21522SJames Collins 22838ba21522SJames Collins protected function newFormatter() { 22848ba21522SJames Collins $className = "lessc_formatter_lessjs"; 22858ba21522SJames Collins if (!empty($this->formatterName)) { 22868ba21522SJames Collins if (!is_string($this->formatterName)) 22878ba21522SJames Collins return $this->formatterName; 22888ba21522SJames Collins $className = "lessc_formatter_$this->formatterName"; 22898ba21522SJames Collins } 22908ba21522SJames Collins 22918ba21522SJames Collins return new $className; 22928ba21522SJames Collins } 22938ba21522SJames Collins 22948ba21522SJames Collins public function setPreserveComments($preserve) { 22958ba21522SJames Collins $this->preserveComments = $preserve; 22968ba21522SJames Collins } 22978ba21522SJames Collins 22988ba21522SJames Collins public function registerFunction($name, $func) { 22998ba21522SJames Collins $this->libFunctions[$name] = $func; 23008ba21522SJames Collins } 23018ba21522SJames Collins 23028ba21522SJames Collins public function unregisterFunction($name) { 23038ba21522SJames Collins unset($this->libFunctions[$name]); 23048ba21522SJames Collins } 23058ba21522SJames Collins 23068ba21522SJames Collins public function setVariables($variables) { 23078ba21522SJames Collins $this->registeredVars = array_merge($this->registeredVars, $variables); 23088ba21522SJames Collins } 23098ba21522SJames Collins 23108ba21522SJames Collins public function unsetVariable($name) { 23118ba21522SJames Collins unset($this->registeredVars[$name]); 23128ba21522SJames Collins } 23138ba21522SJames Collins 23148ba21522SJames Collins public function setImportDir($dirs) { 23158ba21522SJames Collins $this->importDir = (array)$dirs; 23168ba21522SJames Collins } 23178ba21522SJames Collins 23188ba21522SJames Collins public function addImportDir($dir) { 23198ba21522SJames Collins $this->importDir = (array)$this->importDir; 23208ba21522SJames Collins $this->importDir[] = $dir; 23218ba21522SJames Collins } 23228ba21522SJames Collins 23238ba21522SJames Collins public function allParsedFiles() { 23248ba21522SJames Collins return $this->allParsedFiles; 23258ba21522SJames Collins } 23268ba21522SJames Collins 23278ba21522SJames Collins public function addParsedFile($file) { 23288ba21522SJames Collins $this->allParsedFiles[realpath($file)] = filemtime($file); 23298ba21522SJames Collins } 23308ba21522SJames Collins 23318ba21522SJames Collins /** 23328ba21522SJames Collins * Uses the current value of $this->count to show line and line number 23338ba21522SJames Collins */ 23348ba21522SJames Collins public function throwError($msg = null) { 23358ba21522SJames Collins if ($this->sourceLoc >= 0) { 23368ba21522SJames Collins $this->sourceParser->throwError($msg, $this->sourceLoc); 23378ba21522SJames Collins } 23388ba21522SJames Collins throw new \Exception($msg); 23398ba21522SJames Collins } 23408ba21522SJames Collins 23418ba21522SJames Collins // compile file $in to file $out if $in is newer than $out 23428ba21522SJames Collins // returns true when it compiles, false otherwise 23438ba21522SJames Collins public static function ccompile($in, $out, $less = null) { 23448ba21522SJames Collins if ($less === null) { 23458ba21522SJames Collins $less = new self; 23468ba21522SJames Collins } 23478ba21522SJames Collins return $less->checkedCompile($in, $out); 23488ba21522SJames Collins } 23498ba21522SJames Collins 23508ba21522SJames Collins public static function cexecute($in, $force = false, $less = null) { 23518ba21522SJames Collins if ($less === null) { 23528ba21522SJames Collins $less = new self; 23538ba21522SJames Collins } 23548ba21522SJames Collins return $less->cachedCompile($in, $force); 23558ba21522SJames Collins } 23568ba21522SJames Collins 23578ba21522SJames Collins static protected $cssColors = array( 23588ba21522SJames Collins 'aliceblue' => '240,248,255', 23598ba21522SJames Collins 'antiquewhite' => '250,235,215', 23608ba21522SJames Collins 'aqua' => '0,255,255', 23618ba21522SJames Collins 'aquamarine' => '127,255,212', 23628ba21522SJames Collins 'azure' => '240,255,255', 23638ba21522SJames Collins 'beige' => '245,245,220', 23648ba21522SJames Collins 'bisque' => '255,228,196', 23658ba21522SJames Collins 'black' => '0,0,0', 23668ba21522SJames Collins 'blanchedalmond' => '255,235,205', 23678ba21522SJames Collins 'blue' => '0,0,255', 23688ba21522SJames Collins 'blueviolet' => '138,43,226', 23698ba21522SJames Collins 'brown' => '165,42,42', 23708ba21522SJames Collins 'burlywood' => '222,184,135', 23718ba21522SJames Collins 'cadetblue' => '95,158,160', 23728ba21522SJames Collins 'chartreuse' => '127,255,0', 23738ba21522SJames Collins 'chocolate' => '210,105,30', 23748ba21522SJames Collins 'coral' => '255,127,80', 23758ba21522SJames Collins 'cornflowerblue' => '100,149,237', 23768ba21522SJames Collins 'cornsilk' => '255,248,220', 23778ba21522SJames Collins 'crimson' => '220,20,60', 23788ba21522SJames Collins 'cyan' => '0,255,255', 23798ba21522SJames Collins 'darkblue' => '0,0,139', 23808ba21522SJames Collins 'darkcyan' => '0,139,139', 23818ba21522SJames Collins 'darkgoldenrod' => '184,134,11', 23828ba21522SJames Collins 'darkgray' => '169,169,169', 23838ba21522SJames Collins 'darkgreen' => '0,100,0', 23848ba21522SJames Collins 'darkgrey' => '169,169,169', 23858ba21522SJames Collins 'darkkhaki' => '189,183,107', 23868ba21522SJames Collins 'darkmagenta' => '139,0,139', 23878ba21522SJames Collins 'darkolivegreen' => '85,107,47', 23888ba21522SJames Collins 'darkorange' => '255,140,0', 23898ba21522SJames Collins 'darkorchid' => '153,50,204', 23908ba21522SJames Collins 'darkred' => '139,0,0', 23918ba21522SJames Collins 'darksalmon' => '233,150,122', 23928ba21522SJames Collins 'darkseagreen' => '143,188,143', 23938ba21522SJames Collins 'darkslateblue' => '72,61,139', 23948ba21522SJames Collins 'darkslategray' => '47,79,79', 23958ba21522SJames Collins 'darkslategrey' => '47,79,79', 23968ba21522SJames Collins 'darkturquoise' => '0,206,209', 23978ba21522SJames Collins 'darkviolet' => '148,0,211', 23988ba21522SJames Collins 'deeppink' => '255,20,147', 23998ba21522SJames Collins 'deepskyblue' => '0,191,255', 24008ba21522SJames Collins 'dimgray' => '105,105,105', 24018ba21522SJames Collins 'dimgrey' => '105,105,105', 24028ba21522SJames Collins 'dodgerblue' => '30,144,255', 24038ba21522SJames Collins 'firebrick' => '178,34,34', 24048ba21522SJames Collins 'floralwhite' => '255,250,240', 24058ba21522SJames Collins 'forestgreen' => '34,139,34', 24068ba21522SJames Collins 'fuchsia' => '255,0,255', 24078ba21522SJames Collins 'gainsboro' => '220,220,220', 24088ba21522SJames Collins 'ghostwhite' => '248,248,255', 24098ba21522SJames Collins 'gold' => '255,215,0', 24108ba21522SJames Collins 'goldenrod' => '218,165,32', 24118ba21522SJames Collins 'gray' => '128,128,128', 24128ba21522SJames Collins 'green' => '0,128,0', 24138ba21522SJames Collins 'greenyellow' => '173,255,47', 24148ba21522SJames Collins 'grey' => '128,128,128', 24158ba21522SJames Collins 'honeydew' => '240,255,240', 24168ba21522SJames Collins 'hotpink' => '255,105,180', 24178ba21522SJames Collins 'indianred' => '205,92,92', 24188ba21522SJames Collins 'indigo' => '75,0,130', 24198ba21522SJames Collins 'ivory' => '255,255,240', 24208ba21522SJames Collins 'khaki' => '240,230,140', 24218ba21522SJames Collins 'lavender' => '230,230,250', 24228ba21522SJames Collins 'lavenderblush' => '255,240,245', 24238ba21522SJames Collins 'lawngreen' => '124,252,0', 24248ba21522SJames Collins 'lemonchiffon' => '255,250,205', 24258ba21522SJames Collins 'lightblue' => '173,216,230', 24268ba21522SJames Collins 'lightcoral' => '240,128,128', 24278ba21522SJames Collins 'lightcyan' => '224,255,255', 24288ba21522SJames Collins 'lightgoldenrodyellow' => '250,250,210', 24298ba21522SJames Collins 'lightgray' => '211,211,211', 24308ba21522SJames Collins 'lightgreen' => '144,238,144', 24318ba21522SJames Collins 'lightgrey' => '211,211,211', 24328ba21522SJames Collins 'lightpink' => '255,182,193', 24338ba21522SJames Collins 'lightsalmon' => '255,160,122', 24348ba21522SJames Collins 'lightseagreen' => '32,178,170', 24358ba21522SJames Collins 'lightskyblue' => '135,206,250', 24368ba21522SJames Collins 'lightslategray' => '119,136,153', 24378ba21522SJames Collins 'lightslategrey' => '119,136,153', 24388ba21522SJames Collins 'lightsteelblue' => '176,196,222', 24398ba21522SJames Collins 'lightyellow' => '255,255,224', 24408ba21522SJames Collins 'lime' => '0,255,0', 24418ba21522SJames Collins 'limegreen' => '50,205,50', 24428ba21522SJames Collins 'linen' => '250,240,230', 24438ba21522SJames Collins 'magenta' => '255,0,255', 24448ba21522SJames Collins 'maroon' => '128,0,0', 24458ba21522SJames Collins 'mediumaquamarine' => '102,205,170', 24468ba21522SJames Collins 'mediumblue' => '0,0,205', 24478ba21522SJames Collins 'mediumorchid' => '186,85,211', 24488ba21522SJames Collins 'mediumpurple' => '147,112,219', 24498ba21522SJames Collins 'mediumseagreen' => '60,179,113', 24508ba21522SJames Collins 'mediumslateblue' => '123,104,238', 24518ba21522SJames Collins 'mediumspringgreen' => '0,250,154', 24528ba21522SJames Collins 'mediumturquoise' => '72,209,204', 24538ba21522SJames Collins 'mediumvioletred' => '199,21,133', 24548ba21522SJames Collins 'midnightblue' => '25,25,112', 24558ba21522SJames Collins 'mintcream' => '245,255,250', 24568ba21522SJames Collins 'mistyrose' => '255,228,225', 24578ba21522SJames Collins 'moccasin' => '255,228,181', 24588ba21522SJames Collins 'navajowhite' => '255,222,173', 24598ba21522SJames Collins 'navy' => '0,0,128', 24608ba21522SJames Collins 'oldlace' => '253,245,230', 24618ba21522SJames Collins 'olive' => '128,128,0', 24628ba21522SJames Collins 'olivedrab' => '107,142,35', 24638ba21522SJames Collins 'orange' => '255,165,0', 24648ba21522SJames Collins 'orangered' => '255,69,0', 24658ba21522SJames Collins 'orchid' => '218,112,214', 24668ba21522SJames Collins 'palegoldenrod' => '238,232,170', 24678ba21522SJames Collins 'palegreen' => '152,251,152', 24688ba21522SJames Collins 'paleturquoise' => '175,238,238', 24698ba21522SJames Collins 'palevioletred' => '219,112,147', 24708ba21522SJames Collins 'papayawhip' => '255,239,213', 24718ba21522SJames Collins 'peachpuff' => '255,218,185', 24728ba21522SJames Collins 'peru' => '205,133,63', 24738ba21522SJames Collins 'pink' => '255,192,203', 24748ba21522SJames Collins 'plum' => '221,160,221', 24758ba21522SJames Collins 'powderblue' => '176,224,230', 24768ba21522SJames Collins 'purple' => '128,0,128', 24778ba21522SJames Collins 'red' => '255,0,0', 24788ba21522SJames Collins 'rosybrown' => '188,143,143', 24798ba21522SJames Collins 'royalblue' => '65,105,225', 24808ba21522SJames Collins 'saddlebrown' => '139,69,19', 24818ba21522SJames Collins 'salmon' => '250,128,114', 24828ba21522SJames Collins 'sandybrown' => '244,164,96', 24838ba21522SJames Collins 'seagreen' => '46,139,87', 24848ba21522SJames Collins 'seashell' => '255,245,238', 24858ba21522SJames Collins 'sienna' => '160,82,45', 24868ba21522SJames Collins 'silver' => '192,192,192', 24878ba21522SJames Collins 'skyblue' => '135,206,235', 24888ba21522SJames Collins 'slateblue' => '106,90,205', 24898ba21522SJames Collins 'slategray' => '112,128,144', 24908ba21522SJames Collins 'slategrey' => '112,128,144', 24918ba21522SJames Collins 'snow' => '255,250,250', 24928ba21522SJames Collins 'springgreen' => '0,255,127', 24938ba21522SJames Collins 'steelblue' => '70,130,180', 24948ba21522SJames Collins 'tan' => '210,180,140', 24958ba21522SJames Collins 'teal' => '0,128,128', 24968ba21522SJames Collins 'thistle' => '216,191,216', 24978ba21522SJames Collins 'tomato' => '255,99,71', 24988ba21522SJames Collins 'transparent' => '0,0,0,0', 24998ba21522SJames Collins 'turquoise' => '64,224,208', 25008ba21522SJames Collins 'violet' => '238,130,238', 25018ba21522SJames Collins 'wheat' => '245,222,179', 25028ba21522SJames Collins 'white' => '255,255,255', 25038ba21522SJames Collins 'whitesmoke' => '245,245,245', 25048ba21522SJames Collins 'yellow' => '255,255,0', 25058ba21522SJames Collins 'yellowgreen' => '154,205,50' 25068ba21522SJames Collins ); 25078ba21522SJames Collins} 25088ba21522SJames Collins 25098ba21522SJames Collins// responsible for taking a string of LESS code and converting it into a 25108ba21522SJames Collins// syntax tree 25118ba21522SJames Collinsclass lessc_parser { 25128ba21522SJames Collins static protected $nextBlockId = 0; // used to uniquely identify blocks 25138ba21522SJames Collins 25148ba21522SJames Collins static protected $precedence = array( 25158ba21522SJames Collins '=<' => 0, 25168ba21522SJames Collins '>=' => 0, 25178ba21522SJames Collins '=' => 0, 25188ba21522SJames Collins '<' => 0, 25198ba21522SJames Collins '>' => 0, 25208ba21522SJames Collins 25218ba21522SJames Collins '+' => 1, 25228ba21522SJames Collins '-' => 1, 25238ba21522SJames Collins '*' => 2, 25248ba21522SJames Collins '/' => 2, 25258ba21522SJames Collins '%' => 2, 25268ba21522SJames Collins ); 25278ba21522SJames Collins 25288ba21522SJames Collins static protected $whitePattern; 25298ba21522SJames Collins static protected $commentMulti; 25308ba21522SJames Collins 25318ba21522SJames Collins static protected $commentSingle = "//"; 25328ba21522SJames Collins static protected $commentMultiLeft = "/*"; 25338ba21522SJames Collins static protected $commentMultiRight = "*/"; 25348ba21522SJames Collins 25358ba21522SJames Collins // regex string to match any of the operators 25368ba21522SJames Collins static protected $operatorString; 25378ba21522SJames Collins 25388ba21522SJames Collins // these properties will supress division unless it's inside parenthases 25398ba21522SJames Collins static protected $supressDivisionProps = 25408ba21522SJames Collins array('/border-radius$/i', '/^font$/i'); 25418ba21522SJames Collins 25428ba21522SJames Collins protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport"); 25438ba21522SJames Collins protected $lineDirectives = array("charset"); 25448ba21522SJames Collins 25458ba21522SJames Collins /** 25468ba21522SJames Collins * if we are in parens we can be more liberal with whitespace around 25478ba21522SJames Collins * operators because it must evaluate to a single value and thus is less 25488ba21522SJames Collins * ambiguous. 25498ba21522SJames Collins * 25508ba21522SJames Collins * Consider: 25518ba21522SJames Collins * property1: 10 -5; // is two numbers, 10 and -5 25528ba21522SJames Collins * property2: (10 -5); // should evaluate to 5 25538ba21522SJames Collins */ 25548ba21522SJames Collins protected $inParens = false; 25558ba21522SJames Collins 25568ba21522SJames Collins // caches preg escaped literals 25578ba21522SJames Collins static protected $literalCache = array(); 25588ba21522SJames Collins 2559*a92686aaSJames Collins protected $eatWhiteDefault; 2560*a92686aaSJames Collins protected $lessc; 2561*a92686aaSJames Collins protected $sourceName; 2562*a92686aaSJames Collins public $writeComments; 2563*a92686aaSJames Collins public $count; 2564*a92686aaSJames Collins protected $line; 2565*a92686aaSJames Collins protected $env; 2566*a92686aaSJames Collins public $buffer; 2567*a92686aaSJames Collins protected $seenComments; 2568*a92686aaSJames Collins protected $inExp; 2569*a92686aaSJames Collins 2570*a92686aaSJames Collins 25718ba21522SJames Collins public function __construct($lessc, $sourceName = null) { 25728ba21522SJames Collins $this->eatWhiteDefault = true; 25738ba21522SJames Collins // reference to less needed for vPrefix, mPrefix, and parentSelector 25748ba21522SJames Collins $this->lessc = $lessc; 25758ba21522SJames Collins 25768ba21522SJames Collins $this->sourceName = $sourceName; // name used for error messages 25778ba21522SJames Collins 25788ba21522SJames Collins $this->writeComments = false; 25798ba21522SJames Collins 25808ba21522SJames Collins if (!self::$operatorString) { 25818ba21522SJames Collins self::$operatorString = 25828ba21522SJames Collins '('.implode('|', array_map(array('lessc', 'preg_quote'), 25838ba21522SJames Collins array_keys(self::$precedence))).')'; 25848ba21522SJames Collins 25858ba21522SJames Collins $commentSingle = lessc::preg_quote(self::$commentSingle); 25868ba21522SJames Collins $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft); 25878ba21522SJames Collins $commentMultiRight = lessc::preg_quote(self::$commentMultiRight); 25888ba21522SJames Collins 25898ba21522SJames Collins self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; 25908ba21522SJames Collins self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; 25918ba21522SJames Collins } 25928ba21522SJames Collins } 25938ba21522SJames Collins 25948ba21522SJames Collins public function parse($buffer) { 25958ba21522SJames Collins $this->count = 0; 25968ba21522SJames Collins $this->line = 1; 25978ba21522SJames Collins 25988ba21522SJames Collins $this->env = null; // block stack 25998ba21522SJames Collins $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); 26008ba21522SJames Collins $this->pushSpecialBlock("root"); 26018ba21522SJames Collins $this->eatWhiteDefault = true; 26028ba21522SJames Collins $this->seenComments = array(); 26038ba21522SJames Collins 26048ba21522SJames Collins // trim whitespace on head 26058ba21522SJames Collins // if (preg_match('/^\s+/', $this->buffer, $m)) { 26068ba21522SJames Collins // $this->line += substr_count($m[0], "\n"); 26078ba21522SJames Collins // $this->buffer = ltrim($this->buffer); 26088ba21522SJames Collins // } 26098ba21522SJames Collins $this->whitespace(); 26108ba21522SJames Collins 26118ba21522SJames Collins // parse the entire file 26128ba21522SJames Collins while (false !== $this->parseChunk()); 26138ba21522SJames Collins 26148ba21522SJames Collins if ($this->count != strlen($this->buffer)) 26158ba21522SJames Collins $this->throwError(); 26168ba21522SJames Collins 26178ba21522SJames Collins // TODO report where the block was opened 26188ba21522SJames Collins if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) ) 26198ba21522SJames Collins throw new \Exception('parse error: unclosed block'); 26208ba21522SJames Collins 26218ba21522SJames Collins return $this->env; 26228ba21522SJames Collins } 26238ba21522SJames Collins 26248ba21522SJames Collins /** 26258ba21522SJames Collins * Parse a single chunk off the head of the buffer and append it to the 26268ba21522SJames Collins * current parse environment. 26278ba21522SJames Collins * Returns false when the buffer is empty, or when there is an error. 26288ba21522SJames Collins * 26298ba21522SJames Collins * This function is called repeatedly until the entire document is 26308ba21522SJames Collins * parsed. 26318ba21522SJames Collins * 26328ba21522SJames Collins * This parser is most similar to a recursive descent parser. Single 26338ba21522SJames Collins * functions represent discrete grammatical rules for the language, and 26348ba21522SJames Collins * they are able to capture the text that represents those rules. 26358ba21522SJames Collins * 26368ba21522SJames Collins * Consider the function lessc::keyword(). (all parse functions are 26378ba21522SJames Collins * structured the same) 26388ba21522SJames Collins * 26398ba21522SJames Collins * The function takes a single reference argument. When calling the 26408ba21522SJames Collins * function it will attempt to match a keyword on the head of the buffer. 26418ba21522SJames Collins * If it is successful, it will place the keyword in the referenced 26428ba21522SJames Collins * argument, advance the position in the buffer, and return true. If it 26438ba21522SJames Collins * fails then it won't advance the buffer and it will return false. 26448ba21522SJames Collins * 26458ba21522SJames Collins * All of these parse functions are powered by lessc::match(), which behaves 26468ba21522SJames Collins * the same way, but takes a literal regular expression. Sometimes it is 26478ba21522SJames Collins * more convenient to use match instead of creating a new function. 26488ba21522SJames Collins * 26498ba21522SJames Collins * Because of the format of the functions, to parse an entire string of 26508ba21522SJames Collins * grammatical rules, you can chain them together using &&. 26518ba21522SJames Collins * 26528ba21522SJames Collins * But, if some of the rules in the chain succeed before one fails, then 26538ba21522SJames Collins * the buffer position will be left at an invalid state. In order to 26548ba21522SJames Collins * avoid this, lessc::seek() is used to remember and set buffer positions. 26558ba21522SJames Collins * 26568ba21522SJames Collins * Before parsing a chain, use $s = $this->seek() to remember the current 26578ba21522SJames Collins * position into $s. Then if a chain fails, use $this->seek($s) to 26588ba21522SJames Collins * go back where we started. 26598ba21522SJames Collins */ 26608ba21522SJames Collins protected function parseChunk() { 26618ba21522SJames Collins if (empty($this->buffer)) return false; 26628ba21522SJames Collins $s = $this->seek(); 26638ba21522SJames Collins 26648ba21522SJames Collins if ($this->whitespace()) { 26658ba21522SJames Collins return true; 26668ba21522SJames Collins } 26678ba21522SJames Collins 26688ba21522SJames Collins // setting a property 26698ba21522SJames Collins if ($this->keyword($key) && $this->assign() && 26708ba21522SJames Collins $this->propertyValue($value, $key) && $this->end()) 26718ba21522SJames Collins { 26728ba21522SJames Collins $this->append(array('assign', $key, $value), $s); 26738ba21522SJames Collins return true; 26748ba21522SJames Collins } else { 26758ba21522SJames Collins $this->seek($s); 26768ba21522SJames Collins } 26778ba21522SJames Collins 26788ba21522SJames Collins 26798ba21522SJames Collins // look for special css blocks 26808ba21522SJames Collins if ($this->literal('@', false)) { 26818ba21522SJames Collins $this->count--; 26828ba21522SJames Collins 26838ba21522SJames Collins // media 26848ba21522SJames Collins if ($this->literal('@media')) { 26858ba21522SJames Collins if (($this->mediaQueryList($mediaQueries) || true) 26868ba21522SJames Collins && $this->literal('{')) 26878ba21522SJames Collins { 26888ba21522SJames Collins $media = $this->pushSpecialBlock("media"); 26898ba21522SJames Collins $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; 26908ba21522SJames Collins return true; 26918ba21522SJames Collins } else { 26928ba21522SJames Collins $this->seek($s); 26938ba21522SJames Collins return false; 26948ba21522SJames Collins } 26958ba21522SJames Collins } 26968ba21522SJames Collins 26978ba21522SJames Collins if ($this->literal("@", false) && $this->keyword($dirName)) { 26988ba21522SJames Collins if ($this->isDirective($dirName, $this->blockDirectives)) { 26998ba21522SJames Collins if (($this->openString("{", $dirValue, null, array(";")) || true) && 27008ba21522SJames Collins $this->literal("{")) 27018ba21522SJames Collins { 27028ba21522SJames Collins $dir = $this->pushSpecialBlock("directive"); 27038ba21522SJames Collins $dir->name = $dirName; 27048ba21522SJames Collins if (isset($dirValue)) $dir->value = $dirValue; 27058ba21522SJames Collins return true; 27068ba21522SJames Collins } 27078ba21522SJames Collins } elseif ($this->isDirective($dirName, $this->lineDirectives)) { 27088ba21522SJames Collins if ($this->propertyValue($dirValue) && $this->end()) { 27098ba21522SJames Collins $this->append(array("directive", $dirName, $dirValue)); 27108ba21522SJames Collins return true; 27118ba21522SJames Collins } 27128ba21522SJames Collins } elseif ($this->literal(":", true)) { 27138ba21522SJames Collins //Ruleset Definition 27148ba21522SJames Collins if (($this->openString("{", $dirValue, null, array(";")) || true) && 27158ba21522SJames Collins $this->literal("{")) 27168ba21522SJames Collins { 27178ba21522SJames Collins $dir = $this->pushBlock($this->fixTags(array("@".$dirName))); 27188ba21522SJames Collins $dir->name = $dirName; 27198ba21522SJames Collins if (isset($dirValue)) $dir->value = $dirValue; 27208ba21522SJames Collins return true; 27218ba21522SJames Collins } 27228ba21522SJames Collins } 27238ba21522SJames Collins } 27248ba21522SJames Collins 27258ba21522SJames Collins $this->seek($s); 27268ba21522SJames Collins } 27278ba21522SJames Collins 27288ba21522SJames Collins // setting a variable 27298ba21522SJames Collins if ($this->variable($var) && $this->assign() && 27308ba21522SJames Collins $this->propertyValue($value) && $this->end()) 27318ba21522SJames Collins { 27328ba21522SJames Collins $this->append(array('assign', $var, $value), $s); 27338ba21522SJames Collins return true; 27348ba21522SJames Collins } else { 27358ba21522SJames Collins $this->seek($s); 27368ba21522SJames Collins } 27378ba21522SJames Collins 27388ba21522SJames Collins if ($this->import($importValue)) { 27398ba21522SJames Collins $this->append($importValue, $s); 27408ba21522SJames Collins return true; 27418ba21522SJames Collins } 27428ba21522SJames Collins 27438ba21522SJames Collins // opening parametric mixin 27448ba21522SJames Collins if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && 27458ba21522SJames Collins ($this->guards($guards) || true) && 27468ba21522SJames Collins $this->literal('{')) 27478ba21522SJames Collins { 27488ba21522SJames Collins $block = $this->pushBlock($this->fixTags(array($tag))); 27498ba21522SJames Collins $block->args = $args; 27508ba21522SJames Collins $block->isVararg = $isVararg; 27518ba21522SJames Collins if (!empty($guards)) $block->guards = $guards; 27528ba21522SJames Collins return true; 27538ba21522SJames Collins } else { 27548ba21522SJames Collins $this->seek($s); 27558ba21522SJames Collins } 27568ba21522SJames Collins 27578ba21522SJames Collins // opening a simple block 27588ba21522SJames Collins if ($this->tags($tags) && $this->literal('{', false)) { 27598ba21522SJames Collins $tags = $this->fixTags($tags); 27608ba21522SJames Collins $this->pushBlock($tags); 27618ba21522SJames Collins return true; 27628ba21522SJames Collins } else { 27638ba21522SJames Collins $this->seek($s); 27648ba21522SJames Collins } 27658ba21522SJames Collins 27668ba21522SJames Collins // closing a block 27678ba21522SJames Collins if ($this->literal('}', false)) { 27688ba21522SJames Collins try { 27698ba21522SJames Collins $block = $this->pop(); 27708ba21522SJames Collins } catch (\Exception $e) { 27718ba21522SJames Collins $this->seek($s); 27728ba21522SJames Collins $this->throwError($e->getMessage()); 27738ba21522SJames Collins } 27748ba21522SJames Collins 27758ba21522SJames Collins $hidden = false; 27768ba21522SJames Collins if (is_null($block->type)) { 27778ba21522SJames Collins $hidden = true; 27788ba21522SJames Collins if (!isset($block->args)) { 27798ba21522SJames Collins foreach ($block->tags as $tag) { 27808ba21522SJames Collins if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) { 27818ba21522SJames Collins $hidden = false; 27828ba21522SJames Collins break; 27838ba21522SJames Collins } 27848ba21522SJames Collins } 27858ba21522SJames Collins } 27868ba21522SJames Collins 27878ba21522SJames Collins foreach ($block->tags as $tag) { 27888ba21522SJames Collins if (is_string($tag)) { 27898ba21522SJames Collins $this->env->children[$tag][] = $block; 27908ba21522SJames Collins } 27918ba21522SJames Collins } 27928ba21522SJames Collins } 27938ba21522SJames Collins 27948ba21522SJames Collins if (!$hidden) { 27958ba21522SJames Collins $this->append(array('block', $block), $s); 27968ba21522SJames Collins } 27978ba21522SJames Collins 27988ba21522SJames Collins // this is done here so comments aren't bundled into he block that 27998ba21522SJames Collins // was just closed 28008ba21522SJames Collins $this->whitespace(); 28018ba21522SJames Collins return true; 28028ba21522SJames Collins } 28038ba21522SJames Collins 28048ba21522SJames Collins // mixin 28058ba21522SJames Collins if ($this->mixinTags($tags) && 28068ba21522SJames Collins ($this->argumentDef($argv, $isVararg) || true) && 28078ba21522SJames Collins ($this->keyword($suffix) || true) && $this->end()) 28088ba21522SJames Collins { 28098ba21522SJames Collins $tags = $this->fixTags($tags); 28108ba21522SJames Collins $this->append(array('mixin', $tags, $argv, $suffix), $s); 28118ba21522SJames Collins return true; 28128ba21522SJames Collins } else { 28138ba21522SJames Collins $this->seek($s); 28148ba21522SJames Collins } 28158ba21522SJames Collins 28168ba21522SJames Collins // spare ; 28178ba21522SJames Collins if ($this->literal(';')) return true; 28188ba21522SJames Collins 28198ba21522SJames Collins return false; // got nothing, throw error 28208ba21522SJames Collins } 28218ba21522SJames Collins 28228ba21522SJames Collins protected function isDirective($dirname, $directives) { 28238ba21522SJames Collins // TODO: cache pattern in parser 28248ba21522SJames Collins $pattern = implode("|", 28258ba21522SJames Collins array_map(array("lessc", "preg_quote"), $directives)); 28268ba21522SJames Collins $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; 28278ba21522SJames Collins 28288ba21522SJames Collins return preg_match($pattern, $dirname); 28298ba21522SJames Collins } 28308ba21522SJames Collins 28318ba21522SJames Collins protected function fixTags($tags) { 28328ba21522SJames Collins // move @ tags out of variable namespace 28338ba21522SJames Collins foreach ($tags as &$tag) { 28348ba21522SJames Collins if ($tag[0] == $this->lessc->vPrefix) 28358ba21522SJames Collins $tag[0] = $this->lessc->mPrefix; 28368ba21522SJames Collins } 28378ba21522SJames Collins return $tags; 28388ba21522SJames Collins } 28398ba21522SJames Collins 28408ba21522SJames Collins // a list of expressions 28418ba21522SJames Collins protected function expressionList(&$exps) { 28428ba21522SJames Collins $values = array(); 28438ba21522SJames Collins 28448ba21522SJames Collins while ($this->expression($exp)) { 28458ba21522SJames Collins $values[] = $exp; 28468ba21522SJames Collins } 28478ba21522SJames Collins 28488ba21522SJames Collins if (count($values) == 0) return false; 28498ba21522SJames Collins 28508ba21522SJames Collins $exps = lessc::compressList($values, ' '); 28518ba21522SJames Collins return true; 28528ba21522SJames Collins } 28538ba21522SJames Collins 28548ba21522SJames Collins /** 28558ba21522SJames Collins * Attempt to consume an expression. 28568ba21522SJames Collins * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code 28578ba21522SJames Collins */ 28588ba21522SJames Collins protected function expression(&$out) { 28598ba21522SJames Collins if ($this->value($lhs)) { 28608ba21522SJames Collins $out = $this->expHelper($lhs, 0); 28618ba21522SJames Collins 28628ba21522SJames Collins // look for / shorthand 28638ba21522SJames Collins if (!empty($this->env->supressedDivision)) { 28648ba21522SJames Collins unset($this->env->supressedDivision); 28658ba21522SJames Collins $s = $this->seek(); 28668ba21522SJames Collins if ($this->literal("/") && $this->value($rhs)) { 28678ba21522SJames Collins $out = array("list", "", 28688ba21522SJames Collins array($out, array("keyword", "/"), $rhs)); 28698ba21522SJames Collins } else { 28708ba21522SJames Collins $this->seek($s); 28718ba21522SJames Collins } 28728ba21522SJames Collins } 28738ba21522SJames Collins 28748ba21522SJames Collins return true; 28758ba21522SJames Collins } 28768ba21522SJames Collins return false; 28778ba21522SJames Collins } 28788ba21522SJames Collins 28798ba21522SJames Collins /** 28808ba21522SJames Collins * recursively parse infix equation with $lhs at precedence $minP 28818ba21522SJames Collins */ 28828ba21522SJames Collins protected function expHelper($lhs, $minP) { 28838ba21522SJames Collins $this->inExp = true; 28848ba21522SJames Collins $ss = $this->seek(); 28858ba21522SJames Collins 28868ba21522SJames Collins while (true) { 28878ba21522SJames Collins $whiteBefore = isset($this->buffer[$this->count - 1]) && 28888ba21522SJames Collins ctype_space($this->buffer[$this->count - 1]); 28898ba21522SJames Collins 28908ba21522SJames Collins // If there is whitespace before the operator, then we require 28918ba21522SJames Collins // whitespace after the operator for it to be an expression 28928ba21522SJames Collins $needWhite = $whiteBefore && !$this->inParens; 28938ba21522SJames Collins 28948ba21522SJames Collins if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { 28958ba21522SJames Collins if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { 28968ba21522SJames Collins foreach (self::$supressDivisionProps as $pattern) { 28978ba21522SJames Collins if (preg_match($pattern, $this->env->currentProperty)) { 28988ba21522SJames Collins $this->env->supressedDivision = true; 28998ba21522SJames Collins break 2; 29008ba21522SJames Collins } 29018ba21522SJames Collins } 29028ba21522SJames Collins } 29038ba21522SJames Collins 29048ba21522SJames Collins 29058ba21522SJames Collins $whiteAfter = isset($this->buffer[$this->count - 1]) && 29068ba21522SJames Collins ctype_space($this->buffer[$this->count - 1]); 29078ba21522SJames Collins 29088ba21522SJames Collins if (!$this->value($rhs)) break; 29098ba21522SJames Collins 29108ba21522SJames Collins // peek for next operator to see what to do with rhs 29118ba21522SJames Collins if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { 29128ba21522SJames Collins $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); 29138ba21522SJames Collins } 29148ba21522SJames Collins 29158ba21522SJames Collins $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); 29168ba21522SJames Collins $ss = $this->seek(); 29178ba21522SJames Collins 29188ba21522SJames Collins continue; 29198ba21522SJames Collins } 29208ba21522SJames Collins 29218ba21522SJames Collins break; 29228ba21522SJames Collins } 29238ba21522SJames Collins 29248ba21522SJames Collins $this->seek($ss); 29258ba21522SJames Collins 29268ba21522SJames Collins return $lhs; 29278ba21522SJames Collins } 29288ba21522SJames Collins 29298ba21522SJames Collins // consume a list of values for a property 29308ba21522SJames Collins public function propertyValue(&$value, $keyName = null) { 29318ba21522SJames Collins $values = array(); 29328ba21522SJames Collins 29338ba21522SJames Collins if ($keyName !== null) $this->env->currentProperty = $keyName; 29348ba21522SJames Collins 29358ba21522SJames Collins $s = null; 29368ba21522SJames Collins while ($this->expressionList($v)) { 29378ba21522SJames Collins $values[] = $v; 29388ba21522SJames Collins $s = $this->seek(); 29398ba21522SJames Collins if (!$this->literal(',')) break; 29408ba21522SJames Collins } 29418ba21522SJames Collins 29428ba21522SJames Collins if ($s) $this->seek($s); 29438ba21522SJames Collins 29448ba21522SJames Collins if ($keyName !== null) unset($this->env->currentProperty); 29458ba21522SJames Collins 29468ba21522SJames Collins if (count($values) == 0) return false; 29478ba21522SJames Collins 29488ba21522SJames Collins $value = lessc::compressList($values, ', '); 29498ba21522SJames Collins return true; 29508ba21522SJames Collins } 29518ba21522SJames Collins 29528ba21522SJames Collins protected function parenValue(&$out) { 29538ba21522SJames Collins $s = $this->seek(); 29548ba21522SJames Collins 29558ba21522SJames Collins // speed shortcut 29568ba21522SJames Collins if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { 29578ba21522SJames Collins return false; 29588ba21522SJames Collins } 29598ba21522SJames Collins 29608ba21522SJames Collins $inParens = $this->inParens; 29618ba21522SJames Collins if ($this->literal("(") && 29628ba21522SJames Collins ($this->inParens = true) && $this->expression($exp) && 29638ba21522SJames Collins $this->literal(")")) 29648ba21522SJames Collins { 29658ba21522SJames Collins $out = $exp; 29668ba21522SJames Collins $this->inParens = $inParens; 29678ba21522SJames Collins return true; 29688ba21522SJames Collins } else { 29698ba21522SJames Collins $this->inParens = $inParens; 29708ba21522SJames Collins $this->seek($s); 29718ba21522SJames Collins } 29728ba21522SJames Collins 29738ba21522SJames Collins return false; 29748ba21522SJames Collins } 29758ba21522SJames Collins 29768ba21522SJames Collins // a single value 29778ba21522SJames Collins protected function value(&$value) { 29788ba21522SJames Collins $s = $this->seek(); 29798ba21522SJames Collins 29808ba21522SJames Collins // speed shortcut 29818ba21522SJames Collins if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { 29828ba21522SJames Collins // negation 29838ba21522SJames Collins if ($this->literal("-", false) && 29848ba21522SJames Collins (($this->variable($inner) && $inner = array("variable", $inner)) || 29858ba21522SJames Collins $this->unit($inner) || 29868ba21522SJames Collins $this->parenValue($inner))) 29878ba21522SJames Collins { 29888ba21522SJames Collins $value = array("unary", "-", $inner); 29898ba21522SJames Collins return true; 29908ba21522SJames Collins } else { 29918ba21522SJames Collins $this->seek($s); 29928ba21522SJames Collins } 29938ba21522SJames Collins } 29948ba21522SJames Collins 29958ba21522SJames Collins if ($this->parenValue($value)) return true; 29968ba21522SJames Collins if ($this->unit($value)) return true; 29978ba21522SJames Collins if ($this->color($value)) return true; 29988ba21522SJames Collins if ($this->func($value)) return true; 29998ba21522SJames Collins if ($this->stringValue($value)) return true; 30008ba21522SJames Collins 30018ba21522SJames Collins if ($this->keyword($word)) { 30028ba21522SJames Collins $value = array('keyword', $word); 30038ba21522SJames Collins return true; 30048ba21522SJames Collins } 30058ba21522SJames Collins 30068ba21522SJames Collins // try a variable 30078ba21522SJames Collins if ($this->variable($var)) { 30088ba21522SJames Collins $value = array('variable', $var); 30098ba21522SJames Collins return true; 30108ba21522SJames Collins } 30118ba21522SJames Collins 30128ba21522SJames Collins // unquote string (should this work on any type? 30138ba21522SJames Collins if ($this->literal("~") && $this->stringValue($str)) { 30148ba21522SJames Collins $value = array("escape", $str); 30158ba21522SJames Collins return true; 30168ba21522SJames Collins } else { 30178ba21522SJames Collins $this->seek($s); 30188ba21522SJames Collins } 30198ba21522SJames Collins 30208ba21522SJames Collins // css hack: \0 30218ba21522SJames Collins if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { 30228ba21522SJames Collins $value = array('keyword', '\\'.$m[1]); 30238ba21522SJames Collins return true; 30248ba21522SJames Collins } else { 30258ba21522SJames Collins $this->seek($s); 30268ba21522SJames Collins } 30278ba21522SJames Collins 30288ba21522SJames Collins return false; 30298ba21522SJames Collins } 30308ba21522SJames Collins 30318ba21522SJames Collins // an import statement 30328ba21522SJames Collins protected function import(&$out) { 30338ba21522SJames Collins if (!$this->literal('@import')) return false; 30348ba21522SJames Collins 30358ba21522SJames Collins // @import "something.css" media; 30368ba21522SJames Collins // @import url("something.css") media; 30378ba21522SJames Collins // @import url(something.css) media; 30388ba21522SJames Collins 30398ba21522SJames Collins if ($this->propertyValue($value)) { 30408ba21522SJames Collins $out = array("import", $value); 30418ba21522SJames Collins return true; 30428ba21522SJames Collins } 30438ba21522SJames Collins } 30448ba21522SJames Collins 30458ba21522SJames Collins protected function mediaQueryList(&$out) { 30468ba21522SJames Collins if ($this->genericList($list, "mediaQuery", ",", false)) { 30478ba21522SJames Collins $out = $list[2]; 30488ba21522SJames Collins return true; 30498ba21522SJames Collins } 30508ba21522SJames Collins return false; 30518ba21522SJames Collins } 30528ba21522SJames Collins 30538ba21522SJames Collins protected function mediaQuery(&$out) { 30548ba21522SJames Collins $s = $this->seek(); 30558ba21522SJames Collins 30568ba21522SJames Collins $expressions = null; 30578ba21522SJames Collins $parts = array(); 30588ba21522SJames Collins 30598ba21522SJames Collins if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { 30608ba21522SJames Collins $prop = array("mediaType"); 30618ba21522SJames Collins if (isset($only)) $prop[] = "only"; 30628ba21522SJames Collins if (isset($not)) $prop[] = "not"; 30638ba21522SJames Collins $prop[] = $mediaType; 30648ba21522SJames Collins $parts[] = $prop; 30658ba21522SJames Collins } else { 30668ba21522SJames Collins $this->seek($s); 30678ba21522SJames Collins } 30688ba21522SJames Collins 30698ba21522SJames Collins 30708ba21522SJames Collins if (!empty($mediaType) && !$this->literal("and")) { 30718ba21522SJames Collins // ~ 30728ba21522SJames Collins } else { 30738ba21522SJames Collins $this->genericList($expressions, "mediaExpression", "and", false); 30748ba21522SJames Collins if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); 30758ba21522SJames Collins } 30768ba21522SJames Collins 30778ba21522SJames Collins if (count($parts) == 0) { 30788ba21522SJames Collins $this->seek($s); 30798ba21522SJames Collins return false; 30808ba21522SJames Collins } 30818ba21522SJames Collins 30828ba21522SJames Collins $out = $parts; 30838ba21522SJames Collins return true; 30848ba21522SJames Collins } 30858ba21522SJames Collins 30868ba21522SJames Collins protected function mediaExpression(&$out) { 30878ba21522SJames Collins $s = $this->seek(); 30888ba21522SJames Collins $value = null; 30898ba21522SJames Collins if ($this->literal("(") && 30908ba21522SJames Collins $this->keyword($feature) && 30918ba21522SJames Collins ($this->literal(":") && $this->expression($value) || true) && 30928ba21522SJames Collins $this->literal(")")) 30938ba21522SJames Collins { 30948ba21522SJames Collins $out = array("mediaExp", $feature); 30958ba21522SJames Collins if ($value) $out[] = $value; 30968ba21522SJames Collins return true; 30978ba21522SJames Collins } elseif ($this->variable($variable)) { 30988ba21522SJames Collins $out = array('variable', $variable); 30998ba21522SJames Collins return true; 31008ba21522SJames Collins } 31018ba21522SJames Collins 31028ba21522SJames Collins $this->seek($s); 31038ba21522SJames Collins return false; 31048ba21522SJames Collins } 31058ba21522SJames Collins 31068ba21522SJames Collins // an unbounded string stopped by $end 31078ba21522SJames Collins protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) { 31088ba21522SJames Collins $oldWhite = $this->eatWhiteDefault; 31098ba21522SJames Collins $this->eatWhiteDefault = false; 31108ba21522SJames Collins 31118ba21522SJames Collins $stop = array("'", '"', "@{", $end); 31128ba21522SJames Collins $stop = array_map(array("lessc", "preg_quote"), $stop); 31138ba21522SJames Collins // $stop[] = self::$commentMulti; 31148ba21522SJames Collins 31158ba21522SJames Collins if (!is_null($rejectStrs)) { 31168ba21522SJames Collins $stop = array_merge($stop, $rejectStrs); 31178ba21522SJames Collins } 31188ba21522SJames Collins 31198ba21522SJames Collins $patt = '(.*?)('.implode("|", $stop).')'; 31208ba21522SJames Collins 31218ba21522SJames Collins $nestingLevel = 0; 31228ba21522SJames Collins 31238ba21522SJames Collins $content = array(); 31248ba21522SJames Collins while ($this->match($patt, $m, false)) { 31258ba21522SJames Collins if (!empty($m[1])) { 31268ba21522SJames Collins $content[] = $m[1]; 31278ba21522SJames Collins if ($nestingOpen) { 31288ba21522SJames Collins $nestingLevel += substr_count($m[1], $nestingOpen); 31298ba21522SJames Collins } 31308ba21522SJames Collins } 31318ba21522SJames Collins 31328ba21522SJames Collins $tok = $m[2]; 31338ba21522SJames Collins 31348ba21522SJames Collins $this->count-= strlen($tok); 31358ba21522SJames Collins if ($tok == $end) { 31368ba21522SJames Collins if ($nestingLevel == 0) { 31378ba21522SJames Collins break; 31388ba21522SJames Collins } else { 31398ba21522SJames Collins $nestingLevel--; 31408ba21522SJames Collins } 31418ba21522SJames Collins } 31428ba21522SJames Collins 31438ba21522SJames Collins if (($tok == "'" || $tok == '"') && $this->stringValue($str)) { 31448ba21522SJames Collins $content[] = $str; 31458ba21522SJames Collins continue; 31468ba21522SJames Collins } 31478ba21522SJames Collins 31488ba21522SJames Collins if ($tok == "@{" && $this->interpolation($inter)) { 31498ba21522SJames Collins $content[] = $inter; 31508ba21522SJames Collins continue; 31518ba21522SJames Collins } 31528ba21522SJames Collins 31538ba21522SJames Collins if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) { 31548ba21522SJames Collins break; 31558ba21522SJames Collins } 31568ba21522SJames Collins 31578ba21522SJames Collins $content[] = $tok; 31588ba21522SJames Collins $this->count+= strlen($tok); 31598ba21522SJames Collins } 31608ba21522SJames Collins 31618ba21522SJames Collins $this->eatWhiteDefault = $oldWhite; 31628ba21522SJames Collins 31638ba21522SJames Collins if (count($content) == 0) return false; 31648ba21522SJames Collins 31658ba21522SJames Collins // trim the end 31668ba21522SJames Collins if (is_string(end($content))) { 31678ba21522SJames Collins $content[count($content) - 1] = rtrim(end($content)); 31688ba21522SJames Collins } 31698ba21522SJames Collins 31708ba21522SJames Collins $out = array("string", "", $content); 31718ba21522SJames Collins return true; 31728ba21522SJames Collins } 31738ba21522SJames Collins 31748ba21522SJames Collins protected function stringValue(&$out) { 31758ba21522SJames Collins $s = $this->seek(); 31768ba21522SJames Collins if ($this->literal('"', false)) { 31778ba21522SJames Collins $delim = '"'; 31788ba21522SJames Collins } elseif ($this->literal("'", false)) { 31798ba21522SJames Collins $delim = "'"; 31808ba21522SJames Collins } else { 31818ba21522SJames Collins return false; 31828ba21522SJames Collins } 31838ba21522SJames Collins 31848ba21522SJames Collins $content = array(); 31858ba21522SJames Collins 31868ba21522SJames Collins // look for either ending delim , escape, or string interpolation 31878ba21522SJames Collins $patt = '([^\n]*?)(@\{|\\\\|' . 31888ba21522SJames Collins lessc::preg_quote($delim).')'; 31898ba21522SJames Collins 31908ba21522SJames Collins $oldWhite = $this->eatWhiteDefault; 31918ba21522SJames Collins $this->eatWhiteDefault = false; 31928ba21522SJames Collins 31938ba21522SJames Collins while ($this->match($patt, $m, false)) { 31948ba21522SJames Collins $content[] = $m[1]; 31958ba21522SJames Collins if ($m[2] == "@{") { 31968ba21522SJames Collins $this->count -= strlen($m[2]); 31978ba21522SJames Collins if ($this->interpolation($inter, false)) { 31988ba21522SJames Collins $content[] = $inter; 31998ba21522SJames Collins } else { 32008ba21522SJames Collins $this->count += strlen($m[2]); 32018ba21522SJames Collins $content[] = "@{"; // ignore it 32028ba21522SJames Collins } 32038ba21522SJames Collins } elseif ($m[2] == '\\') { 32048ba21522SJames Collins $content[] = $m[2]; 32058ba21522SJames Collins if ($this->literal($delim, false)) { 32068ba21522SJames Collins $content[] = $delim; 32078ba21522SJames Collins } 32088ba21522SJames Collins } else { 32098ba21522SJames Collins $this->count -= strlen($delim); 32108ba21522SJames Collins break; // delim 32118ba21522SJames Collins } 32128ba21522SJames Collins } 32138ba21522SJames Collins 32148ba21522SJames Collins $this->eatWhiteDefault = $oldWhite; 32158ba21522SJames Collins 32168ba21522SJames Collins if ($this->literal($delim)) { 32178ba21522SJames Collins $out = array("string", $delim, $content); 32188ba21522SJames Collins return true; 32198ba21522SJames Collins } 32208ba21522SJames Collins 32218ba21522SJames Collins $this->seek($s); 32228ba21522SJames Collins return false; 32238ba21522SJames Collins } 32248ba21522SJames Collins 32258ba21522SJames Collins protected function interpolation(&$out) { 32268ba21522SJames Collins $oldWhite = $this->eatWhiteDefault; 32278ba21522SJames Collins $this->eatWhiteDefault = true; 32288ba21522SJames Collins 32298ba21522SJames Collins $s = $this->seek(); 32308ba21522SJames Collins if ($this->literal("@{") && 32318ba21522SJames Collins $this->openString("}", $interp, null, array("'", '"', ";")) && 32328ba21522SJames Collins $this->literal("}", false)) 32338ba21522SJames Collins { 32348ba21522SJames Collins $out = array("interpolate", $interp); 32358ba21522SJames Collins $this->eatWhiteDefault = $oldWhite; 32368ba21522SJames Collins if ($this->eatWhiteDefault) $this->whitespace(); 32378ba21522SJames Collins return true; 32388ba21522SJames Collins } 32398ba21522SJames Collins 32408ba21522SJames Collins $this->eatWhiteDefault = $oldWhite; 32418ba21522SJames Collins $this->seek($s); 32428ba21522SJames Collins return false; 32438ba21522SJames Collins } 32448ba21522SJames Collins 32458ba21522SJames Collins protected function unit(&$unit) { 32468ba21522SJames Collins // speed shortcut 32478ba21522SJames Collins if (isset($this->buffer[$this->count])) { 32488ba21522SJames Collins $char = $this->buffer[$this->count]; 32498ba21522SJames Collins if (!ctype_digit($char) && $char != ".") return false; 32508ba21522SJames Collins } 32518ba21522SJames Collins 32528ba21522SJames Collins if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { 32538ba21522SJames Collins $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); 32548ba21522SJames Collins return true; 32558ba21522SJames Collins } 32568ba21522SJames Collins return false; 32578ba21522SJames Collins } 32588ba21522SJames Collins 32598ba21522SJames Collins // a # color 32608ba21522SJames Collins protected function color(&$out) { 32618ba21522SJames Collins if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { 32628ba21522SJames Collins if (strlen($m[1]) > 7) { 32638ba21522SJames Collins $out = array("string", "", array($m[1])); 32648ba21522SJames Collins } else { 32658ba21522SJames Collins $out = array("raw_color", $m[1]); 32668ba21522SJames Collins } 32678ba21522SJames Collins return true; 32688ba21522SJames Collins } 32698ba21522SJames Collins 32708ba21522SJames Collins return false; 32718ba21522SJames Collins } 32728ba21522SJames Collins 32738ba21522SJames Collins // consume an argument definition list surrounded by () 32748ba21522SJames Collins // each argument is a variable name with optional value 32758ba21522SJames Collins // or at the end a ... or a variable named followed by ... 32768ba21522SJames Collins // arguments are separated by , unless a ; is in the list, then ; is the 32778ba21522SJames Collins // delimiter. 32788ba21522SJames Collins protected function argumentDef(&$args, &$isVararg) { 32798ba21522SJames Collins $s = $this->seek(); 32808ba21522SJames Collins if (!$this->literal('(')) return false; 32818ba21522SJames Collins 32828ba21522SJames Collins $values = array(); 32838ba21522SJames Collins $delim = ","; 32848ba21522SJames Collins $method = "expressionList"; 32858ba21522SJames Collins 32868ba21522SJames Collins $isVararg = false; 32878ba21522SJames Collins while (true) { 32888ba21522SJames Collins if ($this->literal("...")) { 32898ba21522SJames Collins $isVararg = true; 32908ba21522SJames Collins break; 32918ba21522SJames Collins } 32928ba21522SJames Collins 32938ba21522SJames Collins if ($this->$method($value)) { 32948ba21522SJames Collins if ($value[0] == "variable") { 32958ba21522SJames Collins $arg = array("arg", $value[1]); 32968ba21522SJames Collins $ss = $this->seek(); 32978ba21522SJames Collins 32988ba21522SJames Collins if ($this->assign() && $this->$method($rhs)) { 32998ba21522SJames Collins $arg[] = $rhs; 33008ba21522SJames Collins } else { 33018ba21522SJames Collins $this->seek($ss); 33028ba21522SJames Collins if ($this->literal("...")) { 33038ba21522SJames Collins $arg[0] = "rest"; 33048ba21522SJames Collins $isVararg = true; 33058ba21522SJames Collins } 33068ba21522SJames Collins } 33078ba21522SJames Collins 33088ba21522SJames Collins $values[] = $arg; 33098ba21522SJames Collins if ($isVararg) break; 33108ba21522SJames Collins continue; 33118ba21522SJames Collins } else { 33128ba21522SJames Collins $values[] = array("lit", $value); 33138ba21522SJames Collins } 33148ba21522SJames Collins } 33158ba21522SJames Collins 33168ba21522SJames Collins 33178ba21522SJames Collins if (!$this->literal($delim)) { 33188ba21522SJames Collins if ($delim == "," && $this->literal(";")) { 33198ba21522SJames Collins // found new delim, convert existing args 33208ba21522SJames Collins $delim = ";"; 33218ba21522SJames Collins $method = "propertyValue"; 33228ba21522SJames Collins 33238ba21522SJames Collins // transform arg list 33248ba21522SJames Collins if (isset($values[1])) { // 2 items 33258ba21522SJames Collins $newList = array(); 33268ba21522SJames Collins foreach ($values as $i => $arg) { 33278ba21522SJames Collins switch($arg[0]) { 33288ba21522SJames Collins case "arg": 33298ba21522SJames Collins if ($i) { 33308ba21522SJames Collins $this->throwError("Cannot mix ; and , as delimiter types"); 33318ba21522SJames Collins } 33328ba21522SJames Collins $newList[] = $arg[2]; 33338ba21522SJames Collins break; 33348ba21522SJames Collins case "lit": 33358ba21522SJames Collins $newList[] = $arg[1]; 33368ba21522SJames Collins break; 33378ba21522SJames Collins case "rest": 33388ba21522SJames Collins $this->throwError("Unexpected rest before semicolon"); 33398ba21522SJames Collins } 33408ba21522SJames Collins } 33418ba21522SJames Collins 33428ba21522SJames Collins $newList = array("list", ", ", $newList); 33438ba21522SJames Collins 33448ba21522SJames Collins switch ($values[0][0]) { 33458ba21522SJames Collins case "arg": 33468ba21522SJames Collins $newArg = array("arg", $values[0][1], $newList); 33478ba21522SJames Collins break; 33488ba21522SJames Collins case "lit": 33498ba21522SJames Collins $newArg = array("lit", $newList); 33508ba21522SJames Collins break; 33518ba21522SJames Collins } 33528ba21522SJames Collins 33538ba21522SJames Collins } elseif ($values) { // 1 item 33548ba21522SJames Collins $newArg = $values[0]; 33558ba21522SJames Collins } 33568ba21522SJames Collins 33578ba21522SJames Collins if ($newArg) { 33588ba21522SJames Collins $values = array($newArg); 33598ba21522SJames Collins } 33608ba21522SJames Collins } else { 33618ba21522SJames Collins break; 33628ba21522SJames Collins } 33638ba21522SJames Collins } 33648ba21522SJames Collins } 33658ba21522SJames Collins 33668ba21522SJames Collins if (!$this->literal(')')) { 33678ba21522SJames Collins $this->seek($s); 33688ba21522SJames Collins return false; 33698ba21522SJames Collins } 33708ba21522SJames Collins 33718ba21522SJames Collins $args = $values; 33728ba21522SJames Collins 33738ba21522SJames Collins return true; 33748ba21522SJames Collins } 33758ba21522SJames Collins 33768ba21522SJames Collins // consume a list of tags 33778ba21522SJames Collins // this accepts a hanging delimiter 33788ba21522SJames Collins protected function tags(&$tags, $simple = false, $delim = ',') { 33798ba21522SJames Collins $tags = array(); 33808ba21522SJames Collins while ($this->tag($tt, $simple)) { 33818ba21522SJames Collins $tags[] = $tt; 33828ba21522SJames Collins if (!$this->literal($delim)) break; 33838ba21522SJames Collins } 33848ba21522SJames Collins if (count($tags) == 0) return false; 33858ba21522SJames Collins 33868ba21522SJames Collins return true; 33878ba21522SJames Collins } 33888ba21522SJames Collins 33898ba21522SJames Collins // list of tags of specifying mixin path 33908ba21522SJames Collins // optionally separated by > (lazy, accepts extra >) 33918ba21522SJames Collins protected function mixinTags(&$tags) { 33928ba21522SJames Collins $tags = array(); 33938ba21522SJames Collins while ($this->tag($tt, true)) { 33948ba21522SJames Collins $tags[] = $tt; 33958ba21522SJames Collins $this->literal(">"); 33968ba21522SJames Collins } 33978ba21522SJames Collins 33988ba21522SJames Collins if (count($tags) == 0) return false; 33998ba21522SJames Collins 34008ba21522SJames Collins return true; 34018ba21522SJames Collins } 34028ba21522SJames Collins 34038ba21522SJames Collins // a bracketed value (contained within in a tag definition) 34048ba21522SJames Collins protected function tagBracket(&$parts, &$hasExpression) { 34058ba21522SJames Collins // speed shortcut 34068ba21522SJames Collins if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { 34078ba21522SJames Collins return false; 34088ba21522SJames Collins } 34098ba21522SJames Collins 34108ba21522SJames Collins $s = $this->seek(); 34118ba21522SJames Collins 34128ba21522SJames Collins $hasInterpolation = false; 34138ba21522SJames Collins 34148ba21522SJames Collins if ($this->literal("[", false)) { 34158ba21522SJames Collins $attrParts = array("["); 34168ba21522SJames Collins // keyword, string, operator 34178ba21522SJames Collins while (true) { 34188ba21522SJames Collins if ($this->literal("]", false)) { 34198ba21522SJames Collins $this->count--; 34208ba21522SJames Collins break; // get out early 34218ba21522SJames Collins } 34228ba21522SJames Collins 34238ba21522SJames Collins if ($this->match('\s+', $m)) { 34248ba21522SJames Collins $attrParts[] = " "; 34258ba21522SJames Collins continue; 34268ba21522SJames Collins } 34278ba21522SJames Collins if ($this->stringValue($str)) { 34288ba21522SJames Collins // escape parent selector, (yuck) 34298ba21522SJames Collins foreach ($str[2] as &$chunk) { 34308ba21522SJames Collins if (is_string($chunk)) { 34318ba21522SJames Collins $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk); 34328ba21522SJames Collins } 34338ba21522SJames Collins } 34348ba21522SJames Collins 34358ba21522SJames Collins $attrParts[] = $str; 34368ba21522SJames Collins $hasInterpolation = true; 34378ba21522SJames Collins continue; 34388ba21522SJames Collins } 34398ba21522SJames Collins 34408ba21522SJames Collins if ($this->keyword($word)) { 34418ba21522SJames Collins $attrParts[] = $word; 34428ba21522SJames Collins continue; 34438ba21522SJames Collins } 34448ba21522SJames Collins 34458ba21522SJames Collins if ($this->interpolation($inter, false)) { 34468ba21522SJames Collins $attrParts[] = $inter; 34478ba21522SJames Collins $hasInterpolation = true; 34488ba21522SJames Collins continue; 34498ba21522SJames Collins } 34508ba21522SJames Collins 34518ba21522SJames Collins // operator, handles attr namespace too 34528ba21522SJames Collins if ($this->match('[|-~\$\*\^=]+', $m)) { 34538ba21522SJames Collins $attrParts[] = $m[0]; 34548ba21522SJames Collins continue; 34558ba21522SJames Collins } 34568ba21522SJames Collins 34578ba21522SJames Collins break; 34588ba21522SJames Collins } 34598ba21522SJames Collins 34608ba21522SJames Collins if ($this->literal("]", false)) { 34618ba21522SJames Collins $attrParts[] = "]"; 34628ba21522SJames Collins foreach ($attrParts as $part) { 34638ba21522SJames Collins $parts[] = $part; 34648ba21522SJames Collins } 34658ba21522SJames Collins $hasExpression = $hasExpression || $hasInterpolation; 34668ba21522SJames Collins return true; 34678ba21522SJames Collins } 34688ba21522SJames Collins $this->seek($s); 34698ba21522SJames Collins } 34708ba21522SJames Collins 34718ba21522SJames Collins $this->seek($s); 34728ba21522SJames Collins return false; 34738ba21522SJames Collins } 34748ba21522SJames Collins 34758ba21522SJames Collins // a space separated list of selectors 34768ba21522SJames Collins protected function tag(&$tag, $simple = false) { 34778ba21522SJames Collins if ($simple) 34788ba21522SJames Collins $chars = '^@,:;{}\][>\(\) "\''; 34798ba21522SJames Collins else 34808ba21522SJames Collins $chars = '^@,;{}["\''; 34818ba21522SJames Collins 34828ba21522SJames Collins $s = $this->seek(); 34838ba21522SJames Collins 34848ba21522SJames Collins $hasExpression = false; 34858ba21522SJames Collins $parts = array(); 34868ba21522SJames Collins while ($this->tagBracket($parts, $hasExpression)); 34878ba21522SJames Collins 34888ba21522SJames Collins $oldWhite = $this->eatWhiteDefault; 34898ba21522SJames Collins $this->eatWhiteDefault = false; 34908ba21522SJames Collins 34918ba21522SJames Collins while (true) { 34928ba21522SJames Collins if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { 34938ba21522SJames Collins $parts[] = $m[1]; 34948ba21522SJames Collins if ($simple) break; 34958ba21522SJames Collins 34968ba21522SJames Collins while ($this->tagBracket($parts, $hasExpression)); 34978ba21522SJames Collins continue; 34988ba21522SJames Collins } 34998ba21522SJames Collins 35008ba21522SJames Collins if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { 35018ba21522SJames Collins if ($this->interpolation($interp)) { 35028ba21522SJames Collins $hasExpression = true; 35038ba21522SJames Collins $interp[2] = true; // don't unescape 35048ba21522SJames Collins $parts[] = $interp; 35058ba21522SJames Collins continue; 35068ba21522SJames Collins } 35078ba21522SJames Collins 35088ba21522SJames Collins if ($this->literal("@")) { 35098ba21522SJames Collins $parts[] = "@"; 35108ba21522SJames Collins continue; 35118ba21522SJames Collins } 35128ba21522SJames Collins } 35138ba21522SJames Collins 35148ba21522SJames Collins if ($this->unit($unit)) { // for keyframes 35158ba21522SJames Collins $parts[] = $unit[1]; 35168ba21522SJames Collins $parts[] = $unit[2]; 35178ba21522SJames Collins continue; 35188ba21522SJames Collins } 35198ba21522SJames Collins 35208ba21522SJames Collins break; 35218ba21522SJames Collins } 35228ba21522SJames Collins 35238ba21522SJames Collins $this->eatWhiteDefault = $oldWhite; 35248ba21522SJames Collins if (!$parts) { 35258ba21522SJames Collins $this->seek($s); 35268ba21522SJames Collins return false; 35278ba21522SJames Collins } 35288ba21522SJames Collins 35298ba21522SJames Collins if ($hasExpression) { 35308ba21522SJames Collins $tag = array("exp", array("string", "", $parts)); 35318ba21522SJames Collins } else { 35328ba21522SJames Collins $tag = trim(implode($parts)); 35338ba21522SJames Collins } 35348ba21522SJames Collins 35358ba21522SJames Collins $this->whitespace(); 35368ba21522SJames Collins return true; 35378ba21522SJames Collins } 35388ba21522SJames Collins 35398ba21522SJames Collins // a css function 35408ba21522SJames Collins protected function func(&$func) { 35418ba21522SJames Collins $s = $this->seek(); 35428ba21522SJames Collins 35438ba21522SJames Collins if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { 35448ba21522SJames Collins $fname = $m[1]; 35458ba21522SJames Collins 35468ba21522SJames Collins $sPreArgs = $this->seek(); 35478ba21522SJames Collins 35488ba21522SJames Collins $args = array(); 35498ba21522SJames Collins while (true) { 35508ba21522SJames Collins $ss = $this->seek(); 35518ba21522SJames Collins // this ugly nonsense is for ie filter properties 35528ba21522SJames Collins if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { 35538ba21522SJames Collins $args[] = array("string", "", array($name, "=", $value)); 35548ba21522SJames Collins } else { 35558ba21522SJames Collins $this->seek($ss); 35568ba21522SJames Collins if ($this->expressionList($value)) { 35578ba21522SJames Collins $args[] = $value; 35588ba21522SJames Collins } 35598ba21522SJames Collins } 35608ba21522SJames Collins 35618ba21522SJames Collins if (!$this->literal(',')) break; 35628ba21522SJames Collins } 35638ba21522SJames Collins $args = array('list', ',', $args); 35648ba21522SJames Collins 35658ba21522SJames Collins if ($this->literal(')')) { 35668ba21522SJames Collins $func = array('function', $fname, $args); 35678ba21522SJames Collins return true; 35688ba21522SJames Collins } elseif ($fname == 'url') { 35698ba21522SJames Collins // couldn't parse and in url? treat as string 35708ba21522SJames Collins $this->seek($sPreArgs); 35718ba21522SJames Collins if ($this->openString(")", $string) && $this->literal(")")) { 35728ba21522SJames Collins $func = array('function', $fname, $string); 35738ba21522SJames Collins return true; 35748ba21522SJames Collins } 35758ba21522SJames Collins } 35768ba21522SJames Collins } 35778ba21522SJames Collins 35788ba21522SJames Collins $this->seek($s); 35798ba21522SJames Collins return false; 35808ba21522SJames Collins } 35818ba21522SJames Collins 35828ba21522SJames Collins // consume a less variable 35838ba21522SJames Collins protected function variable(&$name) { 35848ba21522SJames Collins $s = $this->seek(); 35858ba21522SJames Collins if ($this->literal($this->lessc->vPrefix, false) && 35868ba21522SJames Collins ($this->variable($sub) || $this->keyword($name))) 35878ba21522SJames Collins { 35888ba21522SJames Collins if (!empty($sub)) { 35898ba21522SJames Collins $name = array('variable', $sub); 35908ba21522SJames Collins } else { 35918ba21522SJames Collins $name = $this->lessc->vPrefix.$name; 35928ba21522SJames Collins } 35938ba21522SJames Collins return true; 35948ba21522SJames Collins } 35958ba21522SJames Collins 35968ba21522SJames Collins $name = null; 35978ba21522SJames Collins $this->seek($s); 35988ba21522SJames Collins return false; 35998ba21522SJames Collins } 36008ba21522SJames Collins 36018ba21522SJames Collins /** 36028ba21522SJames Collins * Consume an assignment operator 36038ba21522SJames Collins * Can optionally take a name that will be set to the current property name 36048ba21522SJames Collins */ 36058ba21522SJames Collins protected function assign($name = null) { 36068ba21522SJames Collins if ($name) $this->currentProperty = $name; 36078ba21522SJames Collins return $this->literal(':') || $this->literal('='); 36088ba21522SJames Collins } 36098ba21522SJames Collins 36108ba21522SJames Collins // consume a keyword 36118ba21522SJames Collins protected function keyword(&$word) { 36128ba21522SJames Collins if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { 36138ba21522SJames Collins $word = $m[1]; 36148ba21522SJames Collins return true; 36158ba21522SJames Collins } 36168ba21522SJames Collins return false; 36178ba21522SJames Collins } 36188ba21522SJames Collins 36198ba21522SJames Collins // consume an end of statement delimiter 36208ba21522SJames Collins protected function end() { 36218ba21522SJames Collins if ($this->literal(';', false)) { 36228ba21522SJames Collins return true; 36238ba21522SJames Collins } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') { 36248ba21522SJames Collins // if there is end of file or a closing block next then we don't need a ; 36258ba21522SJames Collins return true; 36268ba21522SJames Collins } 36278ba21522SJames Collins return false; 36288ba21522SJames Collins } 36298ba21522SJames Collins 36308ba21522SJames Collins protected function guards(&$guards) { 36318ba21522SJames Collins $s = $this->seek(); 36328ba21522SJames Collins 36338ba21522SJames Collins if (!$this->literal("when")) { 36348ba21522SJames Collins $this->seek($s); 36358ba21522SJames Collins return false; 36368ba21522SJames Collins } 36378ba21522SJames Collins 36388ba21522SJames Collins $guards = array(); 36398ba21522SJames Collins 36408ba21522SJames Collins while ($this->guardGroup($g)) { 36418ba21522SJames Collins $guards[] = $g; 36428ba21522SJames Collins if (!$this->literal(",")) break; 36438ba21522SJames Collins } 36448ba21522SJames Collins 36458ba21522SJames Collins if (count($guards) == 0) { 36468ba21522SJames Collins $guards = null; 36478ba21522SJames Collins $this->seek($s); 36488ba21522SJames Collins return false; 36498ba21522SJames Collins } 36508ba21522SJames Collins 36518ba21522SJames Collins return true; 36528ba21522SJames Collins } 36538ba21522SJames Collins 36548ba21522SJames Collins // a bunch of guards that are and'd together 36558ba21522SJames Collins // TODO rename to guardGroup 36568ba21522SJames Collins protected function guardGroup(&$guardGroup) { 36578ba21522SJames Collins $s = $this->seek(); 36588ba21522SJames Collins $guardGroup = array(); 36598ba21522SJames Collins while ($this->guard($guard)) { 36608ba21522SJames Collins $guardGroup[] = $guard; 36618ba21522SJames Collins if (!$this->literal("and")) break; 36628ba21522SJames Collins } 36638ba21522SJames Collins 36648ba21522SJames Collins if (count($guardGroup) == 0) { 36658ba21522SJames Collins $guardGroup = null; 36668ba21522SJames Collins $this->seek($s); 36678ba21522SJames Collins return false; 36688ba21522SJames Collins } 36698ba21522SJames Collins 36708ba21522SJames Collins return true; 36718ba21522SJames Collins } 36728ba21522SJames Collins 36738ba21522SJames Collins protected function guard(&$guard) { 36748ba21522SJames Collins $s = $this->seek(); 36758ba21522SJames Collins $negate = $this->literal("not"); 36768ba21522SJames Collins 36778ba21522SJames Collins if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { 36788ba21522SJames Collins $guard = $exp; 36798ba21522SJames Collins if ($negate) $guard = array("negate", $guard); 36808ba21522SJames Collins return true; 36818ba21522SJames Collins } 36828ba21522SJames Collins 36838ba21522SJames Collins $this->seek($s); 36848ba21522SJames Collins return false; 36858ba21522SJames Collins } 36868ba21522SJames Collins 36878ba21522SJames Collins /* raw parsing functions */ 36888ba21522SJames Collins 36898ba21522SJames Collins protected function literal($what, $eatWhitespace = null) { 36908ba21522SJames Collins if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; 36918ba21522SJames Collins 36928ba21522SJames Collins // shortcut on single letter 36938ba21522SJames Collins if (!isset($what[1]) && isset($this->buffer[$this->count])) { 36948ba21522SJames Collins if ($this->buffer[$this->count] == $what) { 36958ba21522SJames Collins if (!$eatWhitespace) { 36968ba21522SJames Collins $this->count++; 36978ba21522SJames Collins return true; 36988ba21522SJames Collins } 36998ba21522SJames Collins // goes below... 37008ba21522SJames Collins } else { 37018ba21522SJames Collins return false; 37028ba21522SJames Collins } 37038ba21522SJames Collins } 37048ba21522SJames Collins 37058ba21522SJames Collins if (!isset(self::$literalCache[$what])) { 37068ba21522SJames Collins self::$literalCache[$what] = lessc::preg_quote($what); 37078ba21522SJames Collins } 37088ba21522SJames Collins 37098ba21522SJames Collins return $this->match(self::$literalCache[$what], $m, $eatWhitespace); 37108ba21522SJames Collins } 37118ba21522SJames Collins 37128ba21522SJames Collins protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { 37138ba21522SJames Collins $s = $this->seek(); 37148ba21522SJames Collins $items = array(); 37158ba21522SJames Collins while ($this->$parseItem($value)) { 37168ba21522SJames Collins $items[] = $value; 37178ba21522SJames Collins if ($delim) { 37188ba21522SJames Collins if (!$this->literal($delim)) break; 37198ba21522SJames Collins } 37208ba21522SJames Collins } 37218ba21522SJames Collins 37228ba21522SJames Collins if (count($items) == 0) { 37238ba21522SJames Collins $this->seek($s); 37248ba21522SJames Collins return false; 37258ba21522SJames Collins } 37268ba21522SJames Collins 37278ba21522SJames Collins if ($flatten && count($items) == 1) { 37288ba21522SJames Collins $out = $items[0]; 37298ba21522SJames Collins } else { 37308ba21522SJames Collins $out = array("list", $delim, $items); 37318ba21522SJames Collins } 37328ba21522SJames Collins 37338ba21522SJames Collins return true; 37348ba21522SJames Collins } 37358ba21522SJames Collins 37368ba21522SJames Collins 37378ba21522SJames Collins // advance counter to next occurrence of $what 37388ba21522SJames Collins // $until - don't include $what in advance 37398ba21522SJames Collins // $allowNewline, if string, will be used as valid char set 37408ba21522SJames Collins protected function to($what, &$out, $until = false, $allowNewline = false) { 37418ba21522SJames Collins if (is_string($allowNewline)) { 37428ba21522SJames Collins $validChars = $allowNewline; 37438ba21522SJames Collins } else { 37448ba21522SJames Collins $validChars = $allowNewline ? "." : "[^\n]"; 37458ba21522SJames Collins } 37468ba21522SJames Collins if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false; 37478ba21522SJames Collins if ($until) $this->count -= strlen($what); // give back $what 37488ba21522SJames Collins $out = $m[1]; 37498ba21522SJames Collins return true; 37508ba21522SJames Collins } 37518ba21522SJames Collins 37528ba21522SJames Collins // try to match something on head of buffer 37538ba21522SJames Collins protected function match($regex, &$out, $eatWhitespace = null) { 37548ba21522SJames Collins if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; 37558ba21522SJames Collins 37568ba21522SJames Collins $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais'; 37578ba21522SJames Collins if (preg_match($r, $this->buffer, $out, 0, $this->count)) { 37588ba21522SJames Collins $this->count += strlen($out[0]); 37598ba21522SJames Collins if ($eatWhitespace && $this->writeComments) $this->whitespace(); 37608ba21522SJames Collins return true; 37618ba21522SJames Collins } 37628ba21522SJames Collins return false; 37638ba21522SJames Collins } 37648ba21522SJames Collins 37658ba21522SJames Collins // match some whitespace 37668ba21522SJames Collins protected function whitespace() { 37678ba21522SJames Collins if ($this->writeComments) { 37688ba21522SJames Collins $gotWhite = false; 37698ba21522SJames Collins while (preg_match(self::$whitePattern, $this->buffer, $m, 0, $this->count)) { 37708ba21522SJames Collins if (isset($m[1]) && empty($this->seenComments[$this->count])) { 37718ba21522SJames Collins $this->append(array("comment", $m[1])); 37728ba21522SJames Collins $this->seenComments[$this->count] = true; 37738ba21522SJames Collins } 37748ba21522SJames Collins $this->count += strlen($m[0]); 37758ba21522SJames Collins $gotWhite = true; 37768ba21522SJames Collins } 37778ba21522SJames Collins return $gotWhite; 37788ba21522SJames Collins } else { 37798ba21522SJames Collins $this->match("", $m); 37808ba21522SJames Collins return strlen($m[0]) > 0; 37818ba21522SJames Collins } 37828ba21522SJames Collins } 37838ba21522SJames Collins 37848ba21522SJames Collins // match something without consuming it 37858ba21522SJames Collins protected function peek($regex, &$out = null, $from=null) { 37868ba21522SJames Collins if (is_null($from)) $from = $this->count; 37878ba21522SJames Collins $r = '/'.$regex.'/Ais'; 37888ba21522SJames Collins $result = preg_match($r, $this->buffer, $out, 0, $from); 37898ba21522SJames Collins 37908ba21522SJames Collins return $result; 37918ba21522SJames Collins } 37928ba21522SJames Collins 37938ba21522SJames Collins // seek to a spot in the buffer or return where we are on no argument 37948ba21522SJames Collins protected function seek($where = null) { 37958ba21522SJames Collins if ($where === null) return $this->count; 37968ba21522SJames Collins else $this->count = $where; 37978ba21522SJames Collins return true; 37988ba21522SJames Collins } 37998ba21522SJames Collins 38008ba21522SJames Collins /* misc functions */ 38018ba21522SJames Collins 38028ba21522SJames Collins public function throwError($msg = "parse error", $count = null) { 38038ba21522SJames Collins $count = is_null($count) ? $this->count : $count; 38048ba21522SJames Collins 38058ba21522SJames Collins $line = $this->line + 38068ba21522SJames Collins substr_count(substr($this->buffer, 0, $count), "\n"); 38078ba21522SJames Collins 38088ba21522SJames Collins if (!empty($this->sourceName)) { 38098ba21522SJames Collins $loc = "$this->sourceName on line $line"; 38108ba21522SJames Collins } else { 38118ba21522SJames Collins $loc = "line: $line"; 38128ba21522SJames Collins } 38138ba21522SJames Collins 38148ba21522SJames Collins // TODO this depends on $this->count 38158ba21522SJames Collins if ($this->peek("(.*?)(\n|$)", $m, $count)) { 38168ba21522SJames Collins throw new \Exception("$msg: failed at `$m[1]` $loc"); 38178ba21522SJames Collins } else { 38188ba21522SJames Collins throw new \Exception("$msg: $loc"); 38198ba21522SJames Collins } 38208ba21522SJames Collins } 38218ba21522SJames Collins 38228ba21522SJames Collins protected function pushBlock($selectors=null, $type=null) { 38238ba21522SJames Collins $b = new \stdClass(); 38248ba21522SJames Collins $b->parent = $this->env; 38258ba21522SJames Collins 38268ba21522SJames Collins $b->type = $type; 38278ba21522SJames Collins $b->id = self::$nextBlockId++; 38288ba21522SJames Collins 38298ba21522SJames Collins $b->isVararg = false; // TODO: kill me from here 38308ba21522SJames Collins $b->tags = $selectors; 38318ba21522SJames Collins 38328ba21522SJames Collins $b->props = array(); 38338ba21522SJames Collins $b->children = array(); 38348ba21522SJames Collins 38358ba21522SJames Collins // add a reference to the parser so 38368ba21522SJames Collins // we can access the parser to throw errors 38378ba21522SJames Collins // or retrieve the sourceName of this block. 38388ba21522SJames Collins $b->parser = $this; 38398ba21522SJames Collins 38408ba21522SJames Collins // so we know the position of this block 38418ba21522SJames Collins $b->count = $this->count; 38428ba21522SJames Collins 38438ba21522SJames Collins $this->env = $b; 38448ba21522SJames Collins return $b; 38458ba21522SJames Collins } 38468ba21522SJames Collins 38478ba21522SJames Collins // push a block that doesn't multiply tags 38488ba21522SJames Collins protected function pushSpecialBlock($type) { 38498ba21522SJames Collins return $this->pushBlock(null, $type); 38508ba21522SJames Collins } 38518ba21522SJames Collins 38528ba21522SJames Collins // append a property to the current block 38538ba21522SJames Collins protected function append($prop, $pos = null) { 38548ba21522SJames Collins if ($pos !== null) $prop[-1] = $pos; 38558ba21522SJames Collins $this->env->props[] = $prop; 38568ba21522SJames Collins } 38578ba21522SJames Collins 38588ba21522SJames Collins // pop something off the stack 38598ba21522SJames Collins protected function pop() { 38608ba21522SJames Collins $old = $this->env; 38618ba21522SJames Collins $this->env = $this->env->parent; 38628ba21522SJames Collins return $old; 38638ba21522SJames Collins } 38648ba21522SJames Collins 38658ba21522SJames Collins // remove comments from $text 38668ba21522SJames Collins // todo: make it work for all functions, not just url 38678ba21522SJames Collins protected function removeComments($text) { 38688ba21522SJames Collins $look = array( 38698ba21522SJames Collins 'url(', '//', '/*', '"', "'" 38708ba21522SJames Collins ); 38718ba21522SJames Collins 38728ba21522SJames Collins $out = ''; 38738ba21522SJames Collins $min = null; 38748ba21522SJames Collins while (true) { 38758ba21522SJames Collins // find the next item 38768ba21522SJames Collins foreach ($look as $token) { 38778ba21522SJames Collins $pos = strpos($text, $token); 38788ba21522SJames Collins if ($pos !== false) { 38798ba21522SJames Collins if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); 38808ba21522SJames Collins } 38818ba21522SJames Collins } 38828ba21522SJames Collins 38838ba21522SJames Collins if (is_null($min)) break; 38848ba21522SJames Collins 38858ba21522SJames Collins $count = $min[1]; 38868ba21522SJames Collins $skip = 0; 38878ba21522SJames Collins $newlines = 0; 38888ba21522SJames Collins switch ($min[0]) { 38898ba21522SJames Collins case 'url(': 38908ba21522SJames Collins if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) 38918ba21522SJames Collins $count += strlen($m[0]) - strlen($min[0]); 38928ba21522SJames Collins break; 38938ba21522SJames Collins case '"': 38948ba21522SJames Collins case "'": 38958ba21522SJames Collins if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count)) 38968ba21522SJames Collins $count += strlen($m[0]) - 1; 38978ba21522SJames Collins break; 38988ba21522SJames Collins case '//': 38998ba21522SJames Collins $skip = strpos($text, "\n", $count); 39008ba21522SJames Collins if ($skip === false) $skip = strlen($text) - $count; 39018ba21522SJames Collins else $skip -= $count; 39028ba21522SJames Collins break; 39038ba21522SJames Collins case '/*': 39048ba21522SJames Collins if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { 39058ba21522SJames Collins $skip = strlen($m[0]); 39068ba21522SJames Collins $newlines = substr_count($m[0], "\n"); 39078ba21522SJames Collins } 39088ba21522SJames Collins break; 39098ba21522SJames Collins } 39108ba21522SJames Collins 39118ba21522SJames Collins if ($skip == 0) $count += strlen($min[0]); 39128ba21522SJames Collins 39138ba21522SJames Collins $out .= substr($text, 0, $count).str_repeat("\n", $newlines); 39148ba21522SJames Collins $text = substr($text, $count + $skip); 39158ba21522SJames Collins 39168ba21522SJames Collins $min = null; 39178ba21522SJames Collins } 39188ba21522SJames Collins 39198ba21522SJames Collins return $out.$text; 39208ba21522SJames Collins } 39218ba21522SJames Collins 39228ba21522SJames Collins} 39238ba21522SJames Collins 39248ba21522SJames Collinsclass lessc_formatter_classic { 39258ba21522SJames Collins public $indentChar = " "; 39268ba21522SJames Collins 39278ba21522SJames Collins public $break = "\n"; 39288ba21522SJames Collins public $open = " {"; 39298ba21522SJames Collins public $close = "}"; 39308ba21522SJames Collins public $selectorSeparator = ", "; 39318ba21522SJames Collins public $assignSeparator = ":"; 39328ba21522SJames Collins 39338ba21522SJames Collins public $openSingle = " { "; 39348ba21522SJames Collins public $closeSingle = " }"; 39358ba21522SJames Collins 39368ba21522SJames Collins public $disableSingle = false; 39378ba21522SJames Collins public $breakSelectors = false; 39388ba21522SJames Collins 39398ba21522SJames Collins public $compressColors = false; 39408ba21522SJames Collins 39418ba21522SJames Collins public function __construct() { 39428ba21522SJames Collins $this->indentLevel = 0; 39438ba21522SJames Collins } 39448ba21522SJames Collins 39458ba21522SJames Collins public function indentStr($n = 0) { 39468ba21522SJames Collins return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); 39478ba21522SJames Collins } 39488ba21522SJames Collins 39498ba21522SJames Collins public function property($name, $value) { 39508ba21522SJames Collins return $name . $this->assignSeparator . $value . ";"; 39518ba21522SJames Collins } 39528ba21522SJames Collins 39538ba21522SJames Collins protected function isEmpty($block) { 39548ba21522SJames Collins if (empty($block->lines)) { 39558ba21522SJames Collins foreach ($block->children as $child) { 39568ba21522SJames Collins if (!$this->isEmpty($child)) return false; 39578ba21522SJames Collins } 39588ba21522SJames Collins 39598ba21522SJames Collins return true; 39608ba21522SJames Collins } 39618ba21522SJames Collins return false; 39628ba21522SJames Collins } 39638ba21522SJames Collins 39648ba21522SJames Collins public function block($block) { 39658ba21522SJames Collins if ($this->isEmpty($block)) return; 39668ba21522SJames Collins 39678ba21522SJames Collins $inner = $pre = $this->indentStr(); 39688ba21522SJames Collins 39698ba21522SJames Collins $isSingle = !$this->disableSingle && 39708ba21522SJames Collins is_null($block->type) && count($block->lines) == 1; 39718ba21522SJames Collins 39728ba21522SJames Collins if (!empty($block->selectors)) { 39738ba21522SJames Collins $this->indentLevel++; 39748ba21522SJames Collins 39758ba21522SJames Collins if ($this->breakSelectors) { 39768ba21522SJames Collins $selectorSeparator = $this->selectorSeparator . $this->break . $pre; 39778ba21522SJames Collins } else { 39788ba21522SJames Collins $selectorSeparator = $this->selectorSeparator; 39798ba21522SJames Collins } 39808ba21522SJames Collins 39818ba21522SJames Collins echo $pre . 39828ba21522SJames Collins implode($selectorSeparator, $block->selectors); 39838ba21522SJames Collins if ($isSingle) { 39848ba21522SJames Collins echo $this->openSingle; 39858ba21522SJames Collins $inner = ""; 39868ba21522SJames Collins } else { 39878ba21522SJames Collins echo $this->open . $this->break; 39888ba21522SJames Collins $inner = $this->indentStr(); 39898ba21522SJames Collins } 39908ba21522SJames Collins 39918ba21522SJames Collins } 39928ba21522SJames Collins 39938ba21522SJames Collins if (!empty($block->lines)) { 39948ba21522SJames Collins $glue = $this->break.$inner; 39958ba21522SJames Collins echo $inner . implode($glue, $block->lines); 39968ba21522SJames Collins if (!$isSingle && !empty($block->children)) { 39978ba21522SJames Collins echo $this->break; 39988ba21522SJames Collins } 39998ba21522SJames Collins } 40008ba21522SJames Collins 40018ba21522SJames Collins foreach ($block->children as $child) { 40028ba21522SJames Collins $this->block($child); 40038ba21522SJames Collins } 40048ba21522SJames Collins 40058ba21522SJames Collins if (!empty($block->selectors)) { 40068ba21522SJames Collins if (!$isSingle && empty($block->children)) echo $this->break; 40078ba21522SJames Collins 40088ba21522SJames Collins if ($isSingle) { 40098ba21522SJames Collins echo $this->closeSingle . $this->break; 40108ba21522SJames Collins } else { 40118ba21522SJames Collins echo $pre . $this->close . $this->break; 40128ba21522SJames Collins } 40138ba21522SJames Collins 40148ba21522SJames Collins $this->indentLevel--; 40158ba21522SJames Collins } 40168ba21522SJames Collins } 40178ba21522SJames Collins} 40188ba21522SJames Collins 40198ba21522SJames Collinsclass lessc_formatter_compressed extends lessc_formatter_classic { 40208ba21522SJames Collins public $disableSingle = true; 40218ba21522SJames Collins public $open = "{"; 40228ba21522SJames Collins public $selectorSeparator = ","; 40238ba21522SJames Collins public $assignSeparator = ":"; 40248ba21522SJames Collins public $break = ""; 40258ba21522SJames Collins public $compressColors = true; 40268ba21522SJames Collins 40278ba21522SJames Collins public function indentStr($n = 0) { 40288ba21522SJames Collins return ""; 40298ba21522SJames Collins } 40308ba21522SJames Collins} 40318ba21522SJames Collins 40328ba21522SJames Collinsclass lessc_formatter_lessjs extends lessc_formatter_classic { 40338ba21522SJames Collins public $disableSingle = true; 40348ba21522SJames Collins public $breakSelectors = true; 40358ba21522SJames Collins public $assignSeparator = ": "; 40368ba21522SJames Collins public $selectorSeparator = ","; 4037*a92686aaSJames Collins public $indentLevel; 40388ba21522SJames Collins} 40398ba21522SJames Collins 40408ba21522SJames Collins 4041