: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace Nextend\Framework\Asset\Css\Less;
#[\AllowDynamicProperties]
static protected $nextBlockId = 0; // used to uniquely identify blocks
static protected $precedence = array(
static protected $whitePattern;
static protected $commentMulti;
static protected $commentSingle = "//";
static protected $commentMultiLeft = "/*";
static protected $commentMultiRight = "*/";
// regex string to match any of the operators
static protected $operatorString;
// these properties will supress division unless it's inside parenthases
static protected $supressDivisionProps = array(
protected $blockDirectives = array(
protected $lineDirectives = array("charset");
* if we are in parens we can be more liberal with whitespace around
* operators because it must evaluate to a single value and thus is less
* property1: 10 -5; // is two numbers, 10 and -5
* property2: (10 -5); // should evaluate to 5
protected $inParens = false;
// caches preg escaped literals
static protected $literalCache = array();
public function __construct($lessc, $sourceName = null) {
$this->eatWhiteDefault = true;
// reference to less needed for vPrefix, mPrefix, and parentSelector
$this->sourceName = $sourceName; // name used for error messages
$this->writeComments = false;
if (!self::$operatorString) {
self::$operatorString = '(' . implode('|', array_map(array(
), array_keys(self::$precedence))) . ')';
$commentSingle = LessCompiler::preg_quote(self::$commentSingle);
$commentMultiLeft = LessCompiler::preg_quote(self::$commentMultiLeft);
$commentMultiRight = LessCompiler::preg_quote(self::$commentMultiRight);
self::$commentMulti = $commentMultiLeft . '.*?' . $commentMultiRight;
self::$whitePattern = '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentMulti . ')\s*|\s+/Ais';
public function parse($buffer) {
$this->env = null; // block stack
$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
$this->pushSpecialBlock("root");
$this->eatWhiteDefault = true;
$this->seenComments = array();
// trim whitespace on head
// if (preg_match('/^\s+/', $this->buffer, $m)) {
// $this->line += substr_count($m[0], "\n");
// $this->buffer = ltrim($this->buffer);
$lastCount = $this->count;
while (false !== $this->parseChunk()) ;
if ($this->count != strlen($this->buffer)) $this->throwError();
// TODO report where the block was opened
if (!is_null($this->env->parent)) throw new Exception('parse error: unclosed block');
* Parse a single chunk off the head of the buffer and append it to the
* current parse environment.
* Returns false when the buffer is empty, or when there is an error.
* This function is called repeatedly until the entire document is
* This parser is most similar to a recursive descent parser. Single
* functions represent discrete grammatical rules for the language, and
* they are able to capture the text that represents those rules.
* Consider the function lessc::keyword(). (all parse functions are
* The function takes a single reference argument. When calling the
* function it will attempt to match a keyword on the head of the buffer.
* If it is successful, it will place the keyword in the referenced
* argument, advance the position in the buffer, and return true. If it
* fails then it won't advance the buffer and it will return false.
* All of these parse functions are powered by lessc::match(), which behaves
* the same way, but takes a literal regular expression. Sometimes it is
* more convenient to use match instead of creating a new function.
* Because of the format of the functions, to parse an entire string of
* grammatical rules, you can chain them together using &&.
* But, if some of the rules in the chain succeed before one fails, then
* the buffer position will be left at an invalid state. In order to
* avoid this, lessc::seek() is used to remember and set buffer positions.
* Before parsing a chain, use $s = $this->seek() to remember the current
* position into $s. Then if a chain fails, use $this->seek($s) to
* go back where we started.
protected function parseChunk() {
if (empty($this->buffer)) return false;
if ($this->keyword($key) && $this->assign() && $this->propertyValue($value, $key) && $this->end()) {
// look for special css blocks
if ($this->literal('@', false)) {
if ($this->literal('@media')) {
if (($this->mediaQueryList($mediaQueries) || true) && $this->literal('{')) {
$media = $this->pushSpecialBlock("media");
$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
if ($this->literal("@", false) && $this->keyword($dirName)) {
if ($this->isDirective($dirName, $this->blockDirectives)) {
if (($this->openString("{", $dirValue, null, array(";")) || true) && $this->literal("{")) {
$dir = $this->pushSpecialBlock("directive");
if (isset($dirValue)) $dir->value = $dirValue;
} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
if ($this->propertyValue($dirValue) && $this->end()) {
if ($this->variable($var) && $this->assign() && $this->propertyValue($value) && $this->end()) {
if ($this->import($importValue)) {
$this->append($importValue, $s);
// opening parametric mixin
if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && ($this->guards($guards) || true) && $this->literal('{')) {
$block = $this->pushBlock($this->fixTags(array($tag)));
$block->isVararg = $isVararg;
if (!empty($guards)) $block->guards = $guards;
// opening a simple block
if ($this->tags($tags) && $this->literal('{')) {
$tags = $this->fixTags($tags);
if ($this->literal('}', false)) {
$this->throwError($e->getMessage());
if (is_null($block->type)) {
if (!isset($block->args)) {
foreach ($block->tags as $tag) {
if (!is_string($tag) || $tag[0] != $this->lessc->mPrefix) {
foreach ($block->tags as $tag) {
$this->env->children[$tag][] = $block;
// this is done here so comments aren't bundled into he block that
if ($this->mixinTags($tags) && ($this->argumentValues($argv) || true) && ($this->keyword($suffix) || true) && $this->end()) {
$tags = $this->fixTags($tags);
if ($this->literal(';')) return true;
return false; // got nothing, throw error
protected function isDirective($dirname, $directives) {
// TODO: cache pattern in parser
$pattern = implode("|", array_map(array(
$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
return preg_match($pattern, $dirname);
protected function fixTags($tags) {
// move @ tags out of variable namespace
foreach ($tags as &$tag) {
if ($tag[0] == $this->lessc->vPrefix) $tag[0] = $this->lessc->mPrefix;
protected function expressionList(&$exps) {
while ($this->expression($exp)) {
if (count($values) == 0) return false;
$exps = LessCompiler::compressList($values, ' ');
* Attempt to consume an expression.
* @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
protected function expression(&$out) {
if ($this->value($lhs)) {
$out = $this->expHelper($lhs, 0);
if (!empty($this->env->supressedDivision)) {
unset($this->env->supressedDivision);
if ($this->literal("/") && $this->value($rhs)) {
* recursively parse infix equation with $lhs at precedence $minP
protected function expHelper($lhs, $minP) {
$whiteBefore = isset($this->buffer[$this->count - 1]) && preg_match('/^\\s+$/', $this->buffer[$this->count - 1]);
// If there is whitespace before the operator, then we require
// whitespace after the operator for it to be an expression
$needWhite = $whiteBefore && !$this->inParens;
if ($this->match(self::$operatorString . ($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
foreach (self::$supressDivisionProps as $pattern) {
if (preg_match($pattern, $this->env->currentProperty)) {
$this->env->supressedDivision = true;
$whiteAfter = isset($this->buffer[$this->count - 1]) && preg_match('/^\\s+$/', $this->buffer[$this->count - 1]);
if (!$this->value($rhs)) break;
// peek for next operator to see what to do with rhs
if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
// consume a list of values for a property
public function propertyValue(&$value, $keyName = null) {
if ($keyName !== null) $this->env->currentProperty = $keyName;
while ($this->expressionList($v)) {
if (!$this->literal(',')) break;
if ($keyName !== null) unset($this->env->currentProperty);
if (count($values) == 0) return false;
$value = LessCompiler::compressList($values, ', ');
protected function parenValue(&$out) {
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
$inParens = $this->inParens;
if ($this->literal("(") && ($this->inParens = true) && $this->expression($exp) && $this->literal(")")) {
$this->inParens = $inParens;
$this->inParens = $inParens;
protected function value(&$value) {
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
if ($this->literal("-", false) && (($this->variable($inner) && $inner = array(