: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// make something string like into a string
protected function coerceString($value) {
// turn list of length 1 into value type
protected function flattenList($value) {
if ($value[0] == "list" && count($value[2]) == 1) {
return $this->flattenList($value[2][0]);
protected function toBool($a) {
if ($a) return self::$TRUE; else return self::$FALSE;
// evaluate an expression
protected function evaluate($exp) {
list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
$left = $this->reduce($left, true);
$right = $this->reduce($right, true);
if ($leftColor = $this->coerceColor($left)) {
if ($rightColor = $this->coerceColor($right)) {
// operators that work on all types
return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
return $this->toBool($this->eq($left, $right));
if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
$fname = "op_{$ltype}_{$rtype}";
$out = $this->$fname($op, $left, $right);
if (!is_null($out)) return $out;
// make the expression look it did before being parsed
if ($whiteBefore) $paddedOp = " " . $paddedOp;
if ($whiteAfter) $paddedOp .= " ";
protected function stringConcatenate($left, $right) {
if ($strLeft = $this->coerceString($left)) {
if ($right[0] == "string") {
if ($strRight = $this->coerceString($right)) {
array_unshift($strRight[2], $left);
// make sure a color's components don't go out of bounds
protected function fixColor($c) {
foreach (range(1, 3) as $i) {
if ($c[$i] < 0) $c[$i] = 0;
if ($c[$i] > 255) $c[$i] = 255;
protected function op_number_color($op, $lft, $rgt) {
if ($op == '+' || $op == '*') {
return $this->op_color_number($op, $rgt, $lft);
protected function op_color_number($op, $lft, $rgt) {
if ($rgt[0] == '%') $rgt[1] /= 100;
return $this->op_color_color($op, $lft, array_fill(1, count($lft) - 1, $rgt[1]));
protected function op_color_color($op, $left, $right) {
$max = count($left) > count($right) ? count($left) : count($right);
foreach (range(1, $max - 1) as $i) {
$lval = isset($left[$i]) ? $left[$i] : 0;
$rval = isset($right[$i]) ? $right[$i] : 0;
if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
$this->throwError('evaluate error: color op number failed on op ' . $op);
return $this->fixColor($out);
function lib_red($color) {
$color = $this->coerceColor($color);
$this->throwError('color expected for red()');
function lib_green($color) {
$color = $this->coerceColor($color);
$this->throwError('color expected for green()');
function lib_blue($color) {
$color = $this->coerceColor($color);
$this->throwError('color expected for blue()');
// operator on two numbers
protected function op_number_number($op, $left, $right) {
$unit = empty($left[2]) ? $right[2] : $left[2];
$value = $left[1] + $right[1];
$value = $left[1] * $right[1];
$value = $left[1] - $right[1];
$value = $left[1] % $right[1];
if ($right[1] == 0) $this->throwError('parse error: divide by zero');
$value = $left[1] / $right[1];
return $this->toBool($left[1] < $right[1]);
return $this->toBool($left[1] > $right[1]);
return $this->toBool($left[1] >= $right[1]);
return $this->toBool($left[1] <= $right[1]);
$this->throwError('parse error: unknown number operator: ' . $op);
/* environment functions */
protected function makeOutputBlock($type, $selectors = null) {
$b->selectors = $selectors;
$b->parent = $this->scope;
// the state of execution
protected function pushEnv($block = null) {
// pop something off the stack
protected function popEnv() {
$this->env = $this->env->parent;
// set something in the current env
protected function set($name, $value) {
$this->env->store[$name] = $value;
// get the highest occurrence entry for a name
protected function get($name, $default = null) {
$isArguments = $name == $this->vPrefix . 'arguments';
if ($isArguments && isset($current->arguments)) {
if (isset($current->store[$name])) return $current->store[$name]; else {
$current = isset($current->storeParent) ? $current->storeParent : $current->parent;
// inject array of unparsed strings into environment as variables
protected function injectVariables($args) {
$parser = new LessParser($this, __METHOD__);
foreach ($args as $name => $strValue) {
if ($name[0] != '@') $name = '@' . $name;
$parser->buffer = (string)$strValue;
if (!$parser->propertyValue($value)) {
throw new Exception("failed to parse passed in variable $name: $strValue");
$this->set($name, $value);
* Initialize any static state, can initialize parser for a file
public function __construct($fname = null) {
// used for deprecated parse method
$this->_parseFile = $fname;
public function compile($string, $name = null) {
$locale = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, "C");
$this->parser = $this->makeParser($name);
$root = $this->parser->parse($string);
$this->formatter = $this->newFormatter();
if (!empty($this->registeredVars)) {
$this->injectVariables($this->registeredVars);
$this->sourceParser = $this->parser; // used for error messages
$this->compileBlock($root);
$out = $this->formatter->block($this->scope);
setlocale(LC_NUMERIC, $locale);
public function compileFile($fname, $outFname = null) {
if (!is_readable($fname)) {
throw new Exception('load error: failed to find ' . $fname);
$oldImport = $this->importDir;
$this->importDir = (array)$this->importDir;
$this->importDir[] = $pi['dirname'] . '/';
$this->allParsedFiles = array();
$this->addParsedFile($fname);
$out = $this->compile(file_get_contents($fname), $fname);
$this->importDir = $oldImport;
if ($outFname !== null) {
return file_put_contents($outFname, $out);
// compile only if changed input has changed or output doesn't exist
public function checkedCompile($in, $out) {
if (!is_file($out) || filemtime($in) > filemtime($out)) {
$this->compileFile($in, $out);
* Execute lessphp on a .less file or a lessphp cache structure
* The lessphp cache structure contains information about a specific
* less file having been parsed. It can be used as a hint for future
* calls to determine whether or not a rebuild is required.
* The cache structure contains two important keys that may be used
* compiled: The final compiled CSS
* updated: The time (in seconds) the CSS was last compiled
* The cache structure is a plain-ol' PHP associative array and can
* be serialized and unserialized without a hitch.
* @param bool $force Force rebuild?
* @return array lessphp cache structure
public function cachedCompile($in, $force = false) {
} elseif (is_array($in) and isset($in['root'])) {
if ($force or !isset($in['files'])) {
// If we are forcing a recompile or if for some reason the
// structure does not contain any file information we should
// specify the root to trigger a rebuild.
} elseif (isset($in['files']) and is_array($in['files'])) {
foreach ($in['files'] as $fname => $ftime) {
if (!file_exists($fname) or filemtime($fname) > $ftime) {
// One of the files we knew about previously has changed
// so we should look at our incoming root again.
// TODO: Throw an exception? We got neither a string nor something
// that looks like a compatible lessphp cache structure.
// If we have a root value which means we should rebuild.
$out['compiled'] = $this->compileFile($root);
$out['files'] = $this->allParsedFiles();
$out['updated'] = time();
// No changes, pass back the structure
// we were given initially.
// parse and compile buffer
public function parse($str = null, $initialVariables = null) {
$initialVariables = $str;
$oldVars = $this->registeredVars;
if ($initialVariables !== null) {
$this->setVariables($initialVariables);
if (empty($this->_parseFile)) {
throw new Exception("nothing to parse");
$out = $this->compileFile($this->_parseFile);
$out = $this->compile($str);
$this->registeredVars = $oldVars;
protected function makeParser($name) {
$parser = new LessParser($this, $name);
$parser->writeComments = $this->preserveComments;
public function setFormatter($name) {
$this->formatterName = $name;