<?php
// @codingStandardsIgnoreFile
/**
- * lessphp v0.3.8
+ * lessphp v0.4.0
* http://leafo.net/lessphp
*
* LESS css compiler, adapted from http://lesscss.org
* handling things like indentation.
*/
class lessc {
- static public $VERSION = "v0.3.8";
+ static public $VERSION = "v0.4.0";
static protected $TRUE = array("keyword", "true");
static protected $FALSE = array("keyword", "false");
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;
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));
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) {
$parts[] = "($q[1])";
}
break;
+ case "variable":
+ $parts[] = $this->compileValue($this->reduce($q));
+ break;
}
}
foreach ($selectors as $s) {
if (is_array($s)) {
list(, $value) = $s;
- $out[] = $this->compileValue($this->reduce($value));
+ $out[] = trim($this->compileValue($this->reduce($value)));
} else {
$out[] = $s;
}
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)) {
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") {
}
}
- $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;
} 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;
}
}
}
// 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;
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
$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) {
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
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");
}
foreach ($mixins as $mixin) {
+ if ($mixin === $block && !$orderedArgs) {
+ continue;
+ }
+
$haveScope = false;
if (isset($mixin->parent->scope)) {
$haveScope = true;
if (isset($mixin->args)) {
$haveArgs = true;
$this->pushEnv();
- $this->zipSetArgs($mixin->args, $args);
+ $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
}
$oldParent = $mixin->parent;
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);
}
}
+ 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");
}
return $this->toBool($value[0] == "number" && $value[2] == "em");
}
+ protected function lib_isrem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "rem");
+ }
+
protected function lib_rgbahex($color) {
$color = $this->coerceColor($color);
if (is_null($color))
return array("number", round($value), $arg[2]);
}
+ protected function lib_unit($arg) {
+ if ($arg[0] == "list") {
+ list($number, $newUnit) = $arg[2];
+ return array("number", $this->assertNumber($number),
+ $this->compileValue($this->lib_e($newUnit)));
+ } else {
+ return array("number", $this->assertNumber($arg), "");
+ }
+ }
+
/**
* Helper function to get arguments for color manipulation functions.
* takes a list that contains a color like thing and a percentage
}
// mixes two colors by weight
- // mix(@color1, @color2, @weight);
+ // mix(@color1, @color2, [@weight: 50%]);
// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
protected function lib_mix($args) {
- if ($args[0] != "list" || count($args[2]) < 3)
+ if ($args[0] != "list" || count($args[2]) < 2)
$this->throwError("mix expects (color1, color2, weight)");
- list($first, $second, $weight) = $args[2];
+ list($first, $second) = $args[2];
$first = $this->assertColor($first);
$second = $this->assertColor($second);
$first_a = $this->lib_alpha($first);
$second_a = $this->lib_alpha($second);
- $weight = $weight[1] / 100.0;
+
+ if (isset($args[2][2])) {
+ $weight = $args[2][2][1] / 100.0;
+ } else {
+ $weight = 0.5;
+ }
$w = $weight * 2 - 1;
$a = $first_a - $second_a;
return $this->fixColor($new);
}
+ protected function lib_contrast($args) {
+ if ($args[0] != 'list' || count($args[2]) < 3) {
+ return array(array('color', 0, 0, 0), 0);
+ }
+
+ list($inputColor, $darkColor, $lightColor) = $args[2];
+
+ $inputColor = $this->assertColor($inputColor);
+ $darkColor = $this->assertColor($darkColor);
+ $lightColor = $this->assertColor($lightColor);
+ $hsl = $this->toHSL($inputColor);
+
+ if ($hsl[3] > 50) {
+ return $darkColor;
+ }
+
+ return $lightColor;
+ }
+
protected function assertColor($value, $error = "expected color value") {
$color = $this->coerceColor($value);
if (is_null($color)) $this->throwError($error);
$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;
* Expects H to be in range of 0 to 360, S and L in 0 to 100
*/
protected function toRGB($color) {
- if ($color == 'color') return $color;
+ if ($color[0] == 'color') return $color;
$H = $color[1] / 360;
$S = $color[2] / 100;
protected function reduce($value, $forExpression = false) {
switch ($value[0]) {
+ case "interpolate":
+ $reduced = $this->reduce($value[1]);
+ $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;
case "variable":
$key = $value[1];
if (is_array($key)) {
case 'keyword':
$name = $value[1];
if (isset(self::$cssColors[$name])) {
- list($r, $g, $b) = explode(',', self::$cssColors[$name]);
- return array('color', $r, $g, $b);
+ $rgba = explode(',', self::$cssColors[$name]);
+
+ if(isset($rgba[3]))
+ return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
+
+ return array('color', $rgba[0], $rgba[1], $rgba[2]);
}
return null;
}
return $this->fixColor($out);
}
+ function lib_red($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for red()');
+ }
+
+ return $color[1];
+ }
+
+ function lib_green($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for green()');
+ }
+
+ return $color[2];
+ }
+
+ function lib_blue($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for blue()');
+ }
+
+ return $color[3];
+ }
+
+
// operator on two numbers
protected function op_number_number($op, $left, $right) {
$unit = empty($left[2]) ? $right[2] : $left[2];
$this->importDir = (array)$this->importDir;
$this->importDir[] = $pi['dirname'].'/';
- $this->allParsedFiles = array();
$this->addParsedFile($fname);
$out = $this->compile(file_get_contents($fname), $fname);
'teal' => '0,128,128',
'thistle' => '216,191,216',
'tomato' => '255,99,71',
+ 'transparent' => '0,0,0,0',
'turquoise' => '64,224,208',
'violet' => '238,130,238',
'wheat' => '245,222,179',
static protected $supressDivisionProps =
array('/border-radius$/i', '/^font$/i');
- protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document");
+ protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
protected $lineDirectives = array("charset");
/**
// mixin
if ($this->mixinTags($tags) &&
- ($this->argumentValues($argv) || true) &&
+ ($this->argumentDef($argv, $isVararg) || true) &&
($this->keyword($suffix) || true) && $this->end())
{
$tags = $this->fixTags($tags);
$out = array("mediaExp", $feature);
if ($value) $out[] = $value;
return true;
+ } elseif ($this->variable($variable)) {
+ $out = array('variable', $variable);
+ return true;
}
$this->seek($s);
continue;
}
- if (in_array($tok, $rejectStrs)) {
- $count = null;
+ if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
break;
}
-
$content[] = $tok;
$this->count+= strlen($tok);
}
$s = $this->seek();
if ($this->literal("@{") &&
- $this->keyword($var) &&
+ $this->openString("}", $interp, null, array("'", '"', ";")) &&
$this->literal("}", false))
{
- $out = array("variable", $this->lessc->vPrefix . $var);
+ $out = array("interpolate", $interp);
$this->eatWhiteDefault = $oldWhite;
if ($this->eatWhiteDefault) $this->whitespace();
return true;
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) {
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(')')) {
}
// 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);
return false;
}
- // a single tag
+ // a space separated list of selectors
protected function tag(&$tag, $simple = false) {
if ($simple)
- $chars = '^,:;{}\][>\(\) "\'';
+ $chars = '^@,:;{}\][>\(\) "\'';
else
- $chars = '^,;{}["\'';
+ $chars = '^@,;{}["\'';
- if (!$simple && $this->tagExpression($tag)) {
- return true;
- }
+ $s = $this->seek();
- $tag = '';
- while ($this->tagBracket($first)) $tag .= $first;
+ $hasExpression = false;
+ $parts = array();
+ while ($this->tagBracket($parts, $hasExpression));
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
while (true) {
if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
- $tag .= $m[1];
+ $parts[] = $m[1];
if ($simple) break;
- while ($this->tagBracket($brack)) $tag .= $brack;
+ while ($this->tagBracket($parts, $hasExpression));
continue;
- } elseif ($this->unit($unit)) { // for keyframes
- $tag .= $unit[1] . $unit[2];
+ }
+
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
+ if ($this->interpolation($interp)) {
+ $hasExpression = true;
+ $interp[2] = true; // don't unescape
+ $parts[] = $interp;
+ continue;
+ }
+
+ if ($this->literal("@")) {
+ $parts[] = "@";
+ continue;
+ }
+ }
+
+ if ($this->unit($unit)) { // for keyframes
+ $parts[] = $unit[1];
+ $parts[] = $unit[2];
continue;
}
+
break;
}
+ $this->eatWhiteDefault = $oldWhite;
+ if (!$parts) {
+ $this->seek($s);
+ return false;
+ }
- $tag = trim($tag);
- if ($tag == '') return false;
+ if ($hasExpression) {
+ $tag = array("exp", array("string", "", $parts));
+ } else {
+ $tag = trim(implode($parts));
+ }
+ $this->whitespace();
return true;
}
protected function end() {
if ($this->literal(';')) {
return true;
- } elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') {
+ } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
// if there is end of file or a closing block next then we don't need a ;
return true;
}
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 '//':