xref: /template/mikio/inc/stemmechanics/lesserphp/lessc.inc.php (revision a92686aa569bf19e6b4530af2d5a1a53e4e0f66b)
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