Skip to content
Snippets Groups Projects
Commit 60ce17ac authored by Andreas Gohr's avatar Andreas Gohr
Browse files

updated phpless to 0.4.0

parent 4fd8e66e
No related branches found
No related tags found
No related merge requests found
<?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 '//':
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment