From 60ce17ac379e80c5ca137c559f2fce048516818d Mon Sep 17 00:00:00 2001 From: Andreas Gohr <andi@splitbrain.org> Date: Wed, 14 Aug 2013 21:33:58 +0200 Subject: [PATCH] updated phpless to 0.4.0 --- inc/lessc.inc.php | 398 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 296 insertions(+), 102 deletions(-) diff --git a/inc/lessc.inc.php b/inc/lessc.inc.php index 5c81ad2a9..0699de52f 100644 --- a/inc/lessc.inc.php +++ b/inc/lessc.inc.php @@ -1,7 +1,7 @@ <?php /** - * lessphp v0.3.9 + * lessphp v0.4.0 * http://leafo.net/lessphp * * LESS css compiler, adapted from http://lesscss.org @@ -38,7 +38,7 @@ * handling things like indentation. */ class lessc { - static public $VERSION = "v0.3.9"; + static public $VERSION = "v0.4.0"; static protected $TRUE = array("keyword", "true"); static protected $FALSE = array("keyword", "false"); @@ -55,6 +55,8 @@ class lessc { protected $numberPrecision = null; + protected $allParsedFiles = array(); + // set to the parser that generated the current line when compiling // so we know how to create error messages protected $sourceParser = null; @@ -103,12 +105,17 @@ class lessc { if (substr_compare($url, '.css', -4, 4) === 0) return false; $realPath = $this->findImport($url); + if ($realPath === null) return false; if ($this->importDisabled) { return array(false, "/* import disabled */"); } + if (isset($this->allParsedFiles[realpath($realPath)])) { + return array(false, null); + } + $this->addParsedFile($realPath); $parser = $this->makeParser($realPath); $root = $parser->parse(file_get_contents($realPath)); @@ -276,6 +283,8 @@ class lessc { foreach ($this->sortProps($block->props) as $prop) { $this->compileProp($prop, $block, $out); } + + $out->lines = array_values(array_unique($out->lines)); } protected function sortProps($props, $split = false) { @@ -450,7 +459,7 @@ class lessc { return $left == $right; } - protected function patternMatch($block, $callingArgs) { + protected function patternMatch($block, $orderedArgs, $keywordArgs) { // match the guards if it has them // any one of the groups must have all its guards pass for a match if (!empty($block->guards)) { @@ -458,7 +467,7 @@ class lessc { foreach ($block->guards as $guardGroup) { foreach ($guardGroup as $guard) { $this->pushEnv(); - $this->zipSetArgs($block->args, $callingArgs); + $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs); $negate = false; if ($guard[0] == "negate") { @@ -487,24 +496,34 @@ class lessc { } } - $numCalling = count($callingArgs); - if (empty($block->args)) { - return $block->isVararg || $numCalling == 0; + return $block->isVararg || empty($orderedArgs) && empty($keywordArgs); + } + + $remainingArgs = $block->args; + if ($keywordArgs) { + $remainingArgs = array(); + foreach ($block->args as $arg) { + if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) { + continue; + } + + $remainingArgs[] = $arg; + } } $i = -1; // no args // try to match by arity or by argument literal - foreach ($block->args as $i => $arg) { + foreach ($remainingArgs as $i => $arg) { switch ($arg[0]) { case "lit": - if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) { + if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) { return false; } break; case "arg": // no arg and no default value - if (!isset($callingArgs[$i]) && !isset($arg[2])) { + if (!isset($orderedArgs[$i]) && !isset($arg[2])) { return false; } break; @@ -519,14 +538,19 @@ class lessc { } else { $numMatched = $i + 1; // greater than becuase default values always match - return $numMatched >= $numCalling; + return $numMatched >= count($orderedArgs); } } - protected function patternMatchAll($blocks, $callingArgs) { + protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) { $matches = null; foreach ($blocks as $block) { - if ($this->patternMatch($block, $callingArgs)) { + // skip seen blocks that don't have arguments + if (isset($skip[$block->id]) && !isset($block->args)) { + continue; + } + + if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) { $matches[] = $block; } } @@ -535,7 +559,7 @@ class lessc { } // attempt to find blocks matched by path and args - protected function findBlocks($searchIn, $path, $args, $seen=array()) { + protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) { if ($searchIn == null) return null; if (isset($seen[$searchIn->id])) return null; $seen[$searchIn->id] = true; @@ -545,7 +569,7 @@ class lessc { if (isset($searchIn->children[$name])) { $blocks = $searchIn->children[$name]; if (count($path) == 1) { - $matches = $this->patternMatchAll($blocks, $args); + $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen); if (!empty($matches)) { // This will return all blocks that match in the closest // scope that has any matching block, like lessjs @@ -555,7 +579,7 @@ class lessc { $matches = array(); foreach ($blocks as $subBlock) { $subMatches = $this->findBlocks($subBlock, - array_slice($path, 1), $args, $seen); + array_slice($path, 1), $orderedArgs, $keywordArgs, $seen); if (!is_null($subMatches)) { foreach ($subMatches as $sm) { @@ -567,39 +591,51 @@ class lessc { return count($matches) > 0 ? $matches : null; } } - if ($searchIn->parent === $searchIn) return null; - return $this->findBlocks($searchIn->parent, $path, $args, $seen); + return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen); } // sets all argument names in $args to either the default value // or the one passed in through $values - protected function zipSetArgs($args, $values) { - $i = 0; + protected function zipSetArgs($args, $orderedValues, $keywordValues) { $assignedValues = array(); - foreach ($args as $a) { + + $i = 0; + foreach ($args as $a) { if ($a[0] == "arg") { - if ($i < count($values) && !is_null($values[$i])) { - $value = $values[$i]; + if (isset($keywordValues[$a[1]])) { + // has keyword arg + $value = $keywordValues[$a[1]]; + } elseif (isset($orderedValues[$i])) { + // has ordered arg + $value = $orderedValues[$i]; + $i++; } elseif (isset($a[2])) { + // has default value $value = $a[2]; - } else $value = null; + } else { + $this->throwError("Failed to assign arg " . $a[1]); + $value = null; // :( + } $value = $this->reduce($value); $this->set($a[1], $value); $assignedValues[] = $value; + } else { + // a lit + $i++; } - $i++; } // check for a rest $last = end($args); if ($last[0] == "rest") { - $rest = array_slice($values, count($args) - 1); + $rest = array_slice($orderedValues, count($args) - 1); $this->set($last[1], $this->reduce(array("list", " ", $rest))); } - $this->env->arguments = $assignedValues; + // wow is this the only true use of PHP's + operator for arrays? + $this->env->arguments = $assignedValues + $orderedValues; } // compile a prop and update $lines or $blocks appropriately @@ -624,8 +660,28 @@ class lessc { case 'mixin': list(, $path, $args, $suffix) = $prop; - $args = array_map(array($this, "reduce"), (array)$args); - $mixins = $this->findBlocks($block, $path, $args); + $orderedArgs = array(); + $keywordArgs = array(); + foreach ((array)$args as $arg) { + $argval = null; + switch ($arg[0]) { + case "arg": + if (!isset($arg[2])) { + $orderedArgs[] = $this->reduce(array("variable", $arg[1])); + } else { + $keywordArgs[$arg[1]] = $this->reduce($arg[2]); + } + break; + + case "lit": + $orderedArgs[] = $this->reduce($arg[1]); + break; + default: + $this->throwError("Unknown arg type: " . $arg[0]); + } + } + + $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs); if ($mixins === null) { // fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n"); @@ -633,7 +689,7 @@ class lessc { } foreach ($mixins as $mixin) { - if ($mixin === $block && !$args) { + if ($mixin === $block && !$orderedArgs) { continue; } @@ -648,7 +704,7 @@ class lessc { if (isset($mixin->args)) { $haveArgs = true; $this->pushEnv(); - $this->zipSetArgs($mixin->args, $args); + $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs); } $oldParent = $mixin->parent; @@ -705,7 +761,9 @@ class lessc { list(,$importId) = $prop; $import = $this->env->imports[$importId]; if ($import[0] === false) { - $out->lines[] = $import[1]; + if (isset($import[1])) { + $out->lines[] = $import[1]; + } } else { list(, $bottom, $parser, $importDir) = $import; $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); @@ -793,6 +851,60 @@ class lessc { } } + protected function lib_pow($args) { + list($base, $exp) = $this->assertArgs($args, 2, "pow"); + return pow($this->assertNumber($base), $this->assertNumber($exp)); + } + + protected function lib_pi() { + return pi(); + } + + protected function lib_mod($args) { + list($a, $b) = $this->assertArgs($args, 2, "mod"); + return $this->assertNumber($a) % $this->assertNumber($b); + } + + protected function lib_tan($num) { + return tan($this->assertNumber($num)); + } + + protected function lib_sin($num) { + return sin($this->assertNumber($num)); + } + + protected function lib_cos($num) { + return cos($this->assertNumber($num)); + } + + protected function lib_atan($num) { + $num = atan($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_asin($num) { + $num = asin($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_acos($num) { + $num = acos($this->assertNumber($num)); + return array("number", $num, "rad"); + } + + protected function lib_sqrt($num) { + return sqrt($this->assertNumber($num)); + } + + protected function lib_extract($value) { + list($list, $idx) = $this->assertArgs($value, 2, "extract"); + $idx = $this->assertNumber($idx); + // 1 indexed + if ($list[0] == "list" && isset($list[2][$idx - 1])) { + return $list[2][$idx - 1]; + } + } + protected function lib_isnumber($value) { return $this->toBool($value[0] == "number"); } @@ -1085,6 +1197,25 @@ class lessc { $this->throwError($error); } + protected function assertArgs($value, $expectedArgs, $name="") { + if ($expectedArgs == 1) { + return $value; + } else { + if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list"); + $values = $value[2]; + $numValues = count($values); + if ($expectedArgs != $numValues) { + if ($name) { + $name = $name . ": "; + } + + $this->throwError("${name}expecting $expectedArgs arguments, got $numValues"); + } + + return $values; + } + } + protected function toHSL($color) { if ($color[0] == 'hsl') return $color; @@ -1229,6 +1360,10 @@ class lessc { $var = $this->compileValue($reduced); $res = $this->reduce(array("variable", $this->vPrefix . $var)); + if ($res[0] == "raw_color") { + $res = $this->coerceColor($res); + } + if (empty($value[2])) $res = $this->lib_e($res); return $res; @@ -1690,7 +1825,6 @@ class lessc { $this->importDir = (array)$this->importDir; $this->importDir[] = $pi['dirname'].'/'; - $this->allParsedFiles = array(); $this->addParsedFile($fname); $out = $this->compile(file_get_contents($fname), $fname); @@ -2313,7 +2447,7 @@ class lessc_parser { // mixin if ($this->mixinTags($tags) && - ($this->argumentValues($argv) || true) && + ($this->argumentDef($argv, $isVararg) || true) && ($this->keyword($suffix) || true) && $this->end()) { $tags = $this->fixTags($tags); @@ -2781,38 +2915,18 @@ class lessc_parser { return false; } - // consume a list of property values delimited by ; and wrapped in () - protected function argumentValues(&$args, $delim = ',') { - $s = $this->seek(); - if (!$this->literal('(')) return false; - - $values = array(); - while (true) { - if ($this->expressionList($value)) $values[] = $value; - if (!$this->literal($delim)) break; - else { - if ($value == null) $values[] = null; - $value = null; - } - } - - if (!$this->literal(')')) { - $this->seek($s); - return false; - } - - $args = $values; - return true; - } - // consume an argument definition list surrounded by () // each argument is a variable name with optional value // or at the end a ... or a variable named followed by ... - protected function argumentDef(&$args, &$isVararg, $delim = ',') { + // arguments are separated by , unless a ; is in the list, then ; is the + // delimiter. + protected function argumentDef(&$args, &$isVararg) { $s = $this->seek(); if (!$this->literal('(')) return false; $values = array(); + $delim = ","; + $method = "expressionList"; $isVararg = false; while (true) { @@ -2821,28 +2935,77 @@ class lessc_parser { break; } - if ($this->variable($vname)) { - $arg = array("arg", $vname); - $ss = $this->seek(); - if ($this->assign() && $this->expressionList($value)) { - $arg[] = $value; - } else { - $this->seek($ss); - if ($this->literal("...")) { - $arg[0] = "rest"; - $isVararg = true; + if ($this->$method($value)) { + if ($value[0] == "variable") { + $arg = array("arg", $value[1]); + $ss = $this->seek(); + + if ($this->assign() && $this->$method($rhs)) { + $arg[] = $rhs; + } else { + $this->seek($ss); + if ($this->literal("...")) { + $arg[0] = "rest"; + $isVararg = true; + } } + + $values[] = $arg; + if ($isVararg) break; + continue; + } else { + $values[] = array("lit", $value); } - $values[] = $arg; - if ($isVararg) break; - continue; } - if ($this->value($literal)) { - $values[] = array("lit", $literal); - } - if (!$this->literal($delim)) break; + if (!$this->literal($delim)) { + if ($delim == "," && $this->literal(";")) { + // found new delim, convert existing args + $delim = ";"; + $method = "propertyValue"; + + // transform arg list + if (isset($values[1])) { // 2 items + $newList = array(); + foreach ($values as $i => $arg) { + switch($arg[0]) { + case "arg": + if ($i) { + $this->throwError("Cannot mix ; and , as delimiter types"); + } + $newList[] = $arg[2]; + break; + case "lit": + $newList[] = $arg[1]; + break; + case "rest": + $this->throwError("Unexpected rest before semicolon"); + } + } + + $newList = array("list", ", ", $newList); + + switch ($values[0][0]) { + case "arg": + $newArg = array("arg", $values[0][1], $newList); + break; + case "lit": + $newArg = array("lit", $newList); + break; + } + + } elseif ($values) { // 1 item + $newArg = $values[0]; + } + + if ($newArg) { + $values = array($newArg); + } + } else { + break; + } + } } if (!$this->literal(')')) { @@ -2884,32 +3047,69 @@ class lessc_parser { } // a bracketed value (contained within in a tag definition) - protected function tagBracket(&$value) { + protected function tagBracket(&$parts, &$hasExpression) { // speed shortcut if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { return false; } $s = $this->seek(); - if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { - $value = '['.$c.']'; - // whitespace? - if ($this->whitespace()) $value .= " "; - // escape parent selector, (yuck) - $value = str_replace($this->lessc->parentSelector, "$&$", $value); - return true; - } + $hasInterpolation = false; - $this->seek($s); - return false; - } + if ($this->literal("[", false)) { + $attrParts = array("["); + // keyword, string, operator + while (true) { + if ($this->literal("]", false)) { + $this->count--; + break; // get out early + } - protected function tagExpression(&$value) { - $s = $this->seek(); - if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { - $value = array('exp', $exp); - return true; + if ($this->match('\s+', $m)) { + $attrParts[] = " "; + continue; + } + if ($this->string($str)) { + // escape parent selector, (yuck) + foreach ($str[2] as &$chunk) { + $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk); + } + + $attrParts[] = $str; + $hasInterpolation = true; + continue; + } + + if ($this->keyword($word)) { + $attrParts[] = $word; + continue; + } + + if ($this->interpolation($inter, false)) { + $attrParts[] = $inter; + $hasInterpolation = true; + continue; + } + + // operator, handles attr namespace too + if ($this->match('[|-~\$\*\^=]+', $m)) { + $attrParts[] = $m[0]; + continue; + } + + break; + } + + if ($this->literal("]", false)) { + $attrParts[] = "]"; + foreach ($attrParts as $part) { + $parts[] = $part; + } + $hasExpression = $hasExpression || $hasInterpolation; + return true; + } + $this->seek($s); } $this->seek($s); @@ -2925,13 +3125,9 @@ class lessc_parser { $s = $this->seek(); - if (!$simple && $this->tagExpression($tag)) { - return true; - } - $hasExpression = false; $parts = array(); - while ($this->tagBracket($first)) $parts[] = $first; + while ($this->tagBracket($parts, $hasExpression)); $oldWhite = $this->eatWhiteDefault; $this->eatWhiteDefault = false; @@ -2941,9 +3137,7 @@ class lessc_parser { $parts[] = $m[1]; if ($simple) break; - while ($this->tagBracket($brack)) { - $parts[] = $brack; - } + while ($this->tagBracket($parts, $hasExpression)); continue; } @@ -3334,7 +3528,7 @@ class lessc_parser { break; case '"': case "'": - if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count)) + if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count)) $count += strlen($m[0]) - 1; break; case '//': -- GitLab