From bdb776e835c7874d76e60316a6f46c5e9ecb9813 Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Wed, 28 Feb 2018 19:22:37 +0000 Subject: [PATCH 1/9] Enable use of ampersand (&) as a variable value Added the ability for a value to be of Type::T_SELF for a variable, and therefore to be checked during the compiling of Type::T_ASSIGN children for their existence and replacement with the compiled parent selector the variable resides in. Doesn't have proper error checking yet, and will need further testing --- src/Compiler.php | 101 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 68003b90..71b42009 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -18,6 +18,7 @@ use Leafo\ScssPhp\Exception\CompilerException; use Leafo\ScssPhp\Formatter\OutputBlock; use Leafo\ScssPhp\Node; +use Leafo\ScssPhp\SourceMap\SourceMapGenerator; use Leafo\ScssPhp\Type; use Leafo\ScssPhp\Parser; use Leafo\ScssPhp\Util; @@ -64,6 +65,10 @@ class Compiler const WITH_SUPPORTS = 4; const WITH_ALL = 7; + const SOURCE_MAP_NONE = 0; + const SOURCE_MAP_INLINE = 1; + const SOURCE_MAP_FILE = 2; + /** * @var array */ @@ -120,11 +125,16 @@ class Compiler protected $encoding = null; protected $lineNumberStyle = null; + protected $sourceMap = self::SOURCE_MAP_NONE; + protected $sourceMapOptions = []; + + /** @var string|Formatter */ protected $formatter = 'Leafo\ScssPhp\Formatter\Nested'; protected $rootEnv; protected $rootBlock; + /** @var Environment */ protected $env; protected $scope; protected $storeEnv; @@ -165,9 +175,6 @@ public function __construct() */ public function compile($code, $path = null) { - $locale = setlocale(LC_NUMERIC, 0); - setlocale(LC_NUMERIC, 'C'); - $this->indentLevel = -1; $this->commentsSeen = []; $this->extends = []; @@ -194,10 +201,23 @@ public function compile($code, $path = null) $this->compileRoot($tree); $this->popEnv(); - $out = $this->formatter->format($this->scope); + $sourceMapGenerator = null; + if($this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + } + $out = $this->formatter->format($this->scope, $sourceMapGenerator); + if(!empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMap = $sourceMapGenerator->generateJson(); - setlocale(LC_NUMERIC, $locale); + $sourceMapUrl = null; + if($this->sourceMap == self::SOURCE_MAP_INLINE) { + $sourceMapUrl = sprintf('data:application/json,%s', self::encodeURIComponent($sourceMap)); + } elseif ($this->sourceMap == self::SOURCE_MAP_FILE) { + $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap); + } + $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl); + } return $out; } @@ -279,6 +299,9 @@ protected function makeOutputBlock($type, $selectors = null) $out->parent = $this->scope; $out->selectors = $selectors; $out->depth = $this->env->depth; + $out->sourceName = $this->env->block->sourceName; + $out->sourceLine = $this->env->block->sourceLine; + $out->sourceColumn = $this->env->block->sourceColumn; return $out; } @@ -661,6 +684,7 @@ protected function compileMedia(Block $media) if ($needsWrap) { $wrapped = new Block; + $wrapped->sourceName = $media->sourceName; $wrapped->sourceIndex = $media->sourceIndex; $wrapped->sourceLine = $media->sourceLine; $wrapped->sourceColumn = $media->sourceColumn; @@ -734,6 +758,7 @@ protected function compileAtRoot(Block $block) // wrap inline selector if ($block->selector) { $wrapped = new Block; + $wrapped->sourceName = $block->sourceName; $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; @@ -790,6 +815,7 @@ private function spliceTree($envs, Block $block, $without) } $b = new Block; + $b->sourceName = $e->block->sourceName; $b->sourceIndex = $e->block->sourceIndex; $b->sourceLine = $e->block->sourceLine; $b->sourceColumn = $e->block->sourceColumn; @@ -1075,12 +1101,20 @@ protected function evalSelectors($selectors) if ($parser->parseSelector($buffer, $newSelectors)) { $selectors = array_map([$this, 'evalSelector'], $newSelectors); + } } return $selectors; } + /** + * @param array $sourceMapOptions + */ + public function setSourceMapOptions($sourceMapOptions) { + $this->sourceMapOptions = $sourceMapOptions; + } + /** * Evaluate selector * @@ -1181,7 +1215,7 @@ protected function flattenSelectorSingle($single) /** * Compile selector to string; self(&) should have been replaced by now * - * @param array $selector + * @param string|array $selector * * @return string */ @@ -1203,7 +1237,7 @@ protected function compileSelector($selector) /** * Compile selector part * - * @param arary $piece + * @param array $piece * * @return string */ @@ -1572,10 +1606,19 @@ protected function compileChild($child, OutputBlock $out) list(, $name, $value) = $child; if ($name[0] === Type::T_VARIABLE) { + $flags = isset($child[3]) ? $child[3] : []; $isDefault = in_array('!default', $flags); $isGlobal = in_array('!global', $flags); + if($value[0] === Type::T_SELF): + + if (!empty($out->selectors)) { + $value[2][0] = implode($out->selectors[0][0]); + $value[0] = Type::T_STRING; + } + endif; + if ($isGlobal) { $this->set($name[1], $this->reduce($value), false, $this->rootEnv); break; @@ -1963,7 +2006,7 @@ protected function shouldEval($value) * @param array $value * @param boolean $inExp * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ protected function reduce($value, $inExp = false) { @@ -2238,7 +2281,7 @@ public function normalizeValue($value) * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opAddNumberNumber($left, $right) { @@ -2251,7 +2294,7 @@ protected function opAddNumberNumber($left, $right) * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opMulNumberNumber($left, $right) { @@ -2264,7 +2307,7 @@ protected function opMulNumberNumber($left, $right) * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opSubNumberNumber($left, $right) { @@ -2277,7 +2320,7 @@ protected function opSubNumberNumber($left, $right) * @param array $left * @param array $right * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ protected function opDivNumberNumber($left, $right) { @@ -2294,7 +2337,7 @@ protected function opDivNumberNumber($left, $right) * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opModNumberNumber($left, $right) { @@ -2580,7 +2623,7 @@ protected function opLtNumberNumber($left, $right) * @param array $left * @param array $right * - * @return array + * @return \Leafo\ScssPhp\Node\Number */ protected function opCmpNumberNumber($left, $right) { @@ -2772,6 +2815,10 @@ public function compileValue($value) case Type::T_NULL: return 'null'; + case Type::T_SELF: + + return Type::T_SELF; + default: $this->throwError("unknown value type: $type"); } @@ -2857,6 +2904,7 @@ protected function multiplySelectors(Environment $env) foreach ($env->selectors as $selector) { foreach ($parentSelectors as $parent) { $selectors[] = $this->joinSelectors($parent, $selector); + } } @@ -3031,10 +3079,14 @@ protected function set($name, $value, $shadow = false, Environment $env = null) { $name = $this->normalizeName($name); + + if (! isset($env)) { $env = $this->getStoreEnv(); } + //if($name == "parent") var_dump($env); + if ($shadow) { $this->setRaw($name, $value, $env); } else { @@ -3304,6 +3356,13 @@ public function setLineNumberStyle($lineNumberStyle) $this->lineNumberStyle = $lineNumberStyle; } + /** + * @param int $sourceMap + */ + public function setSourceMap($sourceMap) { + $this->sourceMap = $sourceMap; + } + /** * Register function * @@ -3505,7 +3564,7 @@ protected function fileExists($name) * Call SCSS @function * * @param string $name - * @param array $args + * @param array $argValues * @param array $returnValue * * @return boolean Returns true if returnValue is set; otherwise, false @@ -3777,7 +3836,7 @@ protected function applyArguments($argDef, $argValues) * * @param mixed $value * - * @return array + * @return array|\Leafo\ScssPhp\Node\Number */ private function coerceValue($value) { @@ -3850,7 +3909,8 @@ protected function coerceMap($item) /** * Coerce something to list * - * @param array $item + * @param array $item + * @param string $delim * * @return array */ @@ -5210,6 +5270,8 @@ protected function libVariableExists($args) * Workaround IE7's content counter bug. * * @param array $args + * + * @return array */ protected function libCounter($args) { @@ -5258,4 +5320,9 @@ protected function libInspect($args) return $args[0]; } + + public static function encodeURIComponent($string){ + $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'); + return strtr(rawurlencode($string), $revert); + } } From 6f4b1212c6a84771f839db563e8e981ca3ee3fcc Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Wed, 28 Feb 2018 19:24:15 +0000 Subject: [PATCH 2/9] Enable use of ampersand (&) as a variable value Adds a selfCache() function to check if there's an attempt to use an ampersand (&) as a variable value, and if so defines a Type::T_SELF value to use in compilation. --- src/Parser.php | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 619b7ba8..568d18c9 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -280,7 +280,7 @@ public function parseSelector($buffer, &$out) protected function parseChunk() { $s = $this->seek(); - + // the directives if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') { if ($this->literal('@at-root') && @@ -621,11 +621,14 @@ protected function parseChunk() $this->valueList($value) && $this->end() ) { + + + // check for '!flag' $assignmentFlags = $this->stripAssignmentFlags($value); $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s); - return true; + return true; } $this->seek($s); @@ -708,6 +711,7 @@ protected function pushBlock($selectors, $pos = 0) list($line, $column) = $this->getSourcePosition($pos); $b = new Block; + $b->sourceName = $this->sourceName; $b->sourceLine = $line; $b->sourceColumn = $column; $b->sourceIndex = $this->sourceIndex; @@ -1350,6 +1354,11 @@ protected function value(&$out) return true; } + if ($this->selfCache($out)) { + return true; + } + + if ($this->keyword($keyword)) { if ($keyword === 'null') { $out = [Type::T_NULL]; @@ -1363,6 +1372,27 @@ protected function value(&$out) return false; } + /** + * Parse variable value of "&" + * + * @param array $out + * + * @return boolean + */ + protected function selfCache(&$out) { + + $s = $this->seek(); + + if ($this->literal('&')) { + + $out = [Type::T_SELF, '', [Type::T_SELF]]; + + return true; + } + + return false; + } + /** * Parse parenthesized value * @@ -2468,7 +2498,13 @@ private function getSourcePosition($pos) */ private function saveEncoding() { - if (ini_get('mbstring.func_overload') & 2) { + if (version_compare(PHP_VERSION, '7.2.0') >= 0) { + return; + } + + $iniDirective = 'mbstring' . '.func_overload'; // deprecated in PHP 7.2 + + if (ini_get($iniDirective) & 2) { $this->encoding = mb_internal_encoding(); mb_internal_encoding('iso-8859-1'); From 0dd316e1bd3ac564cb15afa1ef18fd12ef12b1b0 Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Thu, 9 Aug 2018 21:48:38 +0100 Subject: [PATCH 3/9] Various fixes to closer match Sass 3.5 Updates to allow for (square) bracketed lists, selection of self(&) as a value for a variable, and fixes for @at-root declarations that include selectors and/or self(&) selectors in their first child blocks. --- src/Compiler.php | 394 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 302 insertions(+), 92 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 71b42009..fa69056c 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2017 Leaf Corcoran + * @copyright 2012-2018 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -65,9 +65,9 @@ class Compiler const WITH_SUPPORTS = 4; const WITH_ALL = 7; - const SOURCE_MAP_NONE = 0; + const SOURCE_MAP_NONE = 0; const SOURCE_MAP_INLINE = 1; - const SOURCE_MAP_FILE = 2; + const SOURCE_MAP_FILE = 2; /** * @var array @@ -128,13 +128,17 @@ class Compiler protected $sourceMap = self::SOURCE_MAP_NONE; protected $sourceMapOptions = []; - /** @var string|Formatter */ + /** + * @var string|\Leafo\ScssPhp\Formatter + */ protected $formatter = 'Leafo\ScssPhp\Formatter\Nested'; protected $rootEnv; protected $rootBlock; - /** @var Environment */ + /** + * @var \Leafo\ScssPhp\Compiler\Environment + */ protected $env; protected $scope; protected $storeEnv; @@ -202,22 +206,35 @@ public function compile($code, $path = null) $this->popEnv(); $sourceMapGenerator = null; - if($this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { - $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + + if ($this->sourceMap) { + if (is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) { + $sourceMapGenerator = $this->sourceMap; + $this->sourceMap = self::SOURCE_MAP_FILE; + } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions); + } } + $out = $this->formatter->format($this->scope, $sourceMapGenerator); - if(!empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { - $sourceMap = $sourceMapGenerator->generateJson(); + if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) { + $sourceMap = $sourceMapGenerator->generateJson(); $sourceMapUrl = null; - if($this->sourceMap == self::SOURCE_MAP_INLINE) { - $sourceMapUrl = sprintf('data:application/json,%s', self::encodeURIComponent($sourceMap)); - } elseif ($this->sourceMap == self::SOURCE_MAP_FILE) { - $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap); + + switch ($this->sourceMap) { + case self::SOURCE_MAP_INLINE: + $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap)); + break; + + case self::SOURCE_MAP_FILE: + $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap); + break; } $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl); } + return $out; } @@ -293,15 +310,22 @@ protected function pushExtends($target, $origin, $block) protected function makeOutputBlock($type, $selectors = null) { $out = new OutputBlock; - $out->type = $type; - $out->lines = []; - $out->children = []; - $out->parent = $this->scope; - $out->selectors = $selectors; - $out->depth = $this->env->depth; - $out->sourceName = $this->env->block->sourceName; - $out->sourceLine = $this->env->block->sourceLine; - $out->sourceColumn = $this->env->block->sourceColumn; + $out->type = $type; + $out->lines = []; + $out->children = []; + $out->parent = $this->scope; + $out->selectors = $selectors; + $out->depth = $this->env->depth; + + if ($this->env->block instanceof Block) { + $out->sourceName = $this->env->block->sourceName; + $out->sourceLine = $this->env->block->sourceLine; + $out->sourceColumn = $this->env->block->sourceColumn; + } else { + $out->sourceName = null; + $out->sourceLine = null; + $out->sourceColumn = null; + } return $out; } @@ -684,7 +708,7 @@ protected function compileMedia(Block $media) if ($needsWrap) { $wrapped = new Block; - $wrapped->sourceName = $media->sourceName; + $wrapped->sourceName = $media->sourceName; $wrapped->sourceIndex = $media->sourceIndex; $wrapped->sourceLine = $media->sourceLine; $wrapped->sourceColumn = $media->sourceColumn; @@ -751,6 +775,11 @@ protected function compileDirective(Block $block) */ protected function compileAtRoot(Block $block) { + + if (!$block->selector) { + $storeEnv = $this->getStoreEnv(); + } + $env = $this->pushEnv($block); $envs = $this->compactEnv($env); $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE; @@ -762,12 +791,61 @@ protected function compileAtRoot(Block $block) $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; - $wrapped->selectors = $block->selector; + $wrapped->selectors = $this->evalSelectors($block->selector); $wrapped->comments = []; $wrapped->parent = $block; $wrapped->children = $block->children; + $newSelectors = []; + $injectIteration = 0; + + foreach($wrapped->selectors as $key1 => $branch) { + if ($this->checkForSelf($branch)) { + foreach ($this->multiplySelectors($env) as $selectorPart) { + $newSelectors[$injectIteration] = $branch; + foreach($branch as $key2 => $leaf) { + if ($leaf === [static::$selfSelector]) { + $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; + continue; + } + } + ++$injectIteration; + } + } else { + $newSelectors[$injectIteration] = $branch; + ++$injectIteration; + } + } + + $wrapped->selectors = $newSelectors; $block->children = [[Type::T_BLOCK, $wrapped]]; + } else { + + foreach($block->children as $childIndex => $child) { + + $newSelectors = []; + $injectIteration = 0; + + foreach($child[1]->selectors as $key1 => $branch) { + if ($this->checkForSelf($branch)) { + foreach ($this->multiplySelectors($env) as $selectorPart) { + $newSelectors[$injectIteration] = $branch; + foreach($branch as $key2 => $leaf) { + if ($leaf === [static::$selfSelector]) { + $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; + continue; + } + } + ++$injectIteration; + } + } else { + $newSelectors[$injectIteration] = $branch; + ++$injectIteration; + } + } + $block->children[$childIndex][1]->selectors = $newSelectors; + } + } $this->env = $this->filterWithout($envs, $without); @@ -784,6 +862,28 @@ protected function compileAtRoot(Block $block) $this->popEnv(); } + /** + * Determine if a self(&) declaration is part of the selector part + * + * @param array $branch + * + * @return boolean + */ + private function checkForSelf($branch) + { + $hasSelf = false; + array_walk_recursive( + $branch, + function ($value, $key) use (&$hasSelf) { + if ($value === Type::T_SELF) { + $hasSelf = true; + return; + } + } + ); + return $hasSelf; + } + /** * Splice parse tree * @@ -1042,14 +1142,14 @@ protected function compileBlock(Block $block) switch ($this->lineNumberStyle) { case static::LINE_COMMENTS: $annotation->lines[] = '/* line ' . $line - . ($file ? ', ' . $file : '') - . ' */'; + . ($file ? ', ' . $file : '') + . ' */'; break; case static::DEBUG_INFO: $annotation->lines[] = '@media -sass-debug-info{' - . ($file ? 'filename{font-family:"' . $file . '"}' : '') - . 'line{font-family:' . $line . '}}'; + . ($file ? 'filename{font-family:"' . $file . '"}' : '') + . 'line{font-family:' . $line . '}}'; break; } @@ -1101,20 +1201,12 @@ protected function evalSelectors($selectors) if ($parser->parseSelector($buffer, $newSelectors)) { $selectors = array_map([$this, 'evalSelector'], $newSelectors); - } } return $selectors; } - /** - * @param array $sourceMapOptions - */ - public function setSourceMapOptions($sourceMapOptions) { - $this->sourceMapOptions = $sourceMapOptions; - } - /** * Evaluate selector * @@ -1166,6 +1258,22 @@ protected function collapseSelectors($selectors) { $parts = []; + foreach ($selectors as $selectorColumn) { + $output = ''; + $rows = []; + + foreach($selectorColumn as $selectorRow) { + $rows[] = $this->collapseSelectorPart($selectorRow); + } + + $output .= implode(" ", $rows); + $parts[] = $output; + } + + return implode(', ', $parts); + + /* NOTE: Retaining for potential reversion + foreach ($selectors as $selector) { $output = ''; @@ -1180,6 +1288,35 @@ function ($value, $key) use (&$output) { } return implode(', ', $parts); + + */ + } + + /** + * Collapse a part of a selector + * + * @param array $part + * + * @return array + */ + protected function collapseSelectorPart($part) + { + $output = ""; + + array_walk_recursive( + $part, + function ($value, $key) use (&$output) { + if ($value === Type::T_SELF) { + + $output .= "&"; + + } else { + $output .= $value; + } + } + ); + + return $output; } /** @@ -1606,18 +1743,31 @@ protected function compileChild($child, OutputBlock $out) list(, $name, $value) = $child; if ($name[0] === Type::T_VARIABLE) { - $flags = isset($child[3]) ? $child[3] : []; $isDefault = in_array('!default', $flags); $isGlobal = in_array('!global', $flags); - if($value[0] === Type::T_SELF): - - if (!empty($out->selectors)) { - $value[2][0] = implode($out->selectors[0][0]); - $value[0] = Type::T_STRING; + if ($value[0] === Type::T_SELF) { + $env = $this->env; + + if ($env !== null) { + $selfSelectors = array(); + foreach ($this->multiplySelectors($env) as $selectorPart) { + $selfSelectors[] = $this->collapseSelectors([$selectorPart]); + } + } + + $child[2][0] = $value[0] = Type::T_LIST; + $child[2][1] = $value[1] = ","; + $temp = array(); + + foreach($selfSelectors as $singleSelector) { + $temp[] = array(Type::T_KEYWORD,trim($singleSelector)); } - endif; + + $child[2][2] = $value[2] = $temp; + } + if ($isGlobal) { $this->set($name[1], $this->reduce($value), false, $this->rootEnv); @@ -1772,9 +1922,18 @@ protected function compileChild($child, OutputBlock $out) list(, $for) = $child; $start = $this->reduce($for->start, true); + $end = $this->reduce($for->end, true); + + if (! ($start[2] == $end[2] || $end->unitless())) { + $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr()); + + break; + } + + $unit = $start[2]; $start = $start[1]; - $end = $this->reduce($for->end, true); - $end = $end[1]; + $end = $end[1]; + $d = $start < $end ? 1 : -1; for (;;) { @@ -1784,7 +1943,7 @@ protected function compileChild($child, OutputBlock $out) break; } - $this->set($for->var, new Node\Number($start, '')); + $this->set($for->var, new Node\Number($start, $unit)); $start += $d; $ret = $this->compileChildren($for->children, $out); @@ -1874,7 +2033,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_MIXIN_CONTENT: $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) - ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); + ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); if (! $content) { $content = new \stdClass(); @@ -1969,7 +2128,7 @@ protected function isTruthy($value) * * @param string $value * - * @return bool + * @return boolean */ protected function isImmediateRelationshipCombinator($value) { @@ -2685,7 +2844,9 @@ public function compileValue($value) $b = round($b); if (count($value) === 5 && $value[4] !== 1) { // rgba - return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $value[4] . ')'; + $a = new Node\Number($value[4], ''); + + return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $a . ')'; } $h = sprintf('#%02x%02x%02x', $r, $g, $b); @@ -2765,6 +2926,23 @@ public function compileValue($value) // raw parse node list(, $exp) = $value; + if ($value[1][0] == Type::T_SELF) { + $env = $this->env; + + if ($env === null) return false; + $selfSelectors = array(); + foreach ($this->multiplySelectors($env) as $selectorPart) { + $selfSelectors[] = $this->collapseSelectors([$selectorPart]); + } + + $temp = array(); + foreach($selfSelectors as $singleSelector) { + $temp[] = array(Type::T_STRING,"'",[trim($singleSelector)]); + } + $exp = [Type::T_LIST,",",$temp]; + + } + // strip quotes if it's a string $reduced = $this->reduce($exp); @@ -2815,10 +2993,6 @@ public function compileValue($value) case Type::T_NULL: return 'null'; - case Type::T_SELF: - - return Type::T_SELF; - default: $this->throwError("unknown value type: $type"); } @@ -2904,7 +3078,6 @@ protected function multiplySelectors(Environment $env) foreach ($env->selectors as $selector) { foreach ($parentSelectors as $parent) { $selectors[] = $this->joinSelectors($parent, $selector); - } } @@ -2915,7 +3088,7 @@ protected function multiplySelectors(Environment $env) } /** - * Join selectors; looks for & to replace, or append parent before child + * Join selectors; looks for self(&) to replace, or append parent before child * * @param array $parent * @param array $child @@ -3079,14 +3252,10 @@ protected function set($name, $value, $shadow = false, Environment $env = null) { $name = $this->normalizeName($name); - - if (! isset($env)) { $env = $this->getStoreEnv(); } - //if($name == "parent") var_dump($env); - if ($shadow) { $this->setRaw($name, $value, $env); } else { @@ -3175,7 +3344,8 @@ public function get($name, $shouldThrow = true, Environment $env = null) continue; } - $env = $this->rootEnv; + //$env = $this->rootEnv; + $env = $env->parent; continue; } @@ -3357,12 +3527,29 @@ public function setLineNumberStyle($lineNumberStyle) } /** - * @param int $sourceMap + * Enable/disable source maps + * + * @api + * + * @param integer $sourceMap */ - public function setSourceMap($sourceMap) { + public function setSourceMap($sourceMap) + { $this->sourceMap = $sourceMap; } + /** + * Set source map options + * + * @api + * + * @param array $sourceMapOptions + */ + public function setSourceMapOptions($sourceMapOptions) + { + $this->sourceMapOptions = $sourceMapOptions; + } + /** * Register function * @@ -3461,7 +3648,7 @@ public function findImport($url) if ($this->fileExists($file = $full . '.scss') || ($hasExtension && $this->fileExists($file = $full)) - ) { + ) { return $file; } } @@ -3629,7 +3816,7 @@ protected function callNativeFunction($name, $args, &$returnValue) return false; } - list($sorted, $kwargs) = $this->sortArgs($prototype, $args); + @list($sorted, $kwargs) = $this->sortArgs($prototype, $args); if ($name !== 'if' && $name !== 'call') { foreach ($sorted as &$val) { @@ -3688,7 +3875,7 @@ protected function sortArgs($prototype, $args) $key = $key[1]; if (empty($key)) { - $posArgs[] = $value; + $posArgs[] = empty($arg[2]) ? $value : $arg; } else { $keyArgs[$key] = $value; } @@ -4240,20 +4427,38 @@ protected function libCall($args, $kwargs) { $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true))); - $args = array_map( - function ($a) { - return [null, $a, false]; - }, - $args - ); + $posArgs = []; + + foreach ($args as $arg) { + if (empty($arg[0])) { + if ($arg[2] === true) { + $tmp = $this->reduce($arg[1]); + + if ($tmp[0] === Type::T_LIST) { + foreach ($tmp[2] as $item) { + $posArgs[] = [null, $item, false]; + } + } else { + $posArgs[] = [null, $tmp, true]; + } + + continue; + } + + $posArgs[] = [null, $this->reduce($arg), false]; + continue; + } + + $posArgs[] = [null, $arg, false]; + } if (count($kwargs)) { foreach ($kwargs as $key => $value) { - $args[] = [[Type::T_VARIABLE, $key], $value, false]; + $posArgs[] = [[Type::T_VARIABLE, $key], $value, false]; } } - return $this->reduce([Type::T_FUNCTION_CALL, $name, $args]); + return $this->reduce([Type::T_FUNCTION_CALL, $name, $posArgs]); } protected static $libIf = ['condition', 'if-true', 'if-false']; @@ -4314,7 +4519,7 @@ protected function libRgb($args) protected function libRgba($args) { if ($color = $this->coerceColor($args[0])) { - $num = ! isset($args[1]) ? $args[3] : $args[1]; + $num = isset($args[3]) ? $args[3] : $args[1]; $alpha = $this->assertNumber($num); $color[4] = $alpha; @@ -4509,7 +4714,7 @@ protected function libMix($args) ]; if ($firstAlpha != 1.0 || $secondAlpha != 1.0) { - $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1); + $new[] = $firstAlpha * $weight + $secondAlpha * (1 - $weight); } return $this->fixColor($new); @@ -4732,36 +4937,32 @@ protected function libPercentage($args) protected function libRound($args) { $num = $args[0]; - $num[1] = round($num[1]); - return $num; + return new Node\Number(round($num[1]), $num[2]); } protected static $libFloor = ['value']; protected function libFloor($args) { $num = $args[0]; - $num[1] = floor($num[1]); - return $num; + return new Node\Number(floor($num[1]), $num[2]); } protected static $libCeil = ['value']; protected function libCeil($args) { $num = $args[0]; - $num[1] = ceil($num[1]); - return $num; + return new Node\Number(ceil($num[1]), $num[2]); } protected static $libAbs = ['value']; protected function libAbs($args) { $num = $args[0]; - $num[1] = abs($num[1]); - return $num; + return new Node\Number(abs($num[1]), $num[2]); } protected function libMin($args) @@ -4816,7 +5017,7 @@ protected function getNormalizedNumbers($args) if (null === $unit) { $unit = $number[2]; $originalUnit = $item->unitStr(); - } elseif ($unit !== $number[2]) { + } elseif ($number[1] && $unit !== $number[2]) { $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr()); break; } @@ -4963,7 +5164,21 @@ protected function libMapMerge($args) $map1 = $this->assertMap($args[0]); $map2 = $this->assertMap($args[1]); - return [Type::T_MAP, array_merge($map1[1], $map2[1]), array_merge($map1[2], $map2[2])]; + foreach ($map2[1] as $i2 => $key2) { + $key = $this->compileStringContent($this->coerceString($key2)); + + foreach ($map1[1] as $i1 => $key1) { + if ($key === $this->compileStringContent($this->coerceString($key1))) { + $map1[2][$i1] = $map2[2][$i2]; + continue 2; + } + } + + $map1[1][] = $map2[1][$i2]; + $map1[2][] = $map2[2][$i2]; + } + + return $map1; } protected static $libKeywords = ['args']; @@ -5189,7 +5404,7 @@ protected function libToLowerCase($args) $string = $this->coerceString($args[0]); $stringContent = $this->compileStringContent($string); - $string[2] = [mb_strtolower($stringContent)]; + $string[2] = [function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)]; return $string; } @@ -5200,7 +5415,7 @@ protected function libToUpperCase($args) $string = $this->coerceString($args[0]); $stringContent = $this->compileStringContent($string); - $string[2] = [mb_strtoupper($stringContent)]; + $string[2] = [function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)]; return $string; } @@ -5320,9 +5535,4 @@ protected function libInspect($args) return $args[0]; } - - public static function encodeURIComponent($string){ - $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'); - return strtr(rawurlencode($string), $revert); - } } From a8553aa54118ca4d34325041e100d29c38d69303 Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Thu, 9 Aug 2018 21:49:03 +0100 Subject: [PATCH 4/9] Various fixes to closer match Sass 3.5 Updates to allow for (square) bracketed lists, selection of self(&) as a value for a variable, and fixes for @at-root declarations that include selectors and/or self(&) selectors in their first child blocks. --- src/Parser.php | 149 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 51 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 568d18c9..2d3aae18 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2,7 +2,7 @@ /** * SCSSPHP * - * @copyright 2012-2017 Leaf Corcoran + * @copyright 2012-2018 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * @@ -104,8 +104,8 @@ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8') * * @return string */ - public function getSourceName() - { + public function getSourceName() + { return $this->sourceName; } @@ -280,7 +280,7 @@ public function parseSelector($buffer, &$out) protected function parseChunk() { $s = $this->seek(); - + // the directives if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') { if ($this->literal('@at-root') && @@ -367,7 +367,7 @@ protected function parseChunk() if ($this->literal('@import') && $this->url($importPath) && $this->end() - ) { + ) { $this->append([Type::T_IMPORT, $importPath], $s); return true; @@ -621,14 +621,11 @@ protected function parseChunk() $this->valueList($value) && $this->end() ) { - - - // check for '!flag' $assignmentFlags = $this->stripAssignmentFlags($value); $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s); - return true; + return true; } $this->seek($s); @@ -711,7 +708,7 @@ protected function pushBlock($selectors, $pos = 0) list($line, $column) = $this->getSourcePosition($pos); $b = new Block; - $b->sourceName = $this->sourceName; + $b->sourceName = $this->sourceName; $b->sourceLine = $line; $b->sourceColumn = $column; $b->sourceIndex = $this->sourceIndex; @@ -1072,7 +1069,7 @@ protected function mediaExpression(&$out) $this->expression($feature) && ($this->literal(':') && $this->expression($value) || true) && $this->literal(')') - ) { + ) { $out = [Type::T_MEDIA_EXPRESSION, $feature]; if ($value) { @@ -1214,23 +1211,30 @@ protected function expression(&$out) { $s = $this->seek(); - if ($this->literal('(')) { - if ($this->literal(')')) { - $out = [Type::T_LIST, '', []]; - - return true; + if ($this->literal('&',false)) { + if ($this->literal('&',false)) { + $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); + return false; } + //Assign parent selectors here + $out = Compiler::$selfSelector; + return true; + } + + $this->seek($s); + + if ($this->literal('(')) { + if ($this->parenExpression($out, $s)) { - if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) { return true; } - $this->seek($s); + } - if ($this->map($out)) { + if ($this->literal('[')) { + if ($this->parenExpression($out, $s, "]")) { return true; } - $this->seek($s); } @@ -1243,6 +1247,36 @@ protected function expression(&$out) return false; } + /** + * Parse expression specifically checking for lists in parenthesis or brackets + * + * @param array $out + * @param integer $s + * @param string $closingParen + * + * @return boolean + */ + protected function parenExpression(&$out, $s, $closingParen = ")") + { + + if ($this->literal($closingParen)) { + $out = [Type::T_LIST, '', []]; + return true; + } + + if ($this->valueList($out) && $this->literal($closingParen) && $out[0] === Type::T_LIST) { + return true; + } + + $this->seek($s); + + if ($this->map($out)) { + return true; + } + + return false; + } + /** * Parse left-hand side of subexpression * @@ -1305,6 +1339,21 @@ protected function value(&$out) { $s = $this->seek(); + if ($this->literal('url(') && $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)) { + $len = strspn($this->buffer, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=', $this->count); + + $this->count += $len; + + if ($this->literal(')')) { + $content = substr($this->buffer, $s, $this->count - $s); + $out = [Type::T_KEYWORD, $content]; + + return true; + } + } + + $this->seek($s); + if ($this->literal('not', false) && $this->whitespace() && $this->value($inner)) { $out = [Type::T_UNARY, 'not', $inner, $this->inParens]; @@ -1332,8 +1381,8 @@ protected function value(&$out) // negation if ($this->literal('-', false) && ($this->variable($inner) || - $this->unit($inner) || - $this->parenValue($inner)) + $this->unit($inner) || + $this->parenValue($inner)) ) { $out = [Type::T_UNARY, '-', $inner, $this->inParens]; @@ -1354,11 +1403,6 @@ protected function value(&$out) return true; } - if ($this->selfCache($out)) { - return true; - } - - if ($this->keyword($keyword)) { if ($keyword === 'null') { $out = [Type::T_NULL]; @@ -1372,27 +1416,6 @@ protected function value(&$out) return false; } - /** - * Parse variable value of "&" - * - * @param array $out - * - * @return boolean - */ - protected function selfCache(&$out) { - - $s = $this->seek(); - - if ($this->literal('&')) { - - $out = [Type::T_SELF, '', [Type::T_SELF]]; - - return true; - } - - return false; - } - /** * Parse parenthesized value * @@ -1426,6 +1449,26 @@ protected function parenValue(&$out) $this->inParens = $inParens; $this->seek($s); + if ($this->literal('[')) { + if ($this->literal(']')) { + $out = [Type::T_LIST, '', []]; + + return true; + } + + $this->inParens = true; + + if ($this->expression($exp) && $this->literal(']')) { + $out = $exp; + $this->inParens = $inParens; + + return true; + } + } + + $this->inParens = $inParens; + $this->seek($s); + return false; } @@ -1473,7 +1516,7 @@ protected function func(&$func) if ($this->keyword($name, false) && $this->literal('(') - ) { + ) { if ($name === 'alpha' && $this->argumentList($args)) { $func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]]; @@ -2102,6 +2145,10 @@ protected function selectorSingle(&$out) // self if ($this->literal('&', false)) { + if ($this->literal('&',false)) { + $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); + return false; + } $parts[] = Compiler::$selfSelector; continue; } @@ -2180,8 +2227,8 @@ protected function selectorSingle(&$out) // attribute selector if ($this->literal('[') && - ($this->openString(']', $str, '[') || true) && - $this->literal(']') + ($this->openString(']', $str, '[') || true) && + $this->literal(']') ) { $parts[] = '['; From b286ce0fe73d7fbb76714d747c69c0b40318bbab Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Thu, 9 Aug 2018 21:54:26 +0100 Subject: [PATCH 5/9] Update formatting errors --- src/Parser.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 2d3aae18..4fe11817 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -105,7 +105,7 @@ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8') * @return string */ public function getSourceName() - { + { return $this->sourceName; } @@ -367,7 +367,7 @@ protected function parseChunk() if ($this->literal('@import') && $this->url($importPath) && $this->end() - ) { + ) { $this->append([Type::T_IMPORT, $importPath], $s); return true; @@ -1069,7 +1069,7 @@ protected function mediaExpression(&$out) $this->expression($feature) && ($this->literal(':') && $this->expression($value) || true) && $this->literal(')') - ) { + ) { $out = [Type::T_MEDIA_EXPRESSION, $feature]; if ($value) { @@ -1381,8 +1381,8 @@ protected function value(&$out) // negation if ($this->literal('-', false) && ($this->variable($inner) || - $this->unit($inner) || - $this->parenValue($inner)) + $this->unit($inner) || + $this->parenValue($inner)) ) { $out = [Type::T_UNARY, '-', $inner, $this->inParens]; @@ -1516,7 +1516,7 @@ protected function func(&$func) if ($this->keyword($name, false) && $this->literal('(') - ) { + ) { if ($name === 'alpha' && $this->argumentList($args)) { $func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]]; From d652477e4cb1178bac8cd9334c85157efb1378ef Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Sun, 12 Aug 2018 23:10:56 +0100 Subject: [PATCH 6/9] Improvements for @at-root and psuedo-selector :not --- src/Compiler.php | 133 ++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 70 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index fa69056c..4d37bbf7 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -779,7 +779,7 @@ protected function compileAtRoot(Block $block) if (!$block->selector) { $storeEnv = $this->getStoreEnv(); } - + $env = $this->pushEnv($block); $envs = $this->compactEnv($env); $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE; @@ -791,59 +791,17 @@ protected function compileAtRoot(Block $block) $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; - $wrapped->selectors = $this->evalSelectors($block->selector); + $wrapped->selectors = $this->preParseSelectors($this->evalSelectors($block->selector), $env); $wrapped->comments = []; $wrapped->parent = $block; $wrapped->children = $block->children; - - $newSelectors = []; - $injectIteration = 0; - - foreach($wrapped->selectors as $key1 => $branch) { - if ($this->checkForSelf($branch)) { - foreach ($this->multiplySelectors($env) as $selectorPart) { - $newSelectors[$injectIteration] = $branch; - foreach($branch as $key2 => $leaf) { - if ($leaf === [static::$selfSelector]) { - $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; - continue; - } - } - ++$injectIteration; - } - } else { - $newSelectors[$injectIteration] = $branch; - ++$injectIteration; - } - } - - $wrapped->selectors = $newSelectors; - $block->children = [[Type::T_BLOCK, $wrapped]]; + $block->children = [[Type::T_BLOCK, $wrapped]]; } else { foreach($block->children as $childIndex => $child) { - - $newSelectors = []; - $injectIteration = 0; - - foreach($child[1]->selectors as $key1 => $branch) { - if ($this->checkForSelf($branch)) { - foreach ($this->multiplySelectors($env) as $selectorPart) { - $newSelectors[$injectIteration] = $branch; - foreach($branch as $key2 => $leaf) { - if ($leaf === [static::$selfSelector]) { - $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; - continue; - } - } - ++$injectIteration; - } - } else { - $newSelectors[$injectIteration] = $branch; - ++$injectIteration; - } + if ($child[0] === Type::T_BLOCK) { + $block->children[$childIndex][1]->selectors = $this->preParseSelectors($child[1]->selectors, $env); } - $block->children[$childIndex][1]->selectors = $newSelectors; } } @@ -862,6 +820,39 @@ protected function compileAtRoot(Block $block) $this->popEnv(); } + /** + * Reconstitute selectors + * + * @param array $selectors + * + * @return array + */ + private function preParseSelectors($selectors, $env) + { + $newSelectors = []; + $injectIteration = 0; + + foreach($selectors as $key1 => $branch) { + if ($this->checkForSelf($branch)) { + foreach ($this->multiplySelectors($env) as $selectorPart) { + $newSelectors[$injectIteration] = $branch; + foreach($branch as $key2 => $leaf) { + if ($leaf === [static::$selfSelector]) { + $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; + continue; + } + } + ++$injectIteration; + } + } else { + $newSelectors[$injectIteration] = $branch; + ++$injectIteration; + } + } + + return $newSelectors; + } + /** * Determine if a self(&) declaration is part of the selector part * @@ -875,13 +866,13 @@ private function checkForSelf($branch) array_walk_recursive( $branch, function ($value, $key) use (&$hasSelf) { - if ($value === Type::T_SELF) { - $hasSelf = true; - return; + if ($value === Type::T_SELF) { + $hasSelf = true; + return; + } } - } ); - return $hasSelf; + return $hasSelf; } /** @@ -1142,14 +1133,14 @@ protected function compileBlock(Block $block) switch ($this->lineNumberStyle) { case static::LINE_COMMENTS: $annotation->lines[] = '/* line ' . $line - . ($file ? ', ' . $file : '') - . ' */'; + . ($file ? ', ' . $file : '') + . ' */'; break; case static::DEBUG_INFO: $annotation->lines[] = '@media -sass-debug-info{' - . ($file ? 'filename{font-family:"' . $file . '"}' : '') - . 'line{font-family:' . $line . '}}'; + . ($file ? 'filename{font-family:"' . $file . '"}' : '') + . 'line{font-family:' . $line . '}}'; break; } @@ -1226,12 +1217,13 @@ protected function evalSelector($selector) * * @return array */ + protected function evalSelectorPart($part) { foreach ($part as &$p) { if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) { $p = $this->compileValue($p); - + // force re-evaluation if (strpos($p, '&') !== false || strpos($p, ',') !== false) { $this->shouldEvaluate = true; @@ -1306,14 +1298,14 @@ protected function collapseSelectorPart($part) array_walk_recursive( $part, function ($value, $key) use (&$output) { - if ($value === Type::T_SELF) { - - $output .= "&"; - - } else { - $output .= $value; - } - } + if ($value === Type::T_SELF) { + + $output .= "&"; + + } else { + $output .= $value; + } + } ); return $output; @@ -1380,6 +1372,7 @@ protected function compileSelector($selector) */ protected function compileSelectorPart($piece) { + foreach ($piece as &$p) { if (! is_array($p)) { continue; @@ -1414,7 +1407,7 @@ protected function hasSelectorPlaceholder($selector) foreach ($selector as $parts) { foreach ($parts as $part) { - if (strlen($part) && '%' === $part[0]) { + if (is_string($part) && strlen($part) && '%' === $part[0]) { return true; } } @@ -2928,19 +2921,19 @@ public function compileValue($value) if ($value[1][0] == Type::T_SELF) { $env = $this->env; - + if ($env === null) return false; $selfSelectors = array(); foreach ($this->multiplySelectors($env) as $selectorPart) { $selfSelectors[] = $this->collapseSelectors([$selectorPart]); } - + $temp = array(); foreach($selfSelectors as $singleSelector) { $temp[] = array(Type::T_STRING,"'",[trim($singleSelector)]); } $exp = [Type::T_LIST,",",$temp]; - + } // strip quotes if it's a string @@ -3648,7 +3641,7 @@ public function findImport($url) if ($this->fileExists($file = $full . '.scss') || ($hasExtension && $this->fileExists($file = $full)) - ) { + ) { return $file; } } From 5ab5ae324cf007bf878ccbdbb360b890ea5790f1 Mon Sep 17 00:00:00 2001 From: niaccurshi Date: Sun, 12 Aug 2018 23:12:48 +0100 Subject: [PATCH 7/9] :not() improvements + properties outside of blocks --- src/Parser.php | 87 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/src/Parser.php b/src/Parser.php index 4fe11817..dbde6eac 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -60,6 +60,7 @@ class Parser private $count; private $env; private $inParens; + private $inNot; private $eatWhiteDefault; private $buffer; private $utf8; @@ -104,7 +105,7 @@ public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8') * * @return string */ - public function getSourceName() + public function getSourceName() { return $this->sourceName; } @@ -607,6 +608,12 @@ protected function parseChunk() $this->valueList($value) && $this->end() ) { + + if ($this->env->parent == NULL || ($this->env->type == Type::T_AT_ROOT)) { + $this->seek($s); + $this->throwParseError('property declarations must be inside a block'); + } + $name = [Type::T_STRING, '', [$name]]; $this->append([Type::T_ASSIGN, $name, $value], $s); @@ -1220,24 +1227,17 @@ protected function expression(&$out) $out = Compiler::$selfSelector; return true; } - + $this->seek($s); if ($this->literal('(')) { if ($this->parenExpression($out, $s)) { - + return true; } $this->seek($s); } - if ($this->literal('[')) { - if ($this->parenExpression($out, $s, "]")) { - return true; - } - $this->seek($s); - } - if ($this->value($lhs)) { $out = $this->expHelper($lhs, 0); @@ -1258,10 +1258,10 @@ protected function expression(&$out) */ protected function parenExpression(&$out, $s, $closingParen = ")") { - + if ($this->literal($closingParen)) { $out = [Type::T_LIST, '', []]; - return true; + return true; } if ($this->valueList($out) && $this->literal($closingParen) && $out[0] === Type::T_LIST) { @@ -1274,6 +1274,7 @@ protected function parenExpression(&$out, $s, $closingParen = ")") return true; } + return false; } @@ -1391,7 +1392,7 @@ protected function value(&$out) $this->seek($s); - if ($this->parenValue($out) || + if ($this->parenOrBracketValue($out) || $this->interpolation($out) || $this->variable($out) || $this->color($out) || @@ -1449,6 +1450,22 @@ protected function parenValue(&$out) $this->inParens = $inParens; $this->seek($s); + return false; + + } + + /** + * Parse parenthesized value + * + * @param array $out + * + * @return boolean + */ + protected function parenOrBracketValue(&$out) + { + + if ($this->parenValue($out)) return true; + if ($this->literal('[')) { if ($this->literal(']')) { $out = [Type::T_LIST, '', []]; @@ -2060,6 +2077,7 @@ protected function selectors(&$out) while ($this->literal(',')) { ; // ignore extra } + $this->match('\s+', $m); } if (count($selectors) === 0) { @@ -2087,6 +2105,7 @@ protected function selector(&$out) for (;;) { if ($this->match('[>+~]+', $m)) { $selector[] = [$m[0]]; + $this->match('\s+', $m); continue; } @@ -2205,6 +2224,44 @@ protected function selectorSingle(&$out) $ss = $this->seek(); + + // negation selector + if (in_array('not',$nameParts)) { + + if ($this->inNot === true) { + $this->inNot = false; + $this->throwParseError('psuedo-selector :not() may not contain another :not() psuedo-selector'); + } + + $this->inNot = true; + + if ($this->literal('(') && + //$this->selectors($selectors) && + $this->openString(')', $str, '(') && + $this->literal(')') + ) { + + $parts[] = '('; + + $strParts = explode("&",$str[2][0]); + $strPartsLength = count($strParts); + foreach($strParts as $index => $strTok) { + $parts[] = $strTok; + if ($index + 1 < $strPartsLength) { + $parts[] = Compiler::$selfSelector; + } + } + + $parts[] = ')'; + $this->inNot = false; + continue; + } + + $this->seek($ss); + } + + + if ($this->literal('(') && ($this->openString(')', $str, '(') || true) && $this->literal(')') @@ -2227,8 +2284,8 @@ protected function selectorSingle(&$out) // attribute selector if ($this->literal('[') && - ($this->openString(']', $str, '[') || true) && - $this->literal(']') + ($this->openString(']', $str, '[') || true) && + $this->literal(']') ) { $parts[] = '['; From 47b15fd07386b8a107e29506c4df48dc73f6fe46 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Thu, 30 Aug 2018 15:35:42 +0200 Subject: [PATCH 8/9] Revert "Merge branch 'm2'" This reverts commit c338ca22b36eff8fce42c03b7a3ae9bc90c4debc. --- src/Compiler.php | 168 ++--------------------------------------------- src/Parser.php | 146 ++++------------------------------------ 2 files changed, 21 insertions(+), 293 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 4d37bbf7..637f1c1c 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -775,11 +775,6 @@ protected function compileDirective(Block $block) */ protected function compileAtRoot(Block $block) { - - if (!$block->selector) { - $storeEnv = $this->getStoreEnv(); - } - $env = $this->pushEnv($block); $envs = $this->compactEnv($env); $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE; @@ -791,19 +786,12 @@ protected function compileAtRoot(Block $block) $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; - $wrapped->selectors = $this->preParseSelectors($this->evalSelectors($block->selector), $env); + $wrapped->selectors = $block->selector; $wrapped->comments = []; $wrapped->parent = $block; $wrapped->children = $block->children; - $block->children = [[Type::T_BLOCK, $wrapped]]; - } else { - - foreach($block->children as $childIndex => $child) { - if ($child[0] === Type::T_BLOCK) { - $block->children[$childIndex][1]->selectors = $this->preParseSelectors($child[1]->selectors, $env); - } - } + $block->children = [[Type::T_BLOCK, $wrapped]]; } $this->env = $this->filterWithout($envs, $without); @@ -820,61 +808,6 @@ protected function compileAtRoot(Block $block) $this->popEnv(); } - /** - * Reconstitute selectors - * - * @param array $selectors - * - * @return array - */ - private function preParseSelectors($selectors, $env) - { - $newSelectors = []; - $injectIteration = 0; - - foreach($selectors as $key1 => $branch) { - if ($this->checkForSelf($branch)) { - foreach ($this->multiplySelectors($env) as $selectorPart) { - $newSelectors[$injectIteration] = $branch; - foreach($branch as $key2 => $leaf) { - if ($leaf === [static::$selfSelector]) { - $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; - continue; - } - } - ++$injectIteration; - } - } else { - $newSelectors[$injectIteration] = $branch; - ++$injectIteration; - } - } - - return $newSelectors; - } - - /** - * Determine if a self(&) declaration is part of the selector part - * - * @param array $branch - * - * @return boolean - */ - private function checkForSelf($branch) - { - $hasSelf = false; - array_walk_recursive( - $branch, - function ($value, $key) use (&$hasSelf) { - if ($value === Type::T_SELF) { - $hasSelf = true; - return; - } - } - ); - return $hasSelf; - } - /** * Splice parse tree * @@ -1217,13 +1150,12 @@ protected function evalSelector($selector) * * @return array */ - protected function evalSelectorPart($part) { foreach ($part as &$p) { if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) { $p = $this->compileValue($p); - + // force re-evaluation if (strpos($p, '&') !== false || strpos($p, ',') !== false) { $this->shouldEvaluate = true; @@ -1250,22 +1182,6 @@ protected function collapseSelectors($selectors) { $parts = []; - foreach ($selectors as $selectorColumn) { - $output = ''; - $rows = []; - - foreach($selectorColumn as $selectorRow) { - $rows[] = $this->collapseSelectorPart($selectorRow); - } - - $output .= implode(" ", $rows); - $parts[] = $output; - } - - return implode(', ', $parts); - - /* NOTE: Retaining for potential reversion - foreach ($selectors as $selector) { $output = ''; @@ -1280,35 +1196,6 @@ function ($value, $key) use (&$output) { } return implode(', ', $parts); - - */ - } - - /** - * Collapse a part of a selector - * - * @param array $part - * - * @return array - */ - protected function collapseSelectorPart($part) - { - $output = ""; - - array_walk_recursive( - $part, - function ($value, $key) use (&$output) { - if ($value === Type::T_SELF) { - - $output .= "&"; - - } else { - $output .= $value; - } - } - ); - - return $output; } /** @@ -1372,7 +1259,6 @@ protected function compileSelector($selector) */ protected function compileSelectorPart($piece) { - foreach ($piece as &$p) { if (! is_array($p)) { continue; @@ -1407,7 +1293,7 @@ protected function hasSelectorPlaceholder($selector) foreach ($selector as $parts) { foreach ($parts as $part) { - if (is_string($part) && strlen($part) && '%' === $part[0]) { + if (strlen($part) && '%' === $part[0]) { return true; } } @@ -1740,28 +1626,6 @@ protected function compileChild($child, OutputBlock $out) $isDefault = in_array('!default', $flags); $isGlobal = in_array('!global', $flags); - if ($value[0] === Type::T_SELF) { - $env = $this->env; - - if ($env !== null) { - $selfSelectors = array(); - foreach ($this->multiplySelectors($env) as $selectorPart) { - $selfSelectors[] = $this->collapseSelectors([$selectorPart]); - } - } - - $child[2][0] = $value[0] = Type::T_LIST; - $child[2][1] = $value[1] = ","; - $temp = array(); - - foreach($selfSelectors as $singleSelector) { - $temp[] = array(Type::T_KEYWORD,trim($singleSelector)); - } - - $child[2][2] = $value[2] = $temp; - } - - if ($isGlobal) { $this->set($name[1], $this->reduce($value), false, $this->rootEnv); break; @@ -2026,7 +1890,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_MIXIN_CONTENT: $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) - ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); + ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); if (! $content) { $content = new \stdClass(); @@ -2919,23 +2783,6 @@ public function compileValue($value) // raw parse node list(, $exp) = $value; - if ($value[1][0] == Type::T_SELF) { - $env = $this->env; - - if ($env === null) return false; - $selfSelectors = array(); - foreach ($this->multiplySelectors($env) as $selectorPart) { - $selfSelectors[] = $this->collapseSelectors([$selectorPart]); - } - - $temp = array(); - foreach($selfSelectors as $singleSelector) { - $temp[] = array(Type::T_STRING,"'",[trim($singleSelector)]); - } - $exp = [Type::T_LIST,",",$temp]; - - } - // strip quotes if it's a string $reduced = $this->reduce($exp); @@ -3081,7 +2928,7 @@ protected function multiplySelectors(Environment $env) } /** - * Join selectors; looks for self(&) to replace, or append parent before child + * Join selectors; looks for & to replace, or append parent before child * * @param array $parent * @param array $child @@ -3337,8 +3184,7 @@ public function get($name, $shouldThrow = true, Environment $env = null) continue; } - //$env = $this->rootEnv; - $env = $env->parent; + $env = $this->rootEnv; continue; } diff --git a/src/Parser.php b/src/Parser.php index 8012eed3..748d38ae 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -60,7 +60,6 @@ class Parser private $count; private $env; private $inParens; - private $inNot; private $eatWhiteDefault; private $buffer; private $utf8; @@ -608,12 +607,6 @@ protected function parseChunk() $this->valueList($value) && $this->end() ) { - - if ($this->env->parent == NULL || ($this->env->type == Type::T_AT_ROOT)) { - $this->seek($s); - $this->throwParseError('property declarations must be inside a block'); - } - $name = [Type::T_STRING, '', [$name]]; $this->append([Type::T_ASSIGN, $name, $value], $s); @@ -1218,63 +1211,32 @@ protected function expression(&$out) { $s = $this->seek(); - if ($this->literal('&',false)) { - if ($this->literal('&',false)) { - $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); - return false; - } - //Assign parent selectors here - $out = Compiler::$selfSelector; - return true; - } - - $this->seek($s); - if ($this->literal('(')) { - if ($this->parenExpression($out, $s)) { - + if ($this->literal(')')) { + $out = [Type::T_LIST, '', []]; + return true; } - $this->seek($s); - } - if ($this->value($lhs)) { - $out = $this->expHelper($lhs, 0); - - return true; - } + if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) { + return true; + } - return false; - } + $this->seek($s); - /** - * Parse expression specifically checking for lists in parenthesis or brackets - * - * @param array $out - * @param integer $s - * @param string $closingParen - * - * @return boolean - */ - protected function parenExpression(&$out, $s, $closingParen = ")") - { - - if ($this->literal($closingParen)) { - $out = [Type::T_LIST, '', []]; - return true; - } + if ($this->map($out)) { + return true; + } - if ($this->valueList($out) && $this->literal($closingParen) && $out[0] === Type::T_LIST) { - return true; + $this->seek($s); } - $this->seek($s); + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); - if ($this->map($out)) { return true; } - return false; } @@ -1392,7 +1354,7 @@ protected function value(&$out) $this->seek($s); - if ($this->parenOrBracketValue($out) || + if ($this->parenValue($out) || $this->interpolation($out) || $this->variable($out) || $this->color($out) || @@ -1450,42 +1412,6 @@ protected function parenValue(&$out) $this->inParens = $inParens; $this->seek($s); - return false; - - } - - /** - * Parse parenthesized value - * - * @param array $out - * - * @return boolean - */ - protected function parenOrBracketValue(&$out) - { - - if ($this->parenValue($out)) return true; - - if ($this->literal('[')) { - if ($this->literal(']')) { - $out = [Type::T_LIST, '', []]; - - return true; - } - - $this->inParens = true; - - if ($this->expression($exp) && $this->literal(']')) { - $out = $exp; - $this->inParens = $inParens; - - return true; - } - } - - $this->inParens = $inParens; - $this->seek($s); - return false; } @@ -2081,7 +2007,6 @@ protected function selectors(&$out) while ($this->literal(',')) { ; // ignore extra } - $this->match('\s+', $m); } if (count($selectors) === 0) { @@ -2109,7 +2034,6 @@ protected function selector(&$out) for (;;) { if ($this->match('[>+~]+', $m)) { $selector[] = [$m[0]]; - $this->match('\s+', $m); continue; } @@ -2168,10 +2092,6 @@ protected function selectorSingle(&$out) // self if ($this->literal('&', false)) { - if ($this->literal('&',false)) { - $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); - return false; - } $parts[] = Compiler::$selfSelector; continue; } @@ -2228,44 +2148,6 @@ protected function selectorSingle(&$out) $ss = $this->seek(); - - // negation selector - if (in_array('not',$nameParts)) { - - if ($this->inNot === true) { - $this->inNot = false; - $this->throwParseError('psuedo-selector :not() may not contain another :not() psuedo-selector'); - } - - $this->inNot = true; - - if ($this->literal('(') && - //$this->selectors($selectors) && - $this->openString(')', $str, '(') && - $this->literal(')') - ) { - - $parts[] = '('; - - $strParts = explode("&",$str[2][0]); - $strPartsLength = count($strParts); - foreach($strParts as $index => $strTok) { - $parts[] = $strTok; - if ($index + 1 < $strPartsLength) { - $parts[] = Compiler::$selfSelector; - } - } - - $parts[] = ')'; - $this->inNot = false; - continue; - } - - $this->seek($ss); - } - - - if ($this->literal('(') && ($this->openString(')', $str, '(') || true) && $this->literal(')') From e526d6f1bd1422b96d57def983d146442a0de91c Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Thu, 30 Aug 2018 15:35:42 +0200 Subject: [PATCH 9/9] Revert "Revert "Merge branch 'm2'"" This reverts commit 47b15fd07386b8a107e29506c4df48dc73f6fe46. --- src/Compiler.php | 168 +++++++++++++++++++++++++++++++++++++++++++++-- src/Parser.php | 146 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 293 insertions(+), 21 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 637f1c1c..4d37bbf7 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -775,6 +775,11 @@ protected function compileDirective(Block $block) */ protected function compileAtRoot(Block $block) { + + if (!$block->selector) { + $storeEnv = $this->getStoreEnv(); + } + $env = $this->pushEnv($block); $envs = $this->compactEnv($env); $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE; @@ -786,12 +791,19 @@ protected function compileAtRoot(Block $block) $wrapped->sourceIndex = $block->sourceIndex; $wrapped->sourceLine = $block->sourceLine; $wrapped->sourceColumn = $block->sourceColumn; - $wrapped->selectors = $block->selector; + $wrapped->selectors = $this->preParseSelectors($this->evalSelectors($block->selector), $env); $wrapped->comments = []; $wrapped->parent = $block; $wrapped->children = $block->children; + $block->children = [[Type::T_BLOCK, $wrapped]]; + } else { + + foreach($block->children as $childIndex => $child) { + if ($child[0] === Type::T_BLOCK) { + $block->children[$childIndex][1]->selectors = $this->preParseSelectors($child[1]->selectors, $env); + } + } - $block->children = [[Type::T_BLOCK, $wrapped]]; } $this->env = $this->filterWithout($envs, $without); @@ -808,6 +820,61 @@ protected function compileAtRoot(Block $block) $this->popEnv(); } + /** + * Reconstitute selectors + * + * @param array $selectors + * + * @return array + */ + private function preParseSelectors($selectors, $env) + { + $newSelectors = []; + $injectIteration = 0; + + foreach($selectors as $key1 => $branch) { + if ($this->checkForSelf($branch)) { + foreach ($this->multiplySelectors($env) as $selectorPart) { + $newSelectors[$injectIteration] = $branch; + foreach($branch as $key2 => $leaf) { + if ($leaf === [static::$selfSelector]) { + $newSelectors[$injectIteration][$key2] = [$this->collapseSelectors([$selectorPart])]; + continue; + } + } + ++$injectIteration; + } + } else { + $newSelectors[$injectIteration] = $branch; + ++$injectIteration; + } + } + + return $newSelectors; + } + + /** + * Determine if a self(&) declaration is part of the selector part + * + * @param array $branch + * + * @return boolean + */ + private function checkForSelf($branch) + { + $hasSelf = false; + array_walk_recursive( + $branch, + function ($value, $key) use (&$hasSelf) { + if ($value === Type::T_SELF) { + $hasSelf = true; + return; + } + } + ); + return $hasSelf; + } + /** * Splice parse tree * @@ -1150,12 +1217,13 @@ protected function evalSelector($selector) * * @return array */ + protected function evalSelectorPart($part) { foreach ($part as &$p) { if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) { $p = $this->compileValue($p); - + // force re-evaluation if (strpos($p, '&') !== false || strpos($p, ',') !== false) { $this->shouldEvaluate = true; @@ -1182,6 +1250,22 @@ protected function collapseSelectors($selectors) { $parts = []; + foreach ($selectors as $selectorColumn) { + $output = ''; + $rows = []; + + foreach($selectorColumn as $selectorRow) { + $rows[] = $this->collapseSelectorPart($selectorRow); + } + + $output .= implode(" ", $rows); + $parts[] = $output; + } + + return implode(', ', $parts); + + /* NOTE: Retaining for potential reversion + foreach ($selectors as $selector) { $output = ''; @@ -1196,6 +1280,35 @@ function ($value, $key) use (&$output) { } return implode(', ', $parts); + + */ + } + + /** + * Collapse a part of a selector + * + * @param array $part + * + * @return array + */ + protected function collapseSelectorPart($part) + { + $output = ""; + + array_walk_recursive( + $part, + function ($value, $key) use (&$output) { + if ($value === Type::T_SELF) { + + $output .= "&"; + + } else { + $output .= $value; + } + } + ); + + return $output; } /** @@ -1259,6 +1372,7 @@ protected function compileSelector($selector) */ protected function compileSelectorPart($piece) { + foreach ($piece as &$p) { if (! is_array($p)) { continue; @@ -1293,7 +1407,7 @@ protected function hasSelectorPlaceholder($selector) foreach ($selector as $parts) { foreach ($parts as $part) { - if (strlen($part) && '%' === $part[0]) { + if (is_string($part) && strlen($part) && '%' === $part[0]) { return true; } } @@ -1626,6 +1740,28 @@ protected function compileChild($child, OutputBlock $out) $isDefault = in_array('!default', $flags); $isGlobal = in_array('!global', $flags); + if ($value[0] === Type::T_SELF) { + $env = $this->env; + + if ($env !== null) { + $selfSelectors = array(); + foreach ($this->multiplySelectors($env) as $selectorPart) { + $selfSelectors[] = $this->collapseSelectors([$selectorPart]); + } + } + + $child[2][0] = $value[0] = Type::T_LIST; + $child[2][1] = $value[1] = ","; + $temp = array(); + + foreach($selfSelectors as $singleSelector) { + $temp[] = array(Type::T_KEYWORD,trim($singleSelector)); + } + + $child[2][2] = $value[2] = $temp; + } + + if ($isGlobal) { $this->set($name[1], $this->reduce($value), false, $this->rootEnv); break; @@ -1890,7 +2026,7 @@ protected function compileChild($child, OutputBlock $out) case Type::T_MIXIN_CONTENT: $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) - ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); + ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); if (! $content) { $content = new \stdClass(); @@ -2783,6 +2919,23 @@ public function compileValue($value) // raw parse node list(, $exp) = $value; + if ($value[1][0] == Type::T_SELF) { + $env = $this->env; + + if ($env === null) return false; + $selfSelectors = array(); + foreach ($this->multiplySelectors($env) as $selectorPart) { + $selfSelectors[] = $this->collapseSelectors([$selectorPart]); + } + + $temp = array(); + foreach($selfSelectors as $singleSelector) { + $temp[] = array(Type::T_STRING,"'",[trim($singleSelector)]); + } + $exp = [Type::T_LIST,",",$temp]; + + } + // strip quotes if it's a string $reduced = $this->reduce($exp); @@ -2928,7 +3081,7 @@ protected function multiplySelectors(Environment $env) } /** - * Join selectors; looks for & to replace, or append parent before child + * Join selectors; looks for self(&) to replace, or append parent before child * * @param array $parent * @param array $child @@ -3184,7 +3337,8 @@ public function get($name, $shouldThrow = true, Environment $env = null) continue; } - $env = $this->rootEnv; + //$env = $this->rootEnv; + $env = $env->parent; continue; } diff --git a/src/Parser.php b/src/Parser.php index 748d38ae..8012eed3 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -60,6 +60,7 @@ class Parser private $count; private $env; private $inParens; + private $inNot; private $eatWhiteDefault; private $buffer; private $utf8; @@ -607,6 +608,12 @@ protected function parseChunk() $this->valueList($value) && $this->end() ) { + + if ($this->env->parent == NULL || ($this->env->type == Type::T_AT_ROOT)) { + $this->seek($s); + $this->throwParseError('property declarations must be inside a block'); + } + $name = [Type::T_STRING, '', [$name]]; $this->append([Type::T_ASSIGN, $name, $value], $s); @@ -1211,32 +1218,63 @@ protected function expression(&$out) { $s = $this->seek(); - if ($this->literal('(')) { - if ($this->literal(')')) { - $out = [Type::T_LIST, '', []]; - - return true; + if ($this->literal('&',false)) { + if ($this->literal('&',false)) { + $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); + return false; } + //Assign parent selectors here + $out = Compiler::$selfSelector; + return true; + } + + $this->seek($s); - if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) { + if ($this->literal('(')) { + if ($this->parenExpression($out, $s)) { + return true; } - $this->seek($s); + } - if ($this->map($out)) { - return true; - } + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); - $this->seek($s); + return true; } - if ($this->value($lhs)) { - $out = $this->expHelper($lhs, 0); + return false; + } + + /** + * Parse expression specifically checking for lists in parenthesis or brackets + * + * @param array $out + * @param integer $s + * @param string $closingParen + * + * @return boolean + */ + protected function parenExpression(&$out, $s, $closingParen = ")") + { + + if ($this->literal($closingParen)) { + $out = [Type::T_LIST, '', []]; + return true; + } + if ($this->valueList($out) && $this->literal($closingParen) && $out[0] === Type::T_LIST) { return true; } + $this->seek($s); + + if ($this->map($out)) { + return true; + } + + return false; } @@ -1354,7 +1392,7 @@ protected function value(&$out) $this->seek($s); - if ($this->parenValue($out) || + if ($this->parenOrBracketValue($out) || $this->interpolation($out) || $this->variable($out) || $this->color($out) || @@ -1412,6 +1450,42 @@ protected function parenValue(&$out) $this->inParens = $inParens; $this->seek($s); + return false; + + } + + /** + * Parse parenthesized value + * + * @param array $out + * + * @return boolean + */ + protected function parenOrBracketValue(&$out) + { + + if ($this->parenValue($out)) return true; + + if ($this->literal('[')) { + if ($this->literal(']')) { + $out = [Type::T_LIST, '', []]; + + return true; + } + + $this->inParens = true; + + if ($this->expression($exp) && $this->literal(']')) { + $out = $exp; + $this->inParens = $inParens; + + return true; + } + } + + $this->inParens = $inParens; + $this->seek($s); + return false; } @@ -2007,6 +2081,7 @@ protected function selectors(&$out) while ($this->literal(',')) { ; // ignore extra } + $this->match('\s+', $m); } if (count($selectors) === 0) { @@ -2034,6 +2109,7 @@ protected function selector(&$out) for (;;) { if ($this->match('[>+~]+', $m)) { $selector[] = [$m[0]]; + $this->match('\s+', $m); continue; } @@ -2092,6 +2168,10 @@ protected function selectorSingle(&$out) // self if ($this->literal('&', false)) { + if ($this->literal('&',false)) { + $this->throwParseError('"&" may only be used at the beginning of a compound selector.'); + return false; + } $parts[] = Compiler::$selfSelector; continue; } @@ -2148,6 +2228,44 @@ protected function selectorSingle(&$out) $ss = $this->seek(); + + // negation selector + if (in_array('not',$nameParts)) { + + if ($this->inNot === true) { + $this->inNot = false; + $this->throwParseError('psuedo-selector :not() may not contain another :not() psuedo-selector'); + } + + $this->inNot = true; + + if ($this->literal('(') && + //$this->selectors($selectors) && + $this->openString(')', $str, '(') && + $this->literal(')') + ) { + + $parts[] = '('; + + $strParts = explode("&",$str[2][0]); + $strPartsLength = count($strParts); + foreach($strParts as $index => $strTok) { + $parts[] = $strTok; + if ($index + 1 < $strPartsLength) { + $parts[] = Compiler::$selfSelector; + } + } + + $parts[] = ')'; + $this->inNot = false; + continue; + } + + $this->seek($ss); + } + + + if ($this->literal('(') && ($this->openString(')', $str, '(') || true) && $this->literal(')')