vendor/scssphp/scssphp/src/Compiler.php line 5650

Open in your IDE?
  1. <?php
  2. /**
  3.  * SCSSPHP
  4.  *
  5.  * @copyright 2012-2020 Leaf Corcoran
  6.  *
  7.  * @license http://opensource.org/licenses/MIT MIT
  8.  *
  9.  * @link http://scssphp.github.io/scssphp
  10.  */
  11. namespace ScssPhp\ScssPhp;
  12. use ScssPhp\ScssPhp\Base\Range;
  13. use ScssPhp\ScssPhp\Compiler\CachedResult;
  14. use ScssPhp\ScssPhp\Compiler\Environment;
  15. use ScssPhp\ScssPhp\Exception\CompilerException;
  16. use ScssPhp\ScssPhp\Exception\ParserException;
  17. use ScssPhp\ScssPhp\Exception\SassException;
  18. use ScssPhp\ScssPhp\Exception\SassScriptException;
  19. use ScssPhp\ScssPhp\Formatter\Compressed;
  20. use ScssPhp\ScssPhp\Formatter\Expanded;
  21. use ScssPhp\ScssPhp\Formatter\OutputBlock;
  22. use ScssPhp\ScssPhp\Logger\LoggerInterface;
  23. use ScssPhp\ScssPhp\Logger\StreamLogger;
  24. use ScssPhp\ScssPhp\Node\Number;
  25. use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
  26. use ScssPhp\ScssPhp\Util\Path;
  27. /**
  28.  * The scss compiler and parser.
  29.  *
  30.  * Converting SCSS to CSS is a three stage process. The incoming file is parsed
  31.  * by `Parser` into a syntax tree, then it is compiled into another tree
  32.  * representing the CSS structure by `Compiler`. The CSS tree is fed into a
  33.  * formatter, like `Formatter` which then outputs CSS as a string.
  34.  *
  35.  * During the first compile, all values are *reduced*, which means that their
  36.  * types are brought to the lowest form before being dump as strings. This
  37.  * handles math equations, variable dereferences, and the like.
  38.  *
  39.  * The `compile` function of `Compiler` is the entry point.
  40.  *
  41.  * In summary:
  42.  *
  43.  * The `Compiler` class creates an instance of the parser, feeds it SCSS code,
  44.  * then transforms the resulting tree to a CSS tree. This class also holds the
  45.  * evaluation context, such as all available mixins and variables at any given
  46.  * time.
  47.  *
  48.  * The `Parser` class is only concerned with parsing its input.
  49.  *
  50.  * The `Formatter` takes a CSS tree, and dumps it to a formatted string,
  51.  * handling things like indentation.
  52.  */
  53. /**
  54.  * SCSS compiler
  55.  *
  56.  * @author Leaf Corcoran <leafot@gmail.com>
  57.  *
  58.  * @final Extending the Compiler is deprecated
  59.  */
  60. class Compiler
  61. {
  62.     /**
  63.      * @deprecated
  64.      */
  65.     const LINE_COMMENTS 1;
  66.     /**
  67.      * @deprecated
  68.      */
  69.     const DEBUG_INFO    2;
  70.     /**
  71.      * @deprecated
  72.      */
  73.     const WITH_RULE     1;
  74.     /**
  75.      * @deprecated
  76.      */
  77.     const WITH_MEDIA    2;
  78.     /**
  79.      * @deprecated
  80.      */
  81.     const WITH_SUPPORTS 4;
  82.     /**
  83.      * @deprecated
  84.      */
  85.     const WITH_ALL      7;
  86.     const SOURCE_MAP_NONE   0;
  87.     const SOURCE_MAP_INLINE 1;
  88.     const SOURCE_MAP_FILE   2;
  89.     /**
  90.      * @var array<string, string>
  91.      */
  92.     protected static $operatorNames = [
  93.         '+'   => 'add',
  94.         '-'   => 'sub',
  95.         '*'   => 'mul',
  96.         '/'   => 'div',
  97.         '%'   => 'mod',
  98.         '=='  => 'eq',
  99.         '!='  => 'neq',
  100.         '<'   => 'lt',
  101.         '>'   => 'gt',
  102.         '<='  => 'lte',
  103.         '>='  => 'gte',
  104.     ];
  105.     /**
  106.      * @var array<string, string>
  107.      */
  108.     protected static $namespaces = [
  109.         'special'  => '%',
  110.         'mixin'    => '@',
  111.         'function' => '^',
  112.     ];
  113.     public static $true         = [Type::T_KEYWORD'true'];
  114.     public static $false        = [Type::T_KEYWORD'false'];
  115.     /** @deprecated */
  116.     public static $NaN          = [Type::T_KEYWORD'NaN'];
  117.     /** @deprecated */
  118.     public static $Infinity     = [Type::T_KEYWORD'Infinity'];
  119.     public static $null         = [Type::T_NULL];
  120.     public static $nullString   = [Type::T_STRING'', []];
  121.     public static $defaultValue = [Type::T_KEYWORD''];
  122.     public static $selfSelector = [Type::T_SELF];
  123.     public static $emptyList    = [Type::T_LIST'', []];
  124.     public static $emptyMap     = [Type::T_MAP, [], []];
  125.     public static $emptyString  = [Type::T_STRING'"', []];
  126.     public static $with         = [Type::T_KEYWORD'with'];
  127.     public static $without      = [Type::T_KEYWORD'without'];
  128.     /**
  129.      * @var array<int, string|callable>
  130.      */
  131.     protected $importPaths = [];
  132.     /**
  133.      * @var array<string, Block>
  134.      */
  135.     protected $importCache = [];
  136.     /**
  137.      * @var string[]
  138.      */
  139.     protected $importedFiles = [];
  140.     /**
  141.      * @var array
  142.      * @phpstan-var array<string, array{0: callable, 1: array|null}>
  143.      */
  144.     protected $userFunctions = [];
  145.     /**
  146.      * @var array<string, mixed>
  147.      */
  148.     protected $registeredVars = [];
  149.     /**
  150.      * @var array<string, bool>
  151.      */
  152.     protected $registeredFeatures = [
  153.         'extend-selector-pseudoclass' => false,
  154.         'at-error'                    => true,
  155.         'units-level-3'               => true,
  156.         'global-variable-shadowing'   => false,
  157.     ];
  158.     /**
  159.      * @var string|null
  160.      */
  161.     protected $encoding null;
  162.     /**
  163.      * @var null
  164.      * @deprecated
  165.      */
  166.     protected $lineNumberStyle null;
  167.     /**
  168.      * @var int|SourceMapGenerator
  169.      * @phpstan-var self::SOURCE_MAP_*|SourceMapGenerator
  170.      */
  171.     protected $sourceMap self::SOURCE_MAP_NONE;
  172.     /**
  173.      * @var array
  174.      * @phpstan-var array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string}
  175.      */
  176.     protected $sourceMapOptions = [];
  177.     /**
  178.      * @var bool
  179.      */
  180.     private $charset true;
  181.     /**
  182.      * @var string|\ScssPhp\ScssPhp\Formatter
  183.      */
  184.     protected $formatter Expanded::class;
  185.     /**
  186.      * @var Environment
  187.      */
  188.     protected $rootEnv;
  189.     /**
  190.      * @var OutputBlock|null
  191.      */
  192.     protected $rootBlock;
  193.     /**
  194.      * @var \ScssPhp\ScssPhp\Compiler\Environment
  195.      */
  196.     protected $env;
  197.     /**
  198.      * @var OutputBlock|null
  199.      */
  200.     protected $scope;
  201.     /**
  202.      * @var Environment|null
  203.      */
  204.     protected $storeEnv;
  205.     /**
  206.      * @var bool|null
  207.      *
  208.      * @deprecated
  209.      */
  210.     protected $charsetSeen;
  211.     /**
  212.      * @var array<int, string|null>
  213.      */
  214.     protected $sourceNames;
  215.     /**
  216.      * @var Cache|null
  217.      */
  218.     protected $cache;
  219.     /**
  220.      * @var bool
  221.      */
  222.     protected $cacheCheckImportResolutions false;
  223.     /**
  224.      * @var int
  225.      */
  226.     protected $indentLevel;
  227.     /**
  228.      * @var array[]
  229.      */
  230.     protected $extends;
  231.     /**
  232.      * @var array<string, int[]>
  233.      */
  234.     protected $extendsMap;
  235.     /**
  236.      * @var array<string, int>
  237.      */
  238.     protected $parsedFiles = [];
  239.     /**
  240.      * @var Parser|null
  241.      */
  242.     protected $parser;
  243.     /**
  244.      * @var int|null
  245.      */
  246.     protected $sourceIndex;
  247.     /**
  248.      * @var int|null
  249.      */
  250.     protected $sourceLine;
  251.     /**
  252.      * @var int|null
  253.      */
  254.     protected $sourceColumn;
  255.     /**
  256.      * @var bool|null
  257.      */
  258.     protected $shouldEvaluate;
  259.     /**
  260.      * @var null
  261.      * @deprecated
  262.      */
  263.     protected $ignoreErrors;
  264.     /**
  265.      * @var bool
  266.      */
  267.     protected $ignoreCallStackMessage false;
  268.     /**
  269.      * @var array[]
  270.      */
  271.     protected $callStack = [];
  272.     /**
  273.      * @var array
  274.      * @phpstan-var list<array{currentDir: string|null, path: string, filePath: string}>
  275.      */
  276.     private $resolvedImports = [];
  277.     /**
  278.      * The directory of the currently processed file
  279.      *
  280.      * @var string|null
  281.      */
  282.     private $currentDirectory;
  283.     /**
  284.      * The directory of the input file
  285.      *
  286.      * @var string
  287.      */
  288.     private $rootDirectory;
  289.     /**
  290.      * @var bool
  291.      */
  292.     private $legacyCwdImportPath true;
  293.     /**
  294.      * @var LoggerInterface
  295.      */
  296.     private $logger;
  297.     /**
  298.      * @var array<string, bool>
  299.      */
  300.     private $warnedChildFunctions = [];
  301.     /**
  302.      * Constructor
  303.      *
  304.      * @param array|null $cacheOptions
  305.      * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string, checkImportResolutions?: bool}|null $cacheOptions
  306.      */
  307.     public function __construct($cacheOptions null)
  308.     {
  309.         $this->sourceNames = [];
  310.         if ($cacheOptions) {
  311.             $this->cache = new Cache($cacheOptions);
  312.             if (!empty($cacheOptions['checkImportResolutions'])) {
  313.                 $this->cacheCheckImportResolutions true;
  314.             }
  315.         }
  316.         $this->logger = new StreamLogger(fopen('php://stderr''w'), true);
  317.     }
  318.     /**
  319.      * Get compiler options
  320.      *
  321.      * @return array<string, mixed>
  322.      *
  323.      * @internal
  324.      */
  325.     public function getCompileOptions()
  326.     {
  327.         $options = [
  328.             'importPaths'        => $this->importPaths,
  329.             'registeredVars'     => $this->registeredVars,
  330.             'registeredFeatures' => $this->registeredFeatures,
  331.             'encoding'           => $this->encoding,
  332.             'sourceMap'          => serialize($this->sourceMap),
  333.             'sourceMapOptions'   => $this->sourceMapOptions,
  334.             'formatter'          => $this->formatter,
  335.             'legacyImportPath'   => $this->legacyCwdImportPath,
  336.         ];
  337.         return $options;
  338.     }
  339.     /**
  340.      * Sets an alternative logger.
  341.      *
  342.      * Changing the logger in the middle of the compilation is not
  343.      * supported and will result in an undefined behavior.
  344.      *
  345.      * @param LoggerInterface $logger
  346.      *
  347.      * @return void
  348.      */
  349.     public function setLogger(LoggerInterface $logger)
  350.     {
  351.         $this->logger $logger;
  352.     }
  353.     /**
  354.      * Set an alternative error output stream, for testing purpose only
  355.      *
  356.      * @param resource $handle
  357.      *
  358.      * @return void
  359.      *
  360.      * @deprecated Use {@see setLogger} instead
  361.      */
  362.     public function setErrorOuput($handle)
  363.     {
  364.         @trigger_error('The method "setErrorOuput" is deprecated. Use "setLogger" instead.'E_USER_DEPRECATED);
  365.         $this->logger = new StreamLogger($handle);
  366.     }
  367.     /**
  368.      * Compile scss
  369.      *
  370.      * @param string      $code
  371.      * @param string|null $path
  372.      *
  373.      * @return string
  374.      *
  375.      * @throws SassException when the source fails to compile
  376.      *
  377.      * @deprecated Use {@see compileString} instead.
  378.      */
  379.     public function compile($code$path null)
  380.     {
  381.         @trigger_error(sprintf('The "%s" method is deprecated. Use "compileString" instead.'__METHOD__), E_USER_DEPRECATED);
  382.         $result $this->compileString($code$path);
  383.         $sourceMap $result->getSourceMap();
  384.         if ($sourceMap !== null) {
  385.             if ($this->sourceMap instanceof SourceMapGenerator) {
  386.                 $this->sourceMap->saveMap($sourceMap);
  387.             } elseif ($this->sourceMap === self::SOURCE_MAP_FILE) {
  388.                 $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
  389.                 $sourceMapGenerator->saveMap($sourceMap);
  390.             }
  391.         }
  392.         return $result->getCss();
  393.     }
  394.     /**
  395.      * Compile scss
  396.      *
  397.      * @param string      $source
  398.      * @param string|null $path
  399.      *
  400.      * @return CompilationResult
  401.      *
  402.      * @throws SassException when the source fails to compile
  403.      */
  404.     public function compileString($source$path null)
  405.     {
  406.         if ($this->cache) {
  407.             $cacheKey       = ($path $path '(stdin)') . ':' md5($source);
  408.             $compileOptions $this->getCompileOptions();
  409.             $cachedResult $this->cache->getCache('compile'$cacheKey$compileOptions);
  410.             if ($cachedResult instanceof CachedResult && $this->isFreshCachedResult($cachedResult)) {
  411.                 return $cachedResult->getResult();
  412.             }
  413.         }
  414.         $this->indentLevel    = -1;
  415.         $this->extends        = [];
  416.         $this->extendsMap     = [];
  417.         $this->sourceIndex    null;
  418.         $this->sourceLine     null;
  419.         $this->sourceColumn   null;
  420.         $this->env            null;
  421.         $this->scope          null;
  422.         $this->storeEnv       null;
  423.         $this->shouldEvaluate null;
  424.         $this->ignoreCallStackMessage false;
  425.         $this->parsedFiles = [];
  426.         $this->importedFiles = [];
  427.         $this->resolvedImports = [];
  428.         if (!\is_null($path) && is_file($path)) {
  429.             $path realpath($path) ?: $path;
  430.             $this->currentDirectory dirname($path);
  431.             $this->rootDirectory $this->currentDirectory;
  432.         } else {
  433.             $this->currentDirectory null;
  434.             $this->rootDirectory getcwd();
  435.         }
  436.         try {
  437.             $this->parser $this->parserFactory($path);
  438.             $tree         $this->parser->parse($source);
  439.             $this->parser null;
  440.             $this->formatter = new $this->formatter();
  441.             $this->rootBlock null;
  442.             $this->rootEnv   $this->pushEnv($tree);
  443.             $warnCallback = function ($message$deprecation) {
  444.                 $this->logger->warn($message$deprecation);
  445.             };
  446.             $previousWarnCallback Warn::setCallback($warnCallback);
  447.             try {
  448.                 $this->injectVariables($this->registeredVars);
  449.                 $this->compileRoot($tree);
  450.                 $this->popEnv();
  451.             } finally {
  452.                 Warn::setCallback($previousWarnCallback);
  453.             }
  454.             $sourceMapGenerator null;
  455.             if ($this->sourceMap) {
  456.                 if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
  457.                     $sourceMapGenerator $this->sourceMap;
  458.                     $this->sourceMap self::SOURCE_MAP_FILE;
  459.                 } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
  460.                     $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
  461.                 }
  462.             }
  463.             $out $this->formatter->format($this->scope$sourceMapGenerator);
  464.             $prefix '';
  465.             if ($this->charset && strlen($out) !== Util::mbStrlen($out)) {
  466.                 $prefix '@charset "UTF-8";' "\n";
  467.                 $out $prefix $out;
  468.             }
  469.             $sourceMap null;
  470.             if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
  471.                 $sourceMap $sourceMapGenerator->generateJson($prefix);
  472.                 $sourceMapUrl null;
  473.                 switch ($this->sourceMap) {
  474.                     case self::SOURCE_MAP_INLINE:
  475.                         $sourceMapUrl sprintf('data:application/json,%s'Util::encodeURIComponent($sourceMap));
  476.                         break;
  477.                     case self::SOURCE_MAP_FILE:
  478.                         if (isset($this->sourceMapOptions['sourceMapURL'])) {
  479.                             $sourceMapUrl $this->sourceMapOptions['sourceMapURL'];
  480.                         }
  481.                         break;
  482.                 }
  483.                 if ($sourceMapUrl !== null) {
  484.                     $out .= sprintf('/*# sourceMappingURL=%s */'$sourceMapUrl);
  485.                 }
  486.             }
  487.         } catch (SassScriptException $e) {
  488.             throw new CompilerException($this->addLocationToMessage($e->getMessage()), 0$e);
  489.         }
  490.         $includedFiles = [];
  491.         foreach ($this->resolvedImports as $resolvedImport) {
  492.             $includedFiles[$resolvedImport['filePath']] = $resolvedImport['filePath'];
  493.         }
  494.         $result = new CompilationResult($out$sourceMaparray_values($includedFiles));
  495.         if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
  496.             $this->cache->setCache('compile'$cacheKey, new CachedResult($result$this->parsedFiles$this->resolvedImports), $compileOptions);
  497.         }
  498.         // Reset state to free memory
  499.         // TODO in 2.0, reset parsedFiles as well when the getter is removed.
  500.         $this->resolvedImports = [];
  501.         $this->importedFiles = [];
  502.         return $result;
  503.     }
  504.     /**
  505.      * @param CachedResult $result
  506.      *
  507.      * @return bool
  508.      */
  509.     private function isFreshCachedResult(CachedResult $result)
  510.     {
  511.         // check if any dependency file changed since the result was compiled
  512.         foreach ($result->getParsedFiles() as $file => $mtime) {
  513.             if (! is_file($file) || filemtime($file) !== $mtime) {
  514.                 return false;
  515.             }
  516.         }
  517.         if ($this->cacheCheckImportResolutions) {
  518.             $resolvedImports = [];
  519.             foreach ($result->getResolvedImports() as $import) {
  520.                 $currentDir $import['currentDir'];
  521.                 $path $import['path'];
  522.                 // store the check across all the results in memory to avoid multiple findImport() on the same path
  523.                 // with same context.
  524.                 // this is happening in a same hit with multiple compilations (especially with big frameworks)
  525.                 if (empty($resolvedImports[$currentDir][$path])) {
  526.                     $resolvedImports[$currentDir][$path] = $this->findImport($path$currentDir);
  527.                 }
  528.                 if ($resolvedImports[$currentDir][$path] !== $import['filePath']) {
  529.                     return false;
  530.                 }
  531.             }
  532.         }
  533.         return true;
  534.     }
  535.     /**
  536.      * Instantiate parser
  537.      *
  538.      * @param string|null $path
  539.      *
  540.      * @return \ScssPhp\ScssPhp\Parser
  541.      */
  542.     protected function parserFactory($path)
  543.     {
  544.         // https://sass-lang.com/documentation/at-rules/import
  545.         // CSS files imported by Sass don’t allow any special Sass features.
  546.         // In order to make sure authors don’t accidentally write Sass in their CSS,
  547.         // all Sass features that aren’t also valid CSS will produce errors.
  548.         // Otherwise, the CSS will be rendered as-is. It can even be extended!
  549.         $cssOnly false;
  550.         if ($path !== null && substr($path, -4) === '.css') {
  551.             $cssOnly true;
  552.         }
  553.         $parser = new Parser($path, \count($this->sourceNames), $this->encoding$this->cache$cssOnly$this->logger);
  554.         $this->sourceNames[] = $path;
  555.         $this->addParsedFile($path);
  556.         return $parser;
  557.     }
  558.     /**
  559.      * Is self extend?
  560.      *
  561.      * @param array $target
  562.      * @param array $origin
  563.      *
  564.      * @return boolean
  565.      */
  566.     protected function isSelfExtend($target$origin)
  567.     {
  568.         foreach ($origin as $sel) {
  569.             if (\in_array($target$sel)) {
  570.                 return true;
  571.             }
  572.         }
  573.         return false;
  574.     }
  575.     /**
  576.      * Push extends
  577.      *
  578.      * @param array      $target
  579.      * @param array      $origin
  580.      * @param array|null $block
  581.      *
  582.      * @return void
  583.      */
  584.     protected function pushExtends($target$origin$block)
  585.     {
  586.         $i = \count($this->extends);
  587.         $this->extends[] = [$target$origin$block];
  588.         foreach ($target as $part) {
  589.             if (isset($this->extendsMap[$part])) {
  590.                 $this->extendsMap[$part][] = $i;
  591.             } else {
  592.                 $this->extendsMap[$part] = [$i];
  593.             }
  594.         }
  595.     }
  596.     /**
  597.      * Make output block
  598.      *
  599.      * @param string|null   $type
  600.      * @param string[]|null $selectors
  601.      *
  602.      * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
  603.      */
  604.     protected function makeOutputBlock($type$selectors null)
  605.     {
  606.         $out = new OutputBlock();
  607.         $out->type      $type;
  608.         $out->lines     = [];
  609.         $out->children  = [];
  610.         $out->parent    $this->scope;
  611.         $out->selectors $selectors;
  612.         $out->depth     $this->env->depth;
  613.         if ($this->env->block instanceof Block) {
  614.             $out->sourceName   $this->env->block->sourceName;
  615.             $out->sourceLine   $this->env->block->sourceLine;
  616.             $out->sourceColumn $this->env->block->sourceColumn;
  617.         } else {
  618.             $out->sourceName   null;
  619.             $out->sourceLine   null;
  620.             $out->sourceColumn null;
  621.         }
  622.         return $out;
  623.     }
  624.     /**
  625.      * Compile root
  626.      *
  627.      * @param \ScssPhp\ScssPhp\Block $rootBlock
  628.      *
  629.      * @return void
  630.      */
  631.     protected function compileRoot(Block $rootBlock)
  632.     {
  633.         $this->rootBlock $this->scope $this->makeOutputBlock(Type::T_ROOT);
  634.         $this->compileChildrenNoReturn($rootBlock->children$this->scope);
  635.         $this->flattenSelectors($this->scope);
  636.         $this->missingSelectors();
  637.     }
  638.     /**
  639.      * Report missing selectors
  640.      *
  641.      * @return void
  642.      */
  643.     protected function missingSelectors()
  644.     {
  645.         foreach ($this->extends as $extend) {
  646.             if (isset($extend[3])) {
  647.                 continue;
  648.             }
  649.             list($target$origin$block) = $extend;
  650.             // ignore if !optional
  651.             if ($block[2]) {
  652.                 continue;
  653.             }
  654.             $target implode(' '$target);
  655.             $origin $this->collapseSelectors($origin);
  656.             $this->sourceLine $block[Parser::SOURCE_LINE];
  657.             throw $this->error("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
  658.         }
  659.     }
  660.     /**
  661.      * Flatten selectors
  662.      *
  663.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
  664.      * @param string                                 $parentKey
  665.      *
  666.      * @return void
  667.      */
  668.     protected function flattenSelectors(OutputBlock $block$parentKey null)
  669.     {
  670.         if ($block->selectors) {
  671.             $selectors = [];
  672.             foreach ($block->selectors as $s) {
  673.                 $selectors[] = $s;
  674.                 if (! \is_array($s)) {
  675.                     continue;
  676.                 }
  677.                 // check extends
  678.                 if (! empty($this->extendsMap)) {
  679.                     $this->matchExtends($s$selectors);
  680.                     // remove duplicates
  681.                     array_walk($selectors, function (&$value) {
  682.                         $value serialize($value);
  683.                     });
  684.                     $selectors array_unique($selectors);
  685.                     array_walk($selectors, function (&$value) {
  686.                         $value unserialize($value);
  687.                     });
  688.                 }
  689.             }
  690.             $block->selectors = [];
  691.             $placeholderSelector false;
  692.             foreach ($selectors as $selector) {
  693.                 if ($this->hasSelectorPlaceholder($selector)) {
  694.                     $placeholderSelector true;
  695.                     continue;
  696.                 }
  697.                 $block->selectors[] = $this->compileSelector($selector);
  698.             }
  699.             if ($placeholderSelector && === \count($block->selectors) && null !== $parentKey) {
  700.                 unset($block->parent->children[$parentKey]);
  701.                 return;
  702.             }
  703.         }
  704.         foreach ($block->children as $key => $child) {
  705.             $this->flattenSelectors($child$key);
  706.         }
  707.     }
  708.     /**
  709.      * Glue parts of :not( or :nth-child( ... that are in general split in selectors parts
  710.      *
  711.      * @param array $parts
  712.      *
  713.      * @return array
  714.      */
  715.     protected function glueFunctionSelectors($parts)
  716.     {
  717.         $new = [];
  718.         foreach ($parts as $part) {
  719.             if (\is_array($part)) {
  720.                 $part $this->glueFunctionSelectors($part);
  721.                 $new[] = $part;
  722.             } else {
  723.                 // a selector part finishing with a ) is the last part of a :not( or :nth-child(
  724.                 // and need to be joined to this
  725.                 if (
  726.                     \count($new) && \is_string($new[\count($new) - 1]) &&
  727.                     \strlen($part) && substr($part, -1) === ')' && strpos($part'(') === false
  728.                 ) {
  729.                     while (\count($new) > && substr($new[\count($new) - 1], -1) !== '(') {
  730.                         $part array_pop($new) . $part;
  731.                     }
  732.                     $new[\count($new) - 1] .= $part;
  733.                 } else {
  734.                     $new[] = $part;
  735.                 }
  736.             }
  737.         }
  738.         return $new;
  739.     }
  740.     /**
  741.      * Match extends
  742.      *
  743.      * @param array   $selector
  744.      * @param array   $out
  745.      * @param integer $from
  746.      * @param boolean $initial
  747.      *
  748.      * @return void
  749.      */
  750.     protected function matchExtends($selector, &$out$from 0$initial true)
  751.     {
  752.         static $partsPile = [];
  753.         $selector $this->glueFunctionSelectors($selector);
  754.         if (\count($selector) == && \in_array(reset($selector), $partsPile)) {
  755.             return;
  756.         }
  757.         $outRecurs = [];
  758.         foreach ($selector as $i => $part) {
  759.             if ($i $from) {
  760.                 continue;
  761.             }
  762.             // check that we are not building an infinite loop of extensions
  763.             // if the new part is just including a previous part don't try to extend anymore
  764.             if (\count($part) > 1) {
  765.                 foreach ($partsPile as $previousPart) {
  766.                     if (! \count(array_diff($previousPart$part))) {
  767.                         continue 2;
  768.                     }
  769.                 }
  770.             }
  771.             $partsPile[] = $part;
  772.             if ($this->matchExtendsSingle($part$origin$initial)) {
  773.                 $after       = \array_slice($selector$i 1);
  774.                 $before      = \array_slice($selector0$i);
  775.                 list($before$nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
  776.                 foreach ($origin as $new) {
  777.                     $k 0;
  778.                     // remove shared parts
  779.                     if (\count($new) > 1) {
  780.                         while ($k $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
  781.                             $k++;
  782.                         }
  783.                     }
  784.                     if (\count($nonBreakableBefore) && $k === \count($new)) {
  785.                         $k--;
  786.                     }
  787.                     $replacement = [];
  788.                     $tempReplacement $k ? \array_slice($new$k) : $new;
  789.                     for ($l = \count($tempReplacement) - 1$l >= 0$l--) {
  790.                         $slice = [];
  791.                         foreach ($tempReplacement[$l] as $chunk) {
  792.                             if (! \in_array($chunk$slice)) {
  793.                                 $slice[] = $chunk;
  794.                             }
  795.                         }
  796.                         array_unshift($replacement$slice);
  797.                         if (! $this->isImmediateRelationshipCombinator(end($slice))) {
  798.                             break;
  799.                         }
  800.                     }
  801.                     $afterBefore $l != ? \array_slice($tempReplacement0$l) : [];
  802.                     // Merge shared direct relationships.
  803.                     $mergedBefore $this->mergeDirectRelationships($afterBefore$nonBreakableBefore);
  804.                     $result array_merge(
  805.                         $before,
  806.                         $mergedBefore,
  807.                         $replacement,
  808.                         $after
  809.                     );
  810.                     if ($result === $selector) {
  811.                         continue;
  812.                     }
  813.                     $this->pushOrMergeExtentedSelector($out$result);
  814.                     // recursively check for more matches
  815.                     $startRecurseFrom = \count($before) + min(\count($nonBreakableBefore), \count($mergedBefore));
  816.                     if (\count($origin) > 1) {
  817.                         $this->matchExtends($result$out$startRecurseFromfalse);
  818.                     } else {
  819.                         $this->matchExtends($result$outRecurs$startRecurseFromfalse);
  820.                     }
  821.                     // selector sequence merging
  822.                     if (! empty($before) && \count($new) > 1) {
  823.                         $preSharedParts $k ? \array_slice($before0$k) : [];
  824.                         $postSharedParts $k ? \array_slice($before$k) : $before;
  825.                         list($betweenSharedParts$nonBreakabl2) = $this->extractRelationshipFromFragment($afterBefore);
  826.                         $result2 array_merge(
  827.                             $preSharedParts,
  828.                             $betweenSharedParts,
  829.                             $postSharedParts,
  830.                             $nonBreakabl2,
  831.                             $nonBreakableBefore,
  832.                             $replacement,
  833.                             $after
  834.                         );
  835.                         $this->pushOrMergeExtentedSelector($out$result2);
  836.                     }
  837.                 }
  838.             }
  839.             array_pop($partsPile);
  840.         }
  841.         while (\count($outRecurs)) {
  842.             $result array_shift($outRecurs);
  843.             $this->pushOrMergeExtentedSelector($out$result);
  844.         }
  845.     }
  846.     /**
  847.      * Test a part for being a pseudo selector
  848.      *
  849.      * @param string $part
  850.      * @param array  $matches
  851.      *
  852.      * @return boolean
  853.      */
  854.     protected function isPseudoSelector($part, &$matches)
  855.     {
  856.         if (
  857.             strpos($part':') === &&
  858.             preg_match(",^::?([\w-]+)\((.+)\)$,"$part$matches)
  859.         ) {
  860.             return true;
  861.         }
  862.         return false;
  863.     }
  864.     /**
  865.      * Push extended selector except if
  866.      *  - this is a pseudo selector
  867.      *  - same as previous
  868.      *  - in a white list
  869.      * in this case we merge the pseudo selector content
  870.      *
  871.      * @param array $out
  872.      * @param array $extended
  873.      *
  874.      * @return void
  875.      */
  876.     protected function pushOrMergeExtentedSelector(&$out$extended)
  877.     {
  878.         if (\count($out) && \count($extended) === && \count(reset($extended)) === 1) {
  879.             $single reset($extended);
  880.             $part reset($single);
  881.             if (
  882.                 $this->isPseudoSelector($part$matchesExtended) &&
  883.                 \in_array($matchesExtended[1], [ 'slotted' ])
  884.             ) {
  885.                 $prev end($out);
  886.                 $prev $this->glueFunctionSelectors($prev);
  887.                 if (\count($prev) === && \count(reset($prev)) === 1) {
  888.                     $single reset($prev);
  889.                     $part reset($single);
  890.                     if (
  891.                         $this->isPseudoSelector($part$matchesPrev) &&
  892.                         $matchesPrev[1] === $matchesExtended[1]
  893.                     ) {
  894.                         $extended explode($matchesExtended[1] . '('$matchesExtended[0], 2);
  895.                         $extended[1] = $matchesPrev[2] . ', ' $extended[1];
  896.                         $extended implode($matchesExtended[1] . '('$extended);
  897.                         $extended = [ [ $extended ]];
  898.                         array_pop($out);
  899.                     }
  900.                 }
  901.             }
  902.         }
  903.         $out[] = $extended;
  904.     }
  905.     /**
  906.      * Match extends single
  907.      *
  908.      * @param array   $rawSingle
  909.      * @param array   $outOrigin
  910.      * @param boolean $initial
  911.      *
  912.      * @return boolean
  913.      */
  914.     protected function matchExtendsSingle($rawSingle, &$outOrigin$initial true)
  915.     {
  916.         $counts = [];
  917.         $single = [];
  918.         // simple usual cases, no need to do the whole trick
  919.         if (\in_array($rawSingle, [['>'],['+'],['~']])) {
  920.             return false;
  921.         }
  922.         foreach ($rawSingle as $part) {
  923.             // matches Number
  924.             if (! \is_string($part)) {
  925.                 return false;
  926.             }
  927.             if (! preg_match('/^[\[.:#%]/'$part) && \count($single)) {
  928.                 $single[\count($single) - 1] .= $part;
  929.             } else {
  930.                 $single[] = $part;
  931.             }
  932.         }
  933.         $extendingDecoratedTag false;
  934.         if (\count($single) > 1) {
  935.             $matches null;
  936.             $extendingDecoratedTag preg_match('/^[a-z0-9]+$/i'$single[0], $matches) ? $matches[0] : false;
  937.         }
  938.         $outOrigin = [];
  939.         $found false;
  940.         foreach ($single as $k => $part) {
  941.             if (isset($this->extendsMap[$part])) {
  942.                 foreach ($this->extendsMap[$part] as $idx) {
  943.                     $counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1;
  944.                 }
  945.             }
  946.             if (
  947.                 $initial &&
  948.                 $this->isPseudoSelector($part$matches) &&
  949.                 ! \in_array($matches[1], [ 'not' ])
  950.             ) {
  951.                 $buffer    $matches[2];
  952.                 $parser    $this->parserFactory(__METHOD__);
  953.                 if ($parser->parseSelector($buffer$subSelectorsfalse)) {
  954.                     foreach ($subSelectors as $ksub => $subSelector) {
  955.                         $subExtended = [];
  956.                         $this->matchExtends($subSelector$subExtended0false);
  957.                         if ($subExtended) {
  958.                             $subSelectorsExtended $subSelectors;
  959.                             $subSelectorsExtended[$ksub] = $subExtended;
  960.                             foreach ($subSelectorsExtended as $ksse => $sse) {
  961.                                 $subSelectorsExtended[$ksse] = $this->collapseSelectors($sse);
  962.                             }
  963.                             $subSelectorsExtended implode(', '$subSelectorsExtended);
  964.                             $singleExtended $single;
  965.                             $singleExtended[$k] = str_replace('(' $buffer ')'"($subSelectorsExtended)"$part);
  966.                             $outOrigin[] = [ $singleExtended ];
  967.                             $found true;
  968.                         }
  969.                     }
  970.                 }
  971.             }
  972.         }
  973.         foreach ($counts as $idx => $count) {
  974.             list($target$origin/* $block */) = $this->extends[$idx];
  975.             $origin $this->glueFunctionSelectors($origin);
  976.             // check count
  977.             if ($count !== \count($target)) {
  978.                 continue;
  979.             }
  980.             $this->extends[$idx][3] = true;
  981.             $rem array_diff($single$target);
  982.             foreach ($origin as $j => $new) {
  983.                 // prevent infinite loop when target extends itself
  984.                 if ($this->isSelfExtend($single$origin) && ! $initial) {
  985.                     return false;
  986.                 }
  987.                 $replacement end($new);
  988.                 // Extending a decorated tag with another tag is not possible.
  989.                 if (
  990.                     $extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
  991.                     preg_match('/^[a-z0-9]+$/i'$replacement[0])
  992.                 ) {
  993.                     unset($origin[$j]);
  994.                     continue;
  995.                 }
  996.                 $combined $this->combineSelectorSingle($replacement$rem);
  997.                 if (\count(array_diff($combined$origin[$j][\count($origin[$j]) - 1]))) {
  998.                     $origin[$j][\count($origin[$j]) - 1] = $combined;
  999.                 }
  1000.             }
  1001.             $outOrigin array_merge($outOrigin$origin);
  1002.             $found true;
  1003.         }
  1004.         return $found;
  1005.     }
  1006.     /**
  1007.      * Extract a relationship from the fragment.
  1008.      *
  1009.      * When extracting the last portion of a selector we will be left with a
  1010.      * fragment which may end with a direction relationship combinator. This
  1011.      * method will extract the relationship fragment and return it along side
  1012.      * the rest.
  1013.      *
  1014.      * @param array $fragment The selector fragment maybe ending with a direction relationship combinator.
  1015.      *
  1016.      * @return array The selector without the relationship fragment if any, the relationship fragment.
  1017.      */
  1018.     protected function extractRelationshipFromFragment(array $fragment)
  1019.     {
  1020.         $parents = [];
  1021.         $children = [];
  1022.         $j $i = \count($fragment);
  1023.         for (;;) {
  1024.             $children $j != $i ? \array_slice($fragment$j$i $j) : [];
  1025.             $parents  = \array_slice($fragment0$j);
  1026.             $slice    end($parents);
  1027.             if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
  1028.                 break;
  1029.             }
  1030.             $j -= 2;
  1031.         }
  1032.         return [$parents$children];
  1033.     }
  1034.     /**
  1035.      * Combine selector single
  1036.      *
  1037.      * @param array $base
  1038.      * @param array $other
  1039.      *
  1040.      * @return array
  1041.      */
  1042.     protected function combineSelectorSingle($base$other)
  1043.     {
  1044.         $tag    = [];
  1045.         $out    = [];
  1046.         $wasTag false;
  1047.         $pseudo = [];
  1048.         while (\count($other) && strpos(end($other), ':') === 0) {
  1049.             array_unshift($pseudoarray_pop($other));
  1050.         }
  1051.         foreach ([array_reverse($base), array_reverse($other)] as $single) {
  1052.             $rang count($single);
  1053.             foreach ($single as $part) {
  1054.                 if (preg_match('/^[\[:]/'$part)) {
  1055.                     $out[] = $part;
  1056.                     $wasTag false;
  1057.                 } elseif (preg_match('/^[\.#]/'$part)) {
  1058.                     array_unshift($out$part);
  1059.                     $wasTag false;
  1060.                 } elseif (preg_match('/^[^_-]/'$part) && $rang === 1) {
  1061.                     $tag[] = $part;
  1062.                     $wasTag true;
  1063.                 } elseif ($wasTag) {
  1064.                     $tag[\count($tag) - 1] .= $part;
  1065.                 } else {
  1066.                     array_unshift($out$part);
  1067.                 }
  1068.                 $rang--;
  1069.             }
  1070.         }
  1071.         if (\count($tag)) {
  1072.             array_unshift($out$tag[0]);
  1073.         }
  1074.         while (\count($pseudo)) {
  1075.             $out[] = array_shift($pseudo);
  1076.         }
  1077.         return $out;
  1078.     }
  1079.     /**
  1080.      * Compile media
  1081.      *
  1082.      * @param \ScssPhp\ScssPhp\Block $media
  1083.      *
  1084.      * @return void
  1085.      */
  1086.     protected function compileMedia(Block $media)
  1087.     {
  1088.         $this->pushEnv($media);
  1089.         $mediaQueries $this->compileMediaQuery($this->multiplyMedia($this->env));
  1090.         if (! empty($mediaQueries)) {
  1091.             $previousScope $this->scope;
  1092.             $parentScope $this->mediaParent($this->scope);
  1093.             foreach ($mediaQueries as $mediaQuery) {
  1094.                 $this->scope $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
  1095.                 $parentScope->children[] = $this->scope;
  1096.                 $parentScope $this->scope;
  1097.             }
  1098.             // top level properties in a media cause it to be wrapped
  1099.             $needsWrap false;
  1100.             foreach ($media->children as $child) {
  1101.                 $type $child[0];
  1102.                 if (
  1103.                     $type !== Type::T_BLOCK &&
  1104.                     $type !== Type::T_MEDIA &&
  1105.                     $type !== Type::T_DIRECTIVE &&
  1106.                     $type !== Type::T_IMPORT
  1107.                 ) {
  1108.                     $needsWrap true;
  1109.                     break;
  1110.                 }
  1111.             }
  1112.             if ($needsWrap) {
  1113.                 $wrapped = new Block();
  1114.                 $wrapped->sourceName   $media->sourceName;
  1115.                 $wrapped->sourceIndex  $media->sourceIndex;
  1116.                 $wrapped->sourceLine   $media->sourceLine;
  1117.                 $wrapped->sourceColumn $media->sourceColumn;
  1118.                 $wrapped->selectors    = [];
  1119.                 $wrapped->comments     = [];
  1120.                 $wrapped->parent       $media;
  1121.                 $wrapped->children     $media->children;
  1122.                 $media->children = [[Type::T_BLOCK$wrapped]];
  1123.             }
  1124.             $this->compileChildrenNoReturn($media->children$this->scope);
  1125.             $this->scope $previousScope;
  1126.         }
  1127.         $this->popEnv();
  1128.     }
  1129.     /**
  1130.      * Media parent
  1131.      *
  1132.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
  1133.      *
  1134.      * @return \ScssPhp\ScssPhp\Formatter\OutputBlock
  1135.      */
  1136.     protected function mediaParent(OutputBlock $scope)
  1137.     {
  1138.         while (! empty($scope->parent)) {
  1139.             if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
  1140.                 break;
  1141.             }
  1142.             $scope $scope->parent;
  1143.         }
  1144.         return $scope;
  1145.     }
  1146.     /**
  1147.      * Compile directive
  1148.      *
  1149.      * @param \ScssPhp\ScssPhp\Block|array $directive
  1150.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  1151.      *
  1152.      * @return void
  1153.      */
  1154.     protected function compileDirective($directiveOutputBlock $out)
  1155.     {
  1156.         if (\is_array($directive)) {
  1157.             $directiveName $this->compileDirectiveName($directive[0]);
  1158.             $s '@' $directiveName;
  1159.             if (! empty($directive[1])) {
  1160.                 $s .= ' ' $this->compileValue($directive[1]);
  1161.             }
  1162.             // sass-spec compliance on newline after directives, a bit tricky :/
  1163.             $appendNewLine = (! empty($directive[2]) || strpos($s"\n")) ? "\n" "";
  1164.             if (\is_array($directive[0]) && empty($directive[1])) {
  1165.                 $appendNewLine "\n";
  1166.             }
  1167.             if (empty($directive[3])) {
  1168.                 $this->appendRootDirective($s ';' $appendNewLine$out, [Type::T_COMMENTType::T_DIRECTIVE]);
  1169.             } else {
  1170.                 $this->appendOutputLine($outType::T_DIRECTIVE$s ';');
  1171.             }
  1172.         } else {
  1173.             $directive->name $this->compileDirectiveName($directive->name);
  1174.             $s '@' $directive->name;
  1175.             if (! empty($directive->value)) {
  1176.                 $s .= ' ' $this->compileValue($directive->value);
  1177.             }
  1178.             if ($directive->name === 'keyframes' || substr($directive->name, -10) === '-keyframes') {
  1179.                 $this->compileKeyframeBlock($directive, [$s]);
  1180.             } else {
  1181.                 $this->compileNestedBlock($directive, [$s]);
  1182.             }
  1183.         }
  1184.     }
  1185.     /**
  1186.      * directive names can include some interpolation
  1187.      *
  1188.      * @param string|array $directiveName
  1189.      * @return string
  1190.      * @throws CompilerException
  1191.      */
  1192.     protected function compileDirectiveName($directiveName)
  1193.     {
  1194.         if (is_string($directiveName)) {
  1195.             return $directiveName;
  1196.         }
  1197.         return $this->compileValue($directiveName);
  1198.     }
  1199.     /**
  1200.      * Compile at-root
  1201.      *
  1202.      * @param \ScssPhp\ScssPhp\Block $block
  1203.      *
  1204.      * @return void
  1205.      */
  1206.     protected function compileAtRoot(Block $block)
  1207.     {
  1208.         $env     $this->pushEnv($block);
  1209.         $envs    $this->compactEnv($env);
  1210.         list($with$without) = $this->compileWith(isset($block->with) ? $block->with null);
  1211.         // wrap inline selector
  1212.         if ($block->selector) {
  1213.             $wrapped = new Block();
  1214.             $wrapped->sourceName   $block->sourceName;
  1215.             $wrapped->sourceIndex  $block->sourceIndex;
  1216.             $wrapped->sourceLine   $block->sourceLine;
  1217.             $wrapped->sourceColumn $block->sourceColumn;
  1218.             $wrapped->selectors    $block->selector;
  1219.             $wrapped->comments     = [];
  1220.             $wrapped->parent       $block;
  1221.             $wrapped->children     $block->children;
  1222.             $wrapped->selfParent   $block->selfParent;
  1223.             $block->children = [[Type::T_BLOCK$wrapped]];
  1224.             $block->selector null;
  1225.         }
  1226.         $selfParent $block->selfParent;
  1227.         assert($selfParent !== null'at-root blocks must have a selfParent set.');
  1228.         if (
  1229.             ! $selfParent->selectors &&
  1230.             isset($block->parent) && $block->parent &&
  1231.             isset($block->parent->selectors) && $block->parent->selectors
  1232.         ) {
  1233.             $selfParent $block->parent;
  1234.         }
  1235.         $this->env $this->filterWithWithout($envs$with$without);
  1236.         $saveScope   $this->scope;
  1237.         $this->scope $this->filterScopeWithWithout($saveScope$with$without);
  1238.         // propagate selfParent to the children where they still can be useful
  1239.         $this->compileChildrenNoReturn($block->children$this->scope$selfParent);
  1240.         $this->scope $this->completeScope($this->scope$saveScope);
  1241.         $this->scope $saveScope;
  1242.         $this->env   $this->extractEnv($envs);
  1243.         $this->popEnv();
  1244.     }
  1245.     /**
  1246.      * Filter at-root scope depending of with/without option
  1247.      *
  1248.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
  1249.      * @param array                                  $with
  1250.      * @param array                                  $without
  1251.      *
  1252.      * @return OutputBlock
  1253.      */
  1254.     protected function filterScopeWithWithout($scope$with$without)
  1255.     {
  1256.         $filteredScopes = [];
  1257.         $childStash = [];
  1258.         if ($scope->type === Type::T_ROOT) {
  1259.             return $scope;
  1260.         }
  1261.         // start from the root
  1262.         while ($scope->parent && $scope->parent->type !== Type::T_ROOT) {
  1263.             array_unshift($childStash$scope);
  1264.             $scope $scope->parent;
  1265.         }
  1266.         for (;;) {
  1267.             if (! $scope) {
  1268.                 break;
  1269.             }
  1270.             if ($this->isWith($scope$with$without)) {
  1271.                 $s = clone $scope;
  1272.                 $s->children = [];
  1273.                 $s->lines    = [];
  1274.                 $s->parent   null;
  1275.                 if ($s->type !== Type::T_MEDIA && $s->type !== Type::T_DIRECTIVE) {
  1276.                     $s->selectors = [];
  1277.                 }
  1278.                 $filteredScopes[] = $s;
  1279.             }
  1280.             if (\count($childStash)) {
  1281.                 $scope array_shift($childStash);
  1282.             } elseif ($scope->children) {
  1283.                 $scope end($scope->children);
  1284.             } else {
  1285.                 $scope null;
  1286.             }
  1287.         }
  1288.         if (! \count($filteredScopes)) {
  1289.             return $this->rootBlock;
  1290.         }
  1291.         $newScope array_shift($filteredScopes);
  1292.         $newScope->parent $this->rootBlock;
  1293.         $this->rootBlock->children[] = $newScope;
  1294.         $p = &$newScope;
  1295.         while (\count($filteredScopes)) {
  1296.             $s array_shift($filteredScopes);
  1297.             $s->parent $p;
  1298.             $p->children[] = $s;
  1299.             $newScope = &$p->children[0];
  1300.             $p = &$p->children[0];
  1301.         }
  1302.         return $newScope;
  1303.     }
  1304.     /**
  1305.      * found missing selector from a at-root compilation in the previous scope
  1306.      * (if at-root is just enclosing a property, the selector is in the parent tree)
  1307.      *
  1308.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
  1309.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
  1310.      *
  1311.      * @return OutputBlock
  1312.      */
  1313.     protected function completeScope($scope$previousScope)
  1314.     {
  1315.         if (! $scope->type && (! $scope->selectors || ! \count($scope->selectors)) && \count($scope->lines)) {
  1316.             $scope->selectors $this->findScopeSelectors($previousScope$scope->depth);
  1317.         }
  1318.         if ($scope->children) {
  1319.             foreach ($scope->children as $k => $c) {
  1320.                 $scope->children[$k] = $this->completeScope($c$previousScope);
  1321.             }
  1322.         }
  1323.         return $scope;
  1324.     }
  1325.     /**
  1326.      * Find a selector by the depth node in the scope
  1327.      *
  1328.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
  1329.      * @param integer                                $depth
  1330.      *
  1331.      * @return array
  1332.      */
  1333.     protected function findScopeSelectors($scope$depth)
  1334.     {
  1335.         if ($scope->depth === $depth && $scope->selectors) {
  1336.             return $scope->selectors;
  1337.         }
  1338.         if ($scope->children) {
  1339.             foreach (array_reverse($scope->children) as $c) {
  1340.                 if ($s $this->findScopeSelectors($c$depth)) {
  1341.                     return $s;
  1342.                 }
  1343.             }
  1344.         }
  1345.         return [];
  1346.     }
  1347.     /**
  1348.      * Compile @at-root's with: inclusion / without: exclusion into 2 lists uses to filter scope/env later
  1349.      *
  1350.      * @param array $withCondition
  1351.      *
  1352.      * @return array
  1353.      */
  1354.     protected function compileWith($withCondition)
  1355.     {
  1356.         // just compile what we have in 2 lists
  1357.         $with = [];
  1358.         $without = ['rule' => true];
  1359.         if ($withCondition) {
  1360.             if ($withCondition[0] === Type::T_INTERPOLATE) {
  1361.                 $w $this->compileValue($withCondition);
  1362.                 $buffer "($w)";
  1363.                 $parser $this->parserFactory(__METHOD__);
  1364.                 if ($parser->parseValue($buffer$reParsedWith)) {
  1365.                     $withCondition $reParsedWith;
  1366.                 }
  1367.             }
  1368.             if ($this->mapHasKey($withCondition, static::$with)) {
  1369.                 $without = []; // cancel the default
  1370.                 $list $this->coerceList($this->libMapGet([$withCondition, static::$with]));
  1371.                 foreach ($list[2] as $item) {
  1372.                     $keyword $this->compileStringContent($this->coerceString($item));
  1373.                     $with[$keyword] = true;
  1374.                 }
  1375.             }
  1376.             if ($this->mapHasKey($withCondition, static::$without)) {
  1377.                 $without = []; // cancel the default
  1378.                 $list $this->coerceList($this->libMapGet([$withCondition, static::$without]));
  1379.                 foreach ($list[2] as $item) {
  1380.                     $keyword $this->compileStringContent($this->coerceString($item));
  1381.                     $without[$keyword] = true;
  1382.                 }
  1383.             }
  1384.         }
  1385.         return [$with$without];
  1386.     }
  1387.     /**
  1388.      * Filter env stack
  1389.      *
  1390.      * @param Environment[] $envs
  1391.      * @param array $with
  1392.      * @param array $without
  1393.      *
  1394.      * @return Environment
  1395.      *
  1396.      * @phpstan-param  non-empty-array<Environment> $envs
  1397.      */
  1398.     protected function filterWithWithout($envs$with$without)
  1399.     {
  1400.         $filtered = [];
  1401.         foreach ($envs as $e) {
  1402.             if ($e->block && ! $this->isWith($e->block$with$without)) {
  1403.                 $ec = clone $e;
  1404.                 $ec->block     null;
  1405.                 $ec->selectors = [];
  1406.                 $filtered[] = $ec;
  1407.             } else {
  1408.                 $filtered[] = $e;
  1409.             }
  1410.         }
  1411.         return $this->extractEnv($filtered);
  1412.     }
  1413.     /**
  1414.      * Filter WITH rules
  1415.      *
  1416.      * @param \ScssPhp\ScssPhp\Block|\ScssPhp\ScssPhp\Formatter\OutputBlock $block
  1417.      * @param array                                                         $with
  1418.      * @param array                                                         $without
  1419.      *
  1420.      * @return boolean
  1421.      */
  1422.     protected function isWith($block$with$without)
  1423.     {
  1424.         if (isset($block->type)) {
  1425.             if ($block->type === Type::T_MEDIA) {
  1426.                 return $this->testWithWithout('media'$with$without);
  1427.             }
  1428.             if ($block->type === Type::T_DIRECTIVE) {
  1429.                 if (isset($block->name)) {
  1430.                     return $this->testWithWithout($this->compileDirectiveName($block->name), $with$without);
  1431.                 } elseif (isset($block->selectors) && preg_match(',@(\w+),ims'json_encode($block->selectors), $m)) {
  1432.                     return $this->testWithWithout($m[1], $with$without);
  1433.                 } else {
  1434.                     return $this->testWithWithout('???'$with$without);
  1435.                 }
  1436.             }
  1437.         } elseif (isset($block->selectors)) {
  1438.             // a selector starting with number is a keyframe rule
  1439.             if (\count($block->selectors)) {
  1440.                 $s reset($block->selectors);
  1441.                 while (\is_array($s)) {
  1442.                     $s reset($s);
  1443.                 }
  1444.                 if (\is_object($s) && $s instanceof Number) {
  1445.                     return $this->testWithWithout('keyframes'$with$without);
  1446.                 }
  1447.             }
  1448.             return $this->testWithWithout('rule'$with$without);
  1449.         }
  1450.         return true;
  1451.     }
  1452.     /**
  1453.      * Test a single type of block against with/without lists
  1454.      *
  1455.      * @param string $what
  1456.      * @param array  $with
  1457.      * @param array  $without
  1458.      *
  1459.      * @return boolean
  1460.      *   true if the block should be kept, false to reject
  1461.      */
  1462.     protected function testWithWithout($what$with$without)
  1463.     {
  1464.         // if without, reject only if in the list (or 'all' is in the list)
  1465.         if (\count($without)) {
  1466.             return (isset($without[$what]) || isset($without['all'])) ? false true;
  1467.         }
  1468.         // otherwise reject all what is not in the with list
  1469.         return (isset($with[$what]) || isset($with['all'])) ? true false;
  1470.     }
  1471.     /**
  1472.      * Compile keyframe block
  1473.      *
  1474.      * @param \ScssPhp\ScssPhp\Block $block
  1475.      * @param string[]               $selectors
  1476.      *
  1477.      * @return void
  1478.      */
  1479.     protected function compileKeyframeBlock(Block $block$selectors)
  1480.     {
  1481.         $env $this->pushEnv($block);
  1482.         $envs $this->compactEnv($env);
  1483.         $this->env $this->extractEnv(array_filter($envs, function (Environment $e) {
  1484.             return ! isset($e->block->selectors);
  1485.         }));
  1486.         $this->scope $this->makeOutputBlock($block->type$selectors);
  1487.         $this->scope->depth 1;
  1488.         $this->scope->parent->children[] = $this->scope;
  1489.         $this->compileChildrenNoReturn($block->children$this->scope);
  1490.         $this->scope $this->scope->parent;
  1491.         $this->env   $this->extractEnv($envs);
  1492.         $this->popEnv();
  1493.     }
  1494.     /**
  1495.      * Compile nested properties lines
  1496.      *
  1497.      * @param \ScssPhp\ScssPhp\Block                 $block
  1498.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  1499.      *
  1500.      * @return void
  1501.      */
  1502.     protected function compileNestedPropertiesBlock(Block $blockOutputBlock $out)
  1503.     {
  1504.         $prefix $this->compileValue($block->prefix) . '-';
  1505.         $nested $this->makeOutputBlock($block->type);
  1506.         $nested->parent $out;
  1507.         if ($block->hasValue) {
  1508.             $nested->depth $out->depth 1;
  1509.         }
  1510.         $out->children[] = $nested;
  1511.         foreach ($block->children as $child) {
  1512.             switch ($child[0]) {
  1513.                 case Type::T_ASSIGN:
  1514.                     array_unshift($child[1][2], $prefix);
  1515.                     break;
  1516.                 case Type::T_NESTED_PROPERTY:
  1517.                     array_unshift($child[1]->prefix[2], $prefix);
  1518.                     break;
  1519.             }
  1520.             $this->compileChild($child$nested);
  1521.         }
  1522.     }
  1523.     /**
  1524.      * Compile nested block
  1525.      *
  1526.      * @param \ScssPhp\ScssPhp\Block $block
  1527.      * @param string[]               $selectors
  1528.      *
  1529.      * @return void
  1530.      */
  1531.     protected function compileNestedBlock(Block $block$selectors)
  1532.     {
  1533.         $this->pushEnv($block);
  1534.         $this->scope $this->makeOutputBlock($block->type$selectors);
  1535.         $this->scope->parent->children[] = $this->scope;
  1536.         // wrap assign children in a block
  1537.         // except for @font-face
  1538.         if ($block->type !== Type::T_DIRECTIVE || $this->compileDirectiveName($block->name) !== 'font-face') {
  1539.             // need wrapping?
  1540.             $needWrapping false;
  1541.             foreach ($block->children as $child) {
  1542.                 if ($child[0] === Type::T_ASSIGN) {
  1543.                     $needWrapping true;
  1544.                     break;
  1545.                 }
  1546.             }
  1547.             if ($needWrapping) {
  1548.                 $wrapped = new Block();
  1549.                 $wrapped->sourceName   $block->sourceName;
  1550.                 $wrapped->sourceIndex  $block->sourceIndex;
  1551.                 $wrapped->sourceLine   $block->sourceLine;
  1552.                 $wrapped->sourceColumn $block->sourceColumn;
  1553.                 $wrapped->selectors    = [];
  1554.                 $wrapped->comments     = [];
  1555.                 $wrapped->parent       $block;
  1556.                 $wrapped->children     $block->children;
  1557.                 $wrapped->selfParent   $block->selfParent;
  1558.                 $block->children = [[Type::T_BLOCK$wrapped]];
  1559.             }
  1560.         }
  1561.         $this->compileChildrenNoReturn($block->children$this->scope);
  1562.         $this->scope $this->scope->parent;
  1563.         $this->popEnv();
  1564.     }
  1565.     /**
  1566.      * Recursively compiles a block.
  1567.      *
  1568.      * A block is analogous to a CSS block in most cases. A single SCSS document
  1569.      * is encapsulated in a block when parsed, but it does not have parent tags
  1570.      * so all of its children appear on the root level when compiled.
  1571.      *
  1572.      * Blocks are made up of selectors and children.
  1573.      *
  1574.      * The children of a block are just all the blocks that are defined within.
  1575.      *
  1576.      * Compiling the block involves pushing a fresh environment on the stack,
  1577.      * and iterating through the props, compiling each one.
  1578.      *
  1579.      * @see Compiler::compileChild()
  1580.      *
  1581.      * @param \ScssPhp\ScssPhp\Block $block
  1582.      *
  1583.      * @return void
  1584.      */
  1585.     protected function compileBlock(Block $block)
  1586.     {
  1587.         $env $this->pushEnv($block);
  1588.         $env->selectors $this->evalSelectors($block->selectors);
  1589.         $out $this->makeOutputBlock(null);
  1590.         $this->scope->children[] = $out;
  1591.         if (\count($block->children)) {
  1592.             $out->selectors $this->multiplySelectors($env$block->selfParent);
  1593.             // propagate selfParent to the children where they still can be useful
  1594.             $selfParentSelectors null;
  1595.             if (isset($block->selfParent->selectors)) {
  1596.                 $selfParentSelectors $block->selfParent->selectors;
  1597.                 $block->selfParent->selectors $out->selectors;
  1598.             }
  1599.             $this->compileChildrenNoReturn($block->children$out$block->selfParent);
  1600.             // and revert for the following children of the same block
  1601.             if ($selfParentSelectors) {
  1602.                 $block->selfParent->selectors $selfParentSelectors;
  1603.             }
  1604.         }
  1605.         $this->popEnv();
  1606.     }
  1607.     /**
  1608.      * Compile the value of a comment that can have interpolation
  1609.      *
  1610.      * @param array   $value
  1611.      * @param boolean $pushEnv
  1612.      *
  1613.      * @return string
  1614.      */
  1615.     protected function compileCommentValue($value$pushEnv false)
  1616.     {
  1617.         $c $value[1];
  1618.         if (isset($value[2])) {
  1619.             if ($pushEnv) {
  1620.                 $this->pushEnv();
  1621.             }
  1622.             try {
  1623.                 $c $this->compileValue($value[2]);
  1624.             } catch (SassScriptException $e) {
  1625.                 $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' $this->addLocationToMessage($e->getMessage()), true);
  1626.                 // ignore error in comment compilation which are only interpolation
  1627.             } catch (SassException $e) {
  1628.                 $this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' $e->getMessage(), true);
  1629.                 // ignore error in comment compilation which are only interpolation
  1630.             }
  1631.             if ($pushEnv) {
  1632.                 $this->popEnv();
  1633.             }
  1634.         }
  1635.         return $c;
  1636.     }
  1637.     /**
  1638.      * Compile root level comment
  1639.      *
  1640.      * @param array $block
  1641.      *
  1642.      * @return void
  1643.      */
  1644.     protected function compileComment($block)
  1645.     {
  1646.         $out $this->makeOutputBlock(Type::T_COMMENT);
  1647.         $out->lines[] = $this->compileCommentValue($blocktrue);
  1648.         $this->scope->children[] = $out;
  1649.     }
  1650.     /**
  1651.      * Evaluate selectors
  1652.      *
  1653.      * @param array $selectors
  1654.      *
  1655.      * @return array
  1656.      */
  1657.     protected function evalSelectors($selectors)
  1658.     {
  1659.         $this->shouldEvaluate false;
  1660.         $selectors array_map([$this'evalSelector'], $selectors);
  1661.         // after evaluating interpolates, we might need a second pass
  1662.         if ($this->shouldEvaluate) {
  1663.             $selectors $this->replaceSelfSelector($selectors'&');
  1664.             $buffer    $this->collapseSelectors($selectors);
  1665.             $parser    $this->parserFactory(__METHOD__);
  1666.             try {
  1667.                 $isValid $parser->parseSelector($buffer$newSelectorstrue);
  1668.             } catch (ParserException $e) {
  1669.                 throw $this->error($e->getMessage());
  1670.             }
  1671.             if ($isValid) {
  1672.                 $selectors array_map([$this'evalSelector'], $newSelectors);
  1673.             }
  1674.         }
  1675.         return $selectors;
  1676.     }
  1677.     /**
  1678.      * Evaluate selector
  1679.      *
  1680.      * @param array $selector
  1681.      *
  1682.      * @return array
  1683.      */
  1684.     protected function evalSelector($selector)
  1685.     {
  1686.         return array_map([$this'evalSelectorPart'], $selector);
  1687.     }
  1688.     /**
  1689.      * Evaluate selector part; replaces all the interpolates, stripping quotes
  1690.      *
  1691.      * @param array $part
  1692.      *
  1693.      * @return array
  1694.      */
  1695.     protected function evalSelectorPart($part)
  1696.     {
  1697.         foreach ($part as &$p) {
  1698.             if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
  1699.                 $p $this->compileValue($p);
  1700.                 // force re-evaluation if self char or non standard char
  1701.                 if (preg_match(',[^\w-],'$p)) {
  1702.                     $this->shouldEvaluate true;
  1703.                 }
  1704.             } elseif (
  1705.                 \is_string($p) && \strlen($p) >= &&
  1706.                 ($first $p[0]) && ($first === '"' || $first === "'") &&
  1707.                 substr($p, -1) === $first
  1708.             ) {
  1709.                 $p substr($p1, -1);
  1710.             }
  1711.         }
  1712.         return $this->flattenSelectorSingle($part);
  1713.     }
  1714.     /**
  1715.      * Collapse selectors
  1716.      *
  1717.      * @param array $selectors
  1718.      *
  1719.      * @return string
  1720.      */
  1721.     protected function collapseSelectors($selectors)
  1722.     {
  1723.         $parts = [];
  1724.         foreach ($selectors as $selector) {
  1725.             $output = [];
  1726.             foreach ($selector as $node) {
  1727.                 $compound '';
  1728.                 array_walk_recursive(
  1729.                     $node,
  1730.                     function ($value$key) use (&$compound) {
  1731.                         $compound .= $value;
  1732.                     }
  1733.                 );
  1734.                 $output[] = $compound;
  1735.             }
  1736.             $parts[] = implode(' '$output);
  1737.         }
  1738.         return implode(', '$parts);
  1739.     }
  1740.     /**
  1741.      * Collapse selectors
  1742.      *
  1743.      * @param array $selectors
  1744.      *
  1745.      * @return array
  1746.      */
  1747.     private function collapseSelectorsAsList($selectors)
  1748.     {
  1749.         $parts = [];
  1750.         foreach ($selectors as $selector) {
  1751.             $output = [];
  1752.             $glueNext false;
  1753.             foreach ($selector as $node) {
  1754.                 $compound '';
  1755.                 array_walk_recursive(
  1756.                     $node,
  1757.                     function ($value$key) use (&$compound) {
  1758.                         $compound .= $value;
  1759.                     }
  1760.                 );
  1761.                 if ($this->isImmediateRelationshipCombinator($compound)) {
  1762.                     if (\count($output)) {
  1763.                         $output[\count($output) - 1] .= ' ' $compound;
  1764.                     } else {
  1765.                         $output[] = $compound;
  1766.                     }
  1767.                     $glueNext true;
  1768.                 } elseif ($glueNext) {
  1769.                     $output[\count($output) - 1] .= ' ' $compound;
  1770.                     $glueNext false;
  1771.                 } else {
  1772.                     $output[] = $compound;
  1773.                 }
  1774.             }
  1775.             foreach ($output as &$o) {
  1776.                 $o = [Type::T_STRING'', [$o]];
  1777.             }
  1778.             $parts[] = [Type::T_LIST' '$output];
  1779.         }
  1780.         return [Type::T_LIST','$parts];
  1781.     }
  1782.     /**
  1783.      * Parse down the selector and revert [self] to "&" before a reparsing
  1784.      *
  1785.      * @param array       $selectors
  1786.      * @param string|null $replace
  1787.      *
  1788.      * @return array
  1789.      */
  1790.     protected function replaceSelfSelector($selectors$replace null)
  1791.     {
  1792.         foreach ($selectors as &$part) {
  1793.             if (\is_array($part)) {
  1794.                 if ($part === [Type::T_SELF]) {
  1795.                     if (\is_null($replace)) {
  1796.                         $replace $this->reduce([Type::T_SELF]);
  1797.                         $replace $this->compileValue($replace);
  1798.                     }
  1799.                     $part $replace;
  1800.                 } else {
  1801.                     $part $this->replaceSelfSelector($part$replace);
  1802.                 }
  1803.             }
  1804.         }
  1805.         return $selectors;
  1806.     }
  1807.     /**
  1808.      * Flatten selector single; joins together .classes and #ids
  1809.      *
  1810.      * @param array $single
  1811.      *
  1812.      * @return array
  1813.      */
  1814.     protected function flattenSelectorSingle($single)
  1815.     {
  1816.         $joined = [];
  1817.         foreach ($single as $part) {
  1818.             if (
  1819.                 empty($joined) ||
  1820.                 ! \is_string($part) ||
  1821.                 preg_match('/[\[.:#%]/'$part)
  1822.             ) {
  1823.                 $joined[] = $part;
  1824.                 continue;
  1825.             }
  1826.             if (\is_array(end($joined))) {
  1827.                 $joined[] = $part;
  1828.             } else {
  1829.                 $joined[\count($joined) - 1] .= $part;
  1830.             }
  1831.         }
  1832.         return $joined;
  1833.     }
  1834.     /**
  1835.      * Compile selector to string; self(&) should have been replaced by now
  1836.      *
  1837.      * @param string|array $selector
  1838.      *
  1839.      * @return string
  1840.      */
  1841.     protected function compileSelector($selector)
  1842.     {
  1843.         if (! \is_array($selector)) {
  1844.             return $selector// media and the like
  1845.         }
  1846.         return implode(
  1847.             ' ',
  1848.             array_map(
  1849.                 [$this'compileSelectorPart'],
  1850.                 $selector
  1851.             )
  1852.         );
  1853.     }
  1854.     /**
  1855.      * Compile selector part
  1856.      *
  1857.      * @param array $piece
  1858.      *
  1859.      * @return string
  1860.      */
  1861.     protected function compileSelectorPart($piece)
  1862.     {
  1863.         foreach ($piece as &$p) {
  1864.             if (! \is_array($p)) {
  1865.                 continue;
  1866.             }
  1867.             switch ($p[0]) {
  1868.                 case Type::T_SELF:
  1869.                     $p '&';
  1870.                     break;
  1871.                 default:
  1872.                     $p $this->compileValue($p);
  1873.                     break;
  1874.             }
  1875.         }
  1876.         return implode($piece);
  1877.     }
  1878.     /**
  1879.      * Has selector placeholder?
  1880.      *
  1881.      * @param array $selector
  1882.      *
  1883.      * @return boolean
  1884.      */
  1885.     protected function hasSelectorPlaceholder($selector)
  1886.     {
  1887.         if (! \is_array($selector)) {
  1888.             return false;
  1889.         }
  1890.         foreach ($selector as $parts) {
  1891.             foreach ($parts as $part) {
  1892.                 if (\strlen($part) && '%' === $part[0]) {
  1893.                     return true;
  1894.                 }
  1895.             }
  1896.         }
  1897.         return false;
  1898.     }
  1899.     /**
  1900.      * @param string $name
  1901.      *
  1902.      * @return void
  1903.      */
  1904.     protected function pushCallStack($name '')
  1905.     {
  1906.         $this->callStack[] = [
  1907.           'n' => $name,
  1908.           Parser::SOURCE_INDEX => $this->sourceIndex,
  1909.           Parser::SOURCE_LINE => $this->sourceLine,
  1910.           Parser::SOURCE_COLUMN => $this->sourceColumn
  1911.         ];
  1912.         // infinite calling loop
  1913.         if (\count($this->callStack) > 25000) {
  1914.             // not displayed but you can var_dump it to deep debug
  1915.             $msg $this->callStackMessage(true100);
  1916.             $msg 'Infinite calling loop';
  1917.             throw $this->error($msg);
  1918.         }
  1919.     }
  1920.     /**
  1921.      * @return void
  1922.      */
  1923.     protected function popCallStack()
  1924.     {
  1925.         array_pop($this->callStack);
  1926.     }
  1927.     /**
  1928.      * Compile children and return result
  1929.      *
  1930.      * @param array                                  $stms
  1931.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  1932.      * @param string                                 $traceName
  1933.      *
  1934.      * @return array|Number|null
  1935.      */
  1936.     protected function compileChildren($stmsOutputBlock $out$traceName '')
  1937.     {
  1938.         $this->pushCallStack($traceName);
  1939.         foreach ($stms as $stm) {
  1940.             $ret $this->compileChild($stm$out);
  1941.             if (isset($ret)) {
  1942.                 $this->popCallStack();
  1943.                 return $ret;
  1944.             }
  1945.         }
  1946.         $this->popCallStack();
  1947.         return null;
  1948.     }
  1949.     /**
  1950.      * Compile children and throw exception if unexpected `@return`
  1951.      *
  1952.      * @param array                                  $stms
  1953.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  1954.      * @param \ScssPhp\ScssPhp\Block                 $selfParent
  1955.      * @param string                                 $traceName
  1956.      *
  1957.      * @return void
  1958.      *
  1959.      * @throws \Exception
  1960.      */
  1961.     protected function compileChildrenNoReturn($stmsOutputBlock $out$selfParent null$traceName '')
  1962.     {
  1963.         $this->pushCallStack($traceName);
  1964.         foreach ($stms as $stm) {
  1965.             if ($selfParent && isset($stm[1]) && \is_object($stm[1]) && $stm[1] instanceof Block) {
  1966.                 $stm[1]->selfParent $selfParent;
  1967.                 $ret $this->compileChild($stm$out);
  1968.                 $stm[1]->selfParent null;
  1969.             } elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDEType::T_EXTEND])) {
  1970.                 $stm['selfParent'] = $selfParent;
  1971.                 $ret $this->compileChild($stm$out);
  1972.                 unset($stm['selfParent']);
  1973.             } else {
  1974.                 $ret $this->compileChild($stm$out);
  1975.             }
  1976.             if (isset($ret)) {
  1977.                 throw $this->error('@return may only be used within a function');
  1978.             }
  1979.         }
  1980.         $this->popCallStack();
  1981.     }
  1982.     /**
  1983.      * evaluate media query : compile internal value keeping the structure unchanged
  1984.      *
  1985.      * @param array $queryList
  1986.      *
  1987.      * @return array
  1988.      */
  1989.     protected function evaluateMediaQuery($queryList)
  1990.     {
  1991.         static $parser null;
  1992.         $outQueryList = [];
  1993.         foreach ($queryList as $kql => $query) {
  1994.             $shouldReparse false;
  1995.             foreach ($query as $kq => $q) {
  1996.                 for ($i 1$i < \count($q); $i++) {
  1997.                     $value $this->compileValue($q[$i]);
  1998.                     // the parser had no mean to know if media type or expression if it was an interpolation
  1999.                     // so you need to reparse if the T_MEDIA_TYPE looks like anything else a media type
  2000.                     if (
  2001.                         $q[0] == Type::T_MEDIA_TYPE &&
  2002.                         (strpos($value'(') !== false ||
  2003.                         strpos($value')') !== false ||
  2004.                         strpos($value':') !== false ||
  2005.                         strpos($value',') !== false)
  2006.                     ) {
  2007.                         $shouldReparse true;
  2008.                     }
  2009.                     $queryList[$kql][$kq][$i] = [Type::T_KEYWORD$value];
  2010.                 }
  2011.             }
  2012.             if ($shouldReparse) {
  2013.                 if (\is_null($parser)) {
  2014.                     $parser $this->parserFactory(__METHOD__);
  2015.                 }
  2016.                 $queryString $this->compileMediaQuery([$queryList[$kql]]);
  2017.                 $queryString reset($queryString);
  2018.                 if (strpos($queryString'@media ') === 0) {
  2019.                     $queryString substr($queryString7);
  2020.                     $queries = [];
  2021.                     if ($parser->parseMediaQueryList($queryString$queries)) {
  2022.                         $queries $this->evaluateMediaQuery($queries[2]);
  2023.                         while (\count($queries)) {
  2024.                             $outQueryList[] = array_shift($queries);
  2025.                         }
  2026.                         continue;
  2027.                     }
  2028.                 }
  2029.             }
  2030.             $outQueryList[] = $queryList[$kql];
  2031.         }
  2032.         return $outQueryList;
  2033.     }
  2034.     /**
  2035.      * Compile media query
  2036.      *
  2037.      * @param array $queryList
  2038.      *
  2039.      * @return string[]
  2040.      */
  2041.     protected function compileMediaQuery($queryList)
  2042.     {
  2043.         $start   '@media ';
  2044.         $default trim($start);
  2045.         $out     = [];
  2046.         $current '';
  2047.         foreach ($queryList as $query) {
  2048.             $type null;
  2049.             $parts = [];
  2050.             $mediaTypeOnly true;
  2051.             foreach ($query as $q) {
  2052.                 if ($q[0] !== Type::T_MEDIA_TYPE) {
  2053.                     $mediaTypeOnly false;
  2054.                     break;
  2055.                 }
  2056.             }
  2057.             foreach ($query as $q) {
  2058.                 switch ($q[0]) {
  2059.                     case Type::T_MEDIA_TYPE:
  2060.                         $newType array_map([$this'compileValue'], \array_slice($q1));
  2061.                         // combining not and anything else than media type is too risky and should be avoided
  2062.                         if (! $mediaTypeOnly) {
  2063.                             if (\in_array(Type::T_NOT$newType) || ($type && \in_array(Type::T_NOT$type) )) {
  2064.                                 if ($type) {
  2065.                                     array_unshift($partsimplode(' 'array_filter($type)));
  2066.                                 }
  2067.                                 if (! empty($parts)) {
  2068.                                     if (\strlen($current)) {
  2069.                                         $current .= $this->formatter->tagSeparator;
  2070.                                     }
  2071.                                     $current .= implode(' and '$parts);
  2072.                                 }
  2073.                                 if ($current) {
  2074.                                     $out[] = $start $current;
  2075.                                 }
  2076.                                 $current '';
  2077.                                 $type    null;
  2078.                                 $parts   = [];
  2079.                             }
  2080.                         }
  2081.                         if ($newType === ['all'] && $default) {
  2082.                             $default $start 'all';
  2083.                         }
  2084.                         // all can be safely ignored and mixed with whatever else
  2085.                         if ($newType !== ['all']) {
  2086.                             if ($type) {
  2087.                                 $type $this->mergeMediaTypes($type$newType);
  2088.                                 if (empty($type)) {
  2089.                                     // merge failed : ignore this query that is not valid, skip to the next one
  2090.                                     $parts = [];
  2091.                                     $default ''// if everything fail, no @media at all
  2092.                                     continue 3;
  2093.                                 }
  2094.                             } else {
  2095.                                 $type $newType;
  2096.                             }
  2097.                         }
  2098.                         break;
  2099.                     case Type::T_MEDIA_EXPRESSION:
  2100.                         if (isset($q[2])) {
  2101.                             $parts[] = '('
  2102.                                 $this->compileValue($q[1])
  2103.                                 . $this->formatter->assignSeparator
  2104.                                 $this->compileValue($q[2])
  2105.                                 . ')';
  2106.                         } else {
  2107.                             $parts[] = '('
  2108.                                 $this->compileValue($q[1])
  2109.                                 . ')';
  2110.                         }
  2111.                         break;
  2112.                     case Type::T_MEDIA_VALUE:
  2113.                         $parts[] = $this->compileValue($q[1]);
  2114.                         break;
  2115.                 }
  2116.             }
  2117.             if ($type) {
  2118.                 array_unshift($partsimplode(' 'array_filter($type)));
  2119.             }
  2120.             if (! empty($parts)) {
  2121.                 if (\strlen($current)) {
  2122.                     $current .= $this->formatter->tagSeparator;
  2123.                 }
  2124.                 $current .= implode(' and '$parts);
  2125.             }
  2126.         }
  2127.         if ($current) {
  2128.             $out[] = $start $current;
  2129.         }
  2130.         // no @media type except all, and no conflict?
  2131.         if (! $out && $default) {
  2132.             $out[] = $default;
  2133.         }
  2134.         return $out;
  2135.     }
  2136.     /**
  2137.      * Merge direct relationships between selectors
  2138.      *
  2139.      * @param array $selectors1
  2140.      * @param array $selectors2
  2141.      *
  2142.      * @return array
  2143.      */
  2144.     protected function mergeDirectRelationships($selectors1$selectors2)
  2145.     {
  2146.         if (empty($selectors1) || empty($selectors2)) {
  2147.             return array_merge($selectors1$selectors2);
  2148.         }
  2149.         $part1 end($selectors1);
  2150.         $part2 end($selectors2);
  2151.         if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
  2152.             return array_merge($selectors1$selectors2);
  2153.         }
  2154.         $merged = [];
  2155.         do {
  2156.             $part1 array_pop($selectors1);
  2157.             $part2 array_pop($selectors2);
  2158.             if (! $this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
  2159.                 if ($this->isImmediateRelationshipCombinator(reset($merged)[0])) {
  2160.                     array_unshift($merged, [$part1[0] . $part2[0]]);
  2161.                     $merged array_merge($selectors1$selectors2$merged);
  2162.                 } else {
  2163.                     $merged array_merge($selectors1, [$part1], $selectors2, [$part2], $merged);
  2164.                 }
  2165.                 break;
  2166.             }
  2167.             array_unshift($merged$part1);
  2168.         } while (! empty($selectors1) && ! empty($selectors2));
  2169.         return $merged;
  2170.     }
  2171.     /**
  2172.      * Merge media types
  2173.      *
  2174.      * @param array $type1
  2175.      * @param array $type2
  2176.      *
  2177.      * @return array|null
  2178.      */
  2179.     protected function mergeMediaTypes($type1$type2)
  2180.     {
  2181.         if (empty($type1)) {
  2182.             return $type2;
  2183.         }
  2184.         if (empty($type2)) {
  2185.             return $type1;
  2186.         }
  2187.         if (\count($type1) > 1) {
  2188.             $m1 strtolower($type1[0]);
  2189.             $t1 strtolower($type1[1]);
  2190.         } else {
  2191.             $m1 '';
  2192.             $t1 strtolower($type1[0]);
  2193.         }
  2194.         if (\count($type2) > 1) {
  2195.             $m2 strtolower($type2[0]);
  2196.             $t2 strtolower($type2[1]);
  2197.         } else {
  2198.             $m2 '';
  2199.             $t2 strtolower($type2[0]);
  2200.         }
  2201.         if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) {
  2202.             if ($t1 === $t2) {
  2203.                 return null;
  2204.             }
  2205.             return [
  2206.                 $m1 === Type::T_NOT $m2 $m1,
  2207.                 $m1 === Type::T_NOT $t2 $t1,
  2208.             ];
  2209.         }
  2210.         if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) {
  2211.             // CSS has no way of representing "neither screen nor print"
  2212.             if ($t1 !== $t2) {
  2213.                 return null;
  2214.             }
  2215.             return [Type::T_NOT$t1];
  2216.         }
  2217.         if ($t1 !== $t2) {
  2218.             return null;
  2219.         }
  2220.         // t1 == t2, neither m1 nor m2 are "not"
  2221.         return [empty($m1) ? $m2 $m1$t1];
  2222.     }
  2223.     /**
  2224.      * Compile import; returns true if the value was something that could be imported
  2225.      *
  2226.      * @param array                                  $rawPath
  2227.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  2228.      * @param boolean                                $once
  2229.      *
  2230.      * @return boolean
  2231.      */
  2232.     protected function compileImport($rawPathOutputBlock $out$once false)
  2233.     {
  2234.         if ($rawPath[0] === Type::T_STRING) {
  2235.             $path $this->compileStringContent($rawPath);
  2236.             if (strpos($path'url(') !== && $filePath $this->findImport($path$this->currentDirectory)) {
  2237.                 $this->registerImport($this->currentDirectory$path$filePath);
  2238.                 if (! $once || ! \in_array($filePath$this->importedFiles)) {
  2239.                     $this->importFile($filePath$out);
  2240.                     $this->importedFiles[] = $filePath;
  2241.                 }
  2242.                 return true;
  2243.             }
  2244.             $this->appendRootDirective('@import ' $this->compileImportPath($rawPath) . ';'$out);
  2245.             return false;
  2246.         }
  2247.         if ($rawPath[0] === Type::T_LIST) {
  2248.             // handle a list of strings
  2249.             if (\count($rawPath[2]) === 0) {
  2250.                 return false;
  2251.             }
  2252.             foreach ($rawPath[2] as $path) {
  2253.                 if ($path[0] !== Type::T_STRING) {
  2254.                     $this->appendRootDirective('@import ' $this->compileImportPath($rawPath) . ';'$out);
  2255.                     return false;
  2256.                 }
  2257.             }
  2258.             foreach ($rawPath[2] as $path) {
  2259.                 $this->compileImport($path$out$once);
  2260.             }
  2261.             return true;
  2262.         }
  2263.         $this->appendRootDirective('@import ' $this->compileImportPath($rawPath) . ';'$out);
  2264.         return false;
  2265.     }
  2266.     /**
  2267.      * @param array $rawPath
  2268.      * @return string
  2269.      * @throws CompilerException
  2270.      */
  2271.     protected function compileImportPath($rawPath)
  2272.     {
  2273.         $path $this->compileValue($rawPath);
  2274.         // case url() without quotes : suppress \r \n remaining in the path
  2275.         // if this is a real string there can not be CR or LF char
  2276.         if (strpos($path'url(') === 0) {
  2277.             $path str_replace(array("\r""\n"), array(''' '), $path);
  2278.         } else {
  2279.             // if this is a file name in a string, spaces should be escaped
  2280.             $path $this->reduce($rawPath);
  2281.             $path $this->escapeImportPathString($path);
  2282.             $path $this->compileValue($path);
  2283.         }
  2284.         return $path;
  2285.     }
  2286.     /**
  2287.      * @param array $path
  2288.      * @return array
  2289.      * @throws CompilerException
  2290.      */
  2291.     protected function escapeImportPathString($path)
  2292.     {
  2293.         switch ($path[0]) {
  2294.             case Type::T_LIST:
  2295.                 foreach ($path[2] as $k => $v) {
  2296.                     $path[2][$k] = $this->escapeImportPathString($v);
  2297.                 }
  2298.                 break;
  2299.             case Type::T_STRING:
  2300.                 if ($path[1]) {
  2301.                     $path $this->compileValue($path);
  2302.                     $path str_replace(' ''\\ '$path);
  2303.                     $path = [Type::T_KEYWORD$path];
  2304.                 }
  2305.                 break;
  2306.         }
  2307.         return $path;
  2308.     }
  2309.     /**
  2310.      * Append a root directive like @import or @charset as near as the possible from the source code
  2311.      * (keeping before comments, @import and @charset coming before in the source code)
  2312.      *
  2313.      * @param string                                 $line
  2314.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  2315.      * @param array                                  $allowed
  2316.      *
  2317.      * @return void
  2318.      */
  2319.     protected function appendRootDirective($line$out$allowed = [Type::T_COMMENT])
  2320.     {
  2321.         $root $out;
  2322.         while ($root->parent) {
  2323.             $root $root->parent;
  2324.         }
  2325.         $i 0;
  2326.         while ($i < \count($root->children)) {
  2327.             if (! isset($root->children[$i]->type) || ! \in_array($root->children[$i]->type$allowed)) {
  2328.                 break;
  2329.             }
  2330.             $i++;
  2331.         }
  2332.         // remove incompatible children from the bottom of the list
  2333.         $saveChildren = [];
  2334.         while ($i < \count($root->children)) {
  2335.             $saveChildren[] = array_pop($root->children);
  2336.         }
  2337.         // insert the directive as a comment
  2338.         $child $this->makeOutputBlock(Type::T_COMMENT);
  2339.         $child->lines[]      = $line;
  2340.         $child->sourceName   $this->sourceNames[$this->sourceIndex];
  2341.         $child->sourceLine   $this->sourceLine;
  2342.         $child->sourceColumn $this->sourceColumn;
  2343.         $root->children[] = $child;
  2344.         // repush children
  2345.         while (\count($saveChildren)) {
  2346.             $root->children[] = array_pop($saveChildren);
  2347.         }
  2348.     }
  2349.     /**
  2350.      * Append lines to the current output block:
  2351.      * directly to the block or through a child if necessary
  2352.      *
  2353.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  2354.      * @param string                                 $type
  2355.      * @param string                                 $line
  2356.      *
  2357.      * @return void
  2358.      */
  2359.     protected function appendOutputLine(OutputBlock $out$type$line)
  2360.     {
  2361.         $outWrite = &$out;
  2362.         // check if it's a flat output or not
  2363.         if (\count($out->children)) {
  2364.             $lastChild = &$out->children[\count($out->children) - 1];
  2365.             if (
  2366.                 $lastChild->depth === $out->depth &&
  2367.                 \is_null($lastChild->selectors) &&
  2368.                 ! \count($lastChild->children)
  2369.             ) {
  2370.                 $outWrite $lastChild;
  2371.             } else {
  2372.                 $nextLines $this->makeOutputBlock($type);
  2373.                 $nextLines->parent $out;
  2374.                 $nextLines->depth  $out->depth;
  2375.                 $out->children[] = $nextLines;
  2376.                 $outWrite = &$nextLines;
  2377.             }
  2378.         }
  2379.         $outWrite->lines[] = $line;
  2380.     }
  2381.     /**
  2382.      * Compile child; returns a value to halt execution
  2383.      *
  2384.      * @param array                                  $child
  2385.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  2386.      *
  2387.      * @return array|Number|null
  2388.      */
  2389.     protected function compileChild($childOutputBlock $out)
  2390.     {
  2391.         if (isset($child[Parser::SOURCE_LINE])) {
  2392.             $this->sourceIndex  = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
  2393.             $this->sourceLine   = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
  2394.             $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
  2395.         } elseif (\is_array($child) && isset($child[1]->sourceLine)) {
  2396.             $this->sourceIndex  $child[1]->sourceIndex;
  2397.             $this->sourceLine   $child[1]->sourceLine;
  2398.             $this->sourceColumn $child[1]->sourceColumn;
  2399.         } elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
  2400.             $this->sourceLine   $out->sourceLine;
  2401.             $sourceIndex  array_search($out->sourceName$this->sourceNames);
  2402.             $this->sourceColumn $out->sourceColumn;
  2403.             if ($sourceIndex === false) {
  2404.                 $sourceIndex null;
  2405.             }
  2406.             $this->sourceIndex $sourceIndex;
  2407.         }
  2408.         switch ($child[0]) {
  2409.             case Type::T_SCSSPHP_IMPORT_ONCE:
  2410.                 $rawPath $this->reduce($child[1]);
  2411.                 $this->compileImport($rawPath$outtrue);
  2412.                 break;
  2413.             case Type::T_IMPORT:
  2414.                 $rawPath $this->reduce($child[1]);
  2415.                 $this->compileImport($rawPath$out);
  2416.                 break;
  2417.             case Type::T_DIRECTIVE:
  2418.                 $this->compileDirective($child[1], $out);
  2419.                 break;
  2420.             case Type::T_AT_ROOT:
  2421.                 $this->compileAtRoot($child[1]);
  2422.                 break;
  2423.             case Type::T_MEDIA:
  2424.                 $this->compileMedia($child[1]);
  2425.                 break;
  2426.             case Type::T_BLOCK:
  2427.                 $this->compileBlock($child[1]);
  2428.                 break;
  2429.             case Type::T_CHARSET:
  2430.                 break;
  2431.             case Type::T_CUSTOM_PROPERTY:
  2432.                 list(, $name$value) = $child;
  2433.                 $compiledName $this->compileValue($name);
  2434.                 // if the value reduces to null from something else then
  2435.                 // the property should be discarded
  2436.                 if ($value[0] !== Type::T_NULL) {
  2437.                     $value $this->reduce($value);
  2438.                     if ($value[0] === Type::T_NULL || $value === static::$nullString) {
  2439.                         break;
  2440.                     }
  2441.                 }
  2442.                 $compiledValue $this->compileValue($value);
  2443.                 $line $this->formatter->customProperty(
  2444.                     $compiledName,
  2445.                     $compiledValue
  2446.                 );
  2447.                 $this->appendOutputLine($outType::T_ASSIGN$line);
  2448.                 break;
  2449.             case Type::T_ASSIGN:
  2450.                 list(, $name$value) = $child;
  2451.                 if ($name[0] === Type::T_VARIABLE) {
  2452.                     $flags     = isset($child[3]) ? $child[3] : [];
  2453.                     $isDefault = \in_array('!default'$flags);
  2454.                     $isGlobal  = \in_array('!global'$flags);
  2455.                     if ($isGlobal) {
  2456.                         $this->set($name[1], $this->reduce($value), false$this->rootEnv$value);
  2457.                         break;
  2458.                     }
  2459.                     $shouldSet $isDefault &&
  2460.                         (\is_null($result $this->get($name[1], false)) ||
  2461.                         $result === static::$null);
  2462.                     if (! $isDefault || $shouldSet) {
  2463.                         $this->set($name[1], $this->reduce($value), truenull$value);
  2464.                     }
  2465.                     break;
  2466.                 }
  2467.                 $compiledName $this->compileValue($name);
  2468.                 // handle shorthand syntaxes : size / line-height...
  2469.                 if (\in_array($compiledName, ['font''grid-row''grid-column''border-radius'])) {
  2470.                     if ($value[0] === Type::T_VARIABLE) {
  2471.                         // if the font value comes from variable, the content is already reduced
  2472.                         // (i.e., formulas were already calculated), so we need the original unreduced value
  2473.                         $value $this->get($value[1], truenulltrue);
  2474.                     }
  2475.                     $shorthandValue=&$value;
  2476.                     $shorthandDividerNeedsUnit false;
  2477.                     $maxListElements           null;
  2478.                     $maxShorthandDividers      1;
  2479.                     switch ($compiledName) {
  2480.                         case 'border-radius':
  2481.                             $maxListElements 4;
  2482.                             $shorthandDividerNeedsUnit true;
  2483.                             break;
  2484.                     }
  2485.                     if ($compiledName === 'font' && $value[0] === Type::T_LIST && $value[1] === ',') {
  2486.                         // this is the case if more than one font is given: example: "font: 400 1em/1.3 arial,helvetica"
  2487.                         // we need to handle the first list element
  2488.                         $shorthandValue=&$value[2][0];
  2489.                     }
  2490.                     if ($shorthandValue[0] === Type::T_EXPRESSION && $shorthandValue[1] === '/') {
  2491.                         $revert true;
  2492.                         if ($shorthandDividerNeedsUnit) {
  2493.                             $divider $shorthandValue[3];
  2494.                             if (\is_array($divider)) {
  2495.                                 $divider $this->reduce($dividertrue);
  2496.                             }
  2497.                             if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
  2498.                                 $revert false;
  2499.                             }
  2500.                         }
  2501.                         if ($revert) {
  2502.                             $shorthandValue $this->expToString($shorthandValue);
  2503.                         }
  2504.                     } elseif ($shorthandValue[0] === Type::T_LIST) {
  2505.                         foreach ($shorthandValue[2] as &$item) {
  2506.                             if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
  2507.                                 if ($maxShorthandDividers 0) {
  2508.                                     $revert true;
  2509.                                     // if the list of values is too long, this has to be a shorthand,
  2510.                                     // otherwise it could be a real division
  2511.                                     if (\is_null($maxListElements) || \count($shorthandValue[2]) <= $maxListElements) {
  2512.                                         if ($shorthandDividerNeedsUnit) {
  2513.                                             $divider $item[3];
  2514.                                             if (\is_array($divider)) {
  2515.                                                 $divider $this->reduce($dividertrue);
  2516.                                             }
  2517.                                             if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
  2518.                                                 $revert false;
  2519.                                             }
  2520.                                         }
  2521.                                     }
  2522.                                     if ($revert) {
  2523.                                         $item $this->expToString($item);
  2524.                                         $maxShorthandDividers--;
  2525.                                     }
  2526.                                 }
  2527.                             }
  2528.                         }
  2529.                     }
  2530.                 }
  2531.                 // if the value reduces to null from something else then
  2532.                 // the property should be discarded
  2533.                 if ($value[0] !== Type::T_NULL) {
  2534.                     $value $this->reduce($value);
  2535.                     if ($value[0] === Type::T_NULL || $value === static::$nullString) {
  2536.                         break;
  2537.                     }
  2538.                 }
  2539.                 $compiledValue $this->compileValue($value);
  2540.                 // ignore empty value
  2541.                 if (\strlen($compiledValue)) {
  2542.                     $line $this->formatter->property(
  2543.                         $compiledName,
  2544.                         $compiledValue
  2545.                     );
  2546.                     $this->appendOutputLine($outType::T_ASSIGN$line);
  2547.                 }
  2548.                 break;
  2549.             case Type::T_COMMENT:
  2550.                 if ($out->type === Type::T_ROOT) {
  2551.                     $this->compileComment($child);
  2552.                     break;
  2553.                 }
  2554.                 $line $this->compileCommentValue($childtrue);
  2555.                 $this->appendOutputLine($outType::T_COMMENT$line);
  2556.                 break;
  2557.             case Type::T_MIXIN:
  2558.             case Type::T_FUNCTION:
  2559.                 list(, $block) = $child;
  2560.                 // the block need to be able to go up to it's parent env to resolve vars
  2561.                 $block->parentEnv $this->getStoreEnv();
  2562.                 $this->set(static::$namespaces[$block->type] . $block->name$blocktrue);
  2563.                 break;
  2564.             case Type::T_EXTEND:
  2565.                 foreach ($child[1] as $sel) {
  2566.                     $replacedSel $this->replaceSelfSelector($sel);
  2567.                     if ($replacedSel !== $sel) {
  2568.                         throw $this->error('Parent selectors aren\'t allowed here.');
  2569.                     }
  2570.                     $results $this->evalSelectors([$sel]);
  2571.                     foreach ($results as $result) {
  2572.                         if (\count($result) !== 1) {
  2573.                             throw $this->error('complex selectors may not be extended.');
  2574.                         }
  2575.                         // only use the first one
  2576.                         $result $result[0];
  2577.                         $selectors $out->selectors;
  2578.                         if (! $selectors && isset($child['selfParent'])) {
  2579.                             $selectors $this->multiplySelectors($this->env$child['selfParent']);
  2580.                         }
  2581.                         if (\count($result) > 1) {
  2582.                             $replacement implode(', '$result);
  2583.                             $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  2584.                             $line $this->sourceLine;
  2585.                             $message = <<<EOL
  2586. on line $line of $fname:
  2587. Compound selectors may no longer be extended.
  2588. Consider `@extend $replacement` instead.
  2589. See http://bit.ly/ExtendCompound for details.
  2590. EOL;
  2591.                             $this->logger->warn($message);
  2592.                         }
  2593.                         $this->pushExtends($result$selectors$child);
  2594.                     }
  2595.                 }
  2596.                 break;
  2597.             case Type::T_IF:
  2598.                 list(, $if) = $child;
  2599.                 if ($this->isTruthy($this->reduce($if->condtrue))) {
  2600.                     return $this->compileChildren($if->children$out);
  2601.                 }
  2602.                 foreach ($if->cases as $case) {
  2603.                     if (
  2604.                         $case->type === Type::T_ELSE ||
  2605.                         $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
  2606.                     ) {
  2607.                         return $this->compileChildren($case->children$out);
  2608.                     }
  2609.                 }
  2610.                 break;
  2611.             case Type::T_EACH:
  2612.                 list(, $each) = $child;
  2613.                 $list $this->coerceList($this->reduce($each->list), ','true);
  2614.                 $this->pushEnv();
  2615.                 foreach ($list[2] as $item) {
  2616.                     if (\count($each->vars) === 1) {
  2617.                         $this->set($each->vars[0], $itemtrue);
  2618.                     } else {
  2619.                         list(,, $values) = $this->coerceList($item);
  2620.                         foreach ($each->vars as $i => $var) {
  2621.                             $this->set($var, isset($values[$i]) ? $values[$i] : static::$nulltrue);
  2622.                         }
  2623.                     }
  2624.                     $ret $this->compileChildren($each->children$out);
  2625.                     if ($ret) {
  2626.                         $store $this->env->store;
  2627.                         $this->popEnv();
  2628.                         $this->backPropagateEnv($store$each->vars);
  2629.                         return $ret;
  2630.                     }
  2631.                 }
  2632.                 $store $this->env->store;
  2633.                 $this->popEnv();
  2634.                 $this->backPropagateEnv($store$each->vars);
  2635.                 break;
  2636.             case Type::T_WHILE:
  2637.                 list(, $while) = $child;
  2638.                 while ($this->isTruthy($this->reduce($while->condtrue))) {
  2639.                     $ret $this->compileChildren($while->children$out);
  2640.                     if ($ret) {
  2641.                         return $ret;
  2642.                     }
  2643.                 }
  2644.                 break;
  2645.             case Type::T_FOR:
  2646.                 list(, $for) = $child;
  2647.                 $startNumber $this->assertNumber($this->reduce($for->starttrue));
  2648.                 $endNumber $this->assertNumber($this->reduce($for->endtrue));
  2649.                 $start $this->assertInteger($startNumber);
  2650.                 $numeratorUnits $startNumber->getNumeratorUnits();
  2651.                 $denominatorUnits $startNumber->getDenominatorUnits();
  2652.                 $end $this->assertInteger($endNumber->coerce($numeratorUnits$denominatorUnits));
  2653.                 $d $start $end : -1;
  2654.                 $this->pushEnv();
  2655.                 for (;;) {
  2656.                     if (
  2657.                         (! $for->until && $start $d == $end) ||
  2658.                         ($for->until && $start == $end)
  2659.                     ) {
  2660.                         break;
  2661.                     }
  2662.                     $this->set($for->var, new Number($start$numeratorUnits$denominatorUnits));
  2663.                     $start += $d;
  2664.                     $ret $this->compileChildren($for->children$out);
  2665.                     if ($ret) {
  2666.                         $store $this->env->store;
  2667.                         $this->popEnv();
  2668.                         $this->backPropagateEnv($store, [$for->var]);
  2669.                         return $ret;
  2670.                     }
  2671.                 }
  2672.                 $store $this->env->store;
  2673.                 $this->popEnv();
  2674.                 $this->backPropagateEnv($store, [$for->var]);
  2675.                 break;
  2676.             case Type::T_RETURN:
  2677.                 return $this->reduce($child[1], true);
  2678.             case Type::T_NESTED_PROPERTY:
  2679.                 $this->compileNestedPropertiesBlock($child[1], $out);
  2680.                 break;
  2681.             case Type::T_INCLUDE:
  2682.                 // including a mixin
  2683.                 list(, $name$argValues$content$argUsing) = $child;
  2684.                 $mixin $this->get(static::$namespaces['mixin'] . $namefalse);
  2685.                 if (! $mixin) {
  2686.                     throw $this->error("Undefined mixin $name");
  2687.                 }
  2688.                 $callingScope $this->getStoreEnv();
  2689.                 // push scope, apply args
  2690.                 $this->pushEnv();
  2691.                 $this->env->depth--;
  2692.                 // Find the parent selectors in the env to be able to know what '&' refers to in the mixin
  2693.                 // and assign this fake parent to childs
  2694.                 $selfParent null;
  2695.                 if (isset($child['selfParent']) && isset($child['selfParent']->selectors)) {
  2696.                     $selfParent $child['selfParent'];
  2697.                 } else {
  2698.                     $parentSelectors $this->multiplySelectors($this->env);
  2699.                     if ($parentSelectors) {
  2700.                         $parent = new Block();
  2701.                         $parent->selectors $parentSelectors;
  2702.                         foreach ($mixin->children as $k => $child) {
  2703.                             if (isset($child[1]) && \is_object($child[1]) && $child[1] instanceof Block) {
  2704.                                 $mixin->children[$k][1]->parent $parent;
  2705.                             }
  2706.                         }
  2707.                     }
  2708.                 }
  2709.                 // clone the stored content to not have its scope spoiled by a further call to the same mixin
  2710.                 // i.e., recursive @include of the same mixin
  2711.                 if (isset($content)) {
  2712.                     $copyContent = clone $content;
  2713.                     $copyContent->scope = clone $callingScope;
  2714.                     $this->setRaw(static::$namespaces['special'] . 'content'$copyContent$this->env);
  2715.                 } else {
  2716.                     $this->setRaw(static::$namespaces['special'] . 'content'null$this->env);
  2717.                 }
  2718.                 // save the "using" argument list for applying it to when "@content" is invoked
  2719.                 if (isset($argUsing)) {
  2720.                     $this->setRaw(static::$namespaces['special'] . 'using'$argUsing$this->env);
  2721.                 } else {
  2722.                     $this->setRaw(static::$namespaces['special'] . 'using'null$this->env);
  2723.                 }
  2724.                 if (isset($mixin->args)) {
  2725.                     $this->applyArguments($mixin->args$argValues);
  2726.                 }
  2727.                 $this->env->marker 'mixin';
  2728.                 if (! empty($mixin->parentEnv)) {
  2729.                     $this->env->declarationScopeParent $mixin->parentEnv;
  2730.                 } else {
  2731.                     throw $this->error("@mixin $name() without parentEnv");
  2732.                 }
  2733.                 $this->compileChildrenNoReturn($mixin->children$out$selfParent$this->env->marker ' ' $name);
  2734.                 $this->popEnv();
  2735.                 break;
  2736.             case Type::T_MIXIN_CONTENT:
  2737.                 $env        = isset($this->storeEnv) ? $this->storeEnv $this->env;
  2738.                 $content    $this->get(static::$namespaces['special'] . 'content'false$env);
  2739.                 $argUsing   $this->get(static::$namespaces['special'] . 'using'false$env);
  2740.                 $argContent $child[1];
  2741.                 if (! $content) {
  2742.                     break;
  2743.                 }
  2744.                 $storeEnv $this->storeEnv;
  2745.                 $varsUsing = [];
  2746.                 if (isset($argUsing) && isset($argContent)) {
  2747.                     // Get the arguments provided for the content with the names provided in the "using" argument list
  2748.                     $this->storeEnv null;
  2749.                     $varsUsing $this->applyArguments($argUsing$argContentfalse);
  2750.                 }
  2751.                 // restore the scope from the @content
  2752.                 $this->storeEnv $content->scope;
  2753.                 // append the vars from using if any
  2754.                 foreach ($varsUsing as $name => $val) {
  2755.                     $this->set($name$valtrue$this->storeEnv);
  2756.                 }
  2757.                 $this->compileChildrenNoReturn($content->children$out);
  2758.                 $this->storeEnv $storeEnv;
  2759.                 break;
  2760.             case Type::T_DEBUG:
  2761.                 list(, $value) = $child;
  2762.                 $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  2763.                 $line  $this->sourceLine;
  2764.                 $value $this->compileDebugValue($value);
  2765.                 $this->logger->debug("$fname:$line DEBUG: $value");
  2766.                 break;
  2767.             case Type::T_WARN:
  2768.                 list(, $value) = $child;
  2769.                 $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  2770.                 $line  $this->sourceLine;
  2771.                 $value $this->compileDebugValue($value);
  2772.                 $this->logger->warn("$value\n         on line $line of $fname");
  2773.                 break;
  2774.             case Type::T_ERROR:
  2775.                 list(, $value) = $child;
  2776.                 $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  2777.                 $line  $this->sourceLine;
  2778.                 $value $this->compileValue($this->reduce($valuetrue));
  2779.                 throw $this->error("File $fname on line $line ERROR: $value\n");
  2780.             default:
  2781.                 throw $this->error("unknown child type: $child[0]");
  2782.         }
  2783.     }
  2784.     /**
  2785.      * Reduce expression to string
  2786.      *
  2787.      * @param array $exp
  2788.      * @param bool $keepParens
  2789.      *
  2790.      * @return array
  2791.      */
  2792.     protected function expToString($exp$keepParens false)
  2793.     {
  2794.         list(, $op$left$right$inParens$whiteLeft$whiteRight) = $exp;
  2795.         $content = [];
  2796.         if ($keepParens && $inParens) {
  2797.             $content[] = '(';
  2798.         }
  2799.         $content[] = $this->reduce($left);
  2800.         if ($whiteLeft) {
  2801.             $content[] = ' ';
  2802.         }
  2803.         $content[] = $op;
  2804.         if ($whiteRight) {
  2805.             $content[] = ' ';
  2806.         }
  2807.         $content[] = $this->reduce($right);
  2808.         if ($keepParens && $inParens) {
  2809.             $content[] = ')';
  2810.         }
  2811.         return [Type::T_STRING''$content];
  2812.     }
  2813.     /**
  2814.      * Is truthy?
  2815.      *
  2816.      * @param array|Number $value
  2817.      *
  2818.      * @return boolean
  2819.      */
  2820.     public function isTruthy($value)
  2821.     {
  2822.         return $value !== static::$false && $value !== static::$null;
  2823.     }
  2824.     /**
  2825.      * Is the value a direct relationship combinator?
  2826.      *
  2827.      * @param string $value
  2828.      *
  2829.      * @return boolean
  2830.      */
  2831.     protected function isImmediateRelationshipCombinator($value)
  2832.     {
  2833.         return $value === '>' || $value === '+' || $value === '~';
  2834.     }
  2835.     /**
  2836.      * Should $value cause its operand to eval
  2837.      *
  2838.      * @param array $value
  2839.      *
  2840.      * @return boolean
  2841.      */
  2842.     protected function shouldEval($value)
  2843.     {
  2844.         switch ($value[0]) {
  2845.             case Type::T_EXPRESSION:
  2846.                 if ($value[1] === '/') {
  2847.                     return $this->shouldEval($value[2]) || $this->shouldEval($value[3]);
  2848.                 }
  2849.                 // fall-thru
  2850.             case Type::T_VARIABLE:
  2851.             case Type::T_FUNCTION_CALL:
  2852.                 return true;
  2853.         }
  2854.         return false;
  2855.     }
  2856.     /**
  2857.      * Reduce value
  2858.      *
  2859.      * @param array|Number $value
  2860.      * @param boolean $inExp
  2861.      *
  2862.      * @return array|Number
  2863.      */
  2864.     protected function reduce($value$inExp false)
  2865.     {
  2866.         if ($value instanceof Number) {
  2867.             return $value;
  2868.         }
  2869.         switch ($value[0]) {
  2870.             case Type::T_EXPRESSION:
  2871.                 list(, $op$left$right$inParens) = $value;
  2872.                 $opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op;
  2873.                 $inExp $inExp || $this->shouldEval($left) || $this->shouldEval($right);
  2874.                 $left $this->reduce($lefttrue);
  2875.                 if ($op !== 'and' && $op !== 'or') {
  2876.                     $right $this->reduce($righttrue);
  2877.                 }
  2878.                 // special case: looks like css shorthand
  2879.                 if (
  2880.                     $opName == 'div' && ! $inParens && ! $inExp &&
  2881.                     (($right[0] !== Type::T_NUMBER && isset($right[2]) && $right[2] != '') ||
  2882.                     ($right[0] === Type::T_NUMBER && ! $right->unitless()))
  2883.                 ) {
  2884.                     return $this->expToString($value);
  2885.                 }
  2886.                 $left  $this->coerceForExpression($left);
  2887.                 $right $this->coerceForExpression($right);
  2888.                 $ltype $left[0];
  2889.                 $rtype $right[0];
  2890.                 $ucOpName ucfirst($opName);
  2891.                 $ucLType  ucfirst($ltype);
  2892.                 $ucRType  ucfirst($rtype);
  2893.                 // this tries:
  2894.                 // 1. op[op name][left type][right type]
  2895.                 // 2. op[left type][right type] (passing the op as first arg
  2896.                 // 3. op[op name]
  2897.                 $fn "op${ucOpName}${ucLType}${ucRType}";
  2898.                 if (
  2899.                     \is_callable([$this$fn]) ||
  2900.                     (($fn "op${ucLType}${ucRType}") &&
  2901.                         \is_callable([$this$fn]) &&
  2902.                         $passOp true) ||
  2903.                     (($fn "op${ucOpName}") &&
  2904.                         \is_callable([$this$fn]) &&
  2905.                         $genOp true)
  2906.                 ) {
  2907.                     $shouldEval $inParens || $inExp;
  2908.                     if (isset($passOp)) {
  2909.                         $out $this->$fn($op$left$right$shouldEval);
  2910.                     } else {
  2911.                         $out $this->$fn($left$right$shouldEval);
  2912.                     }
  2913.                     if (isset($out)) {
  2914.                         return $out;
  2915.                     }
  2916.                 }
  2917.                 return $this->expToString($value);
  2918.             case Type::T_UNARY:
  2919.                 list(, $op$exp$inParens) = $value;
  2920.                 $inExp $inExp || $this->shouldEval($exp);
  2921.                 $exp $this->reduce($exp);
  2922.                 if ($exp instanceof Number) {
  2923.                     switch ($op) {
  2924.                         case '+':
  2925.                             return $exp;
  2926.                         case '-':
  2927.                             return $exp->unaryMinus();
  2928.                     }
  2929.                 }
  2930.                 if ($op === 'not') {
  2931.                     if ($inExp || $inParens) {
  2932.                         if ($exp === static::$false || $exp === static::$null) {
  2933.                             return static::$true;
  2934.                         }
  2935.                         return static::$false;
  2936.                     }
  2937.                     $op $op ' ';
  2938.                 }
  2939.                 return [Type::T_STRING'', [$op$exp]];
  2940.             case Type::T_VARIABLE:
  2941.                 return $this->reduce($this->get($value[1]));
  2942.             case Type::T_LIST:
  2943.                 foreach ($value[2] as &$item) {
  2944.                     $item $this->reduce($item);
  2945.                 }
  2946.                 unset($item);
  2947.                 if (isset($value[3]) && \is_array($value[3])) {
  2948.                     foreach ($value[3] as &$item) {
  2949.                         $item $this->reduce($item);
  2950.                     }
  2951.                     unset($item);
  2952.                 }
  2953.                 return $value;
  2954.             case Type::T_MAP:
  2955.                 foreach ($value[1] as &$item) {
  2956.                     $item $this->reduce($item);
  2957.                 }
  2958.                 foreach ($value[2] as &$item) {
  2959.                     $item $this->reduce($item);
  2960.                 }
  2961.                 return $value;
  2962.             case Type::T_STRING:
  2963.                 foreach ($value[2] as &$item) {
  2964.                     if (\is_array($item) || $item instanceof Number) {
  2965.                         $item $this->reduce($item);
  2966.                     }
  2967.                 }
  2968.                 return $value;
  2969.             case Type::T_INTERPOLATE:
  2970.                 $value[1] = $this->reduce($value[1]);
  2971.                 if ($inExp) {
  2972.                     return [Type::T_KEYWORD$this->compileValue($valuefalse)];
  2973.                 }
  2974.                 return $value;
  2975.             case Type::T_FUNCTION_CALL:
  2976.                 return $this->fncall($value[1], $value[2]);
  2977.             case Type::T_SELF:
  2978.                 $selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent null;
  2979.                 $selfSelector $this->multiplySelectors($this->env$selfParent);
  2980.                 $selfSelector $this->collapseSelectorsAsList($selfSelector);
  2981.                 return $selfSelector;
  2982.             default:
  2983.                 return $value;
  2984.         }
  2985.     }
  2986.     /**
  2987.      * Function caller
  2988.      *
  2989.      * @param string|array $functionReference
  2990.      * @param array        $argValues
  2991.      *
  2992.      * @return array|Number
  2993.      */
  2994.     protected function fncall($functionReference$argValues)
  2995.     {
  2996.         // a string means this is a static hard reference coming from the parsing
  2997.         if (is_string($functionReference)) {
  2998.             $name $functionReference;
  2999.             $functionReference $this->getFunctionReference($name);
  3000.             if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
  3001.                 $functionReference = [Type::T_FUNCTION$name, [Type::T_LIST',', []]];
  3002.             }
  3003.         }
  3004.         // a function type means we just want a plain css function call
  3005.         if ($functionReference[0] === Type::T_FUNCTION) {
  3006.             // for CSS functions, simply flatten the arguments into a list
  3007.             $listArgs = [];
  3008.             foreach ((array) $argValues as $arg) {
  3009.                 if (empty($arg[0]) || count($argValues) === 1) {
  3010.                     $listArgs[] = $this->reduce($this->stringifyFncallArgs($arg[1]));
  3011.                 }
  3012.             }
  3013.             return [Type::T_FUNCTION$functionReference[1], [Type::T_LIST','$listArgs]];
  3014.         }
  3015.         if ($functionReference === static::$null || $functionReference[0] !== Type::T_FUNCTION_REFERENCE) {
  3016.             return static::$defaultValue;
  3017.         }
  3018.         switch ($functionReference[1]) {
  3019.             // SCSS @function
  3020.             case 'scss':
  3021.                 return $this->callScssFunction($functionReference[3], $argValues);
  3022.             // native PHP functions
  3023.             case 'user':
  3024.             case 'native':
  3025.                 list(,,$name$fn$prototype) = $functionReference;
  3026.                 // special cases of css valid functions min/max
  3027.                 $name strtolower($name);
  3028.                 if (\in_array($name, ['min''max']) && count($argValues) >= 1) {
  3029.                     $cssFunction $this->cssValidArg(
  3030.                         [Type::T_FUNCTION_CALL$name$argValues],
  3031.                         ['min''max''calc''env''var']
  3032.                     );
  3033.                     if ($cssFunction !== false) {
  3034.                         return $cssFunction;
  3035.                     }
  3036.                 }
  3037.                 $returnValue $this->callNativeFunction($name$fn$prototype$argValues);
  3038.                 if (! isset($returnValue)) {
  3039.                     return $this->fncall([Type::T_FUNCTION$name, [Type::T_LIST',', []]], $argValues);
  3040.                 }
  3041.                 return $returnValue;
  3042.             default:
  3043.                 return static::$defaultValue;
  3044.         }
  3045.     }
  3046.     /**
  3047.      * @param array|Number $arg
  3048.      * @param string[]     $allowed_function
  3049.      * @param bool         $inFunction
  3050.      *
  3051.      * @return array|Number|false
  3052.      */
  3053.     protected function cssValidArg($arg$allowed_function = [], $inFunction false)
  3054.     {
  3055.         if ($arg instanceof Number) {
  3056.             return $this->stringifyFncallArgs($arg);
  3057.         }
  3058.         switch ($arg[0]) {
  3059.             case Type::T_INTERPOLATE:
  3060.                 return [Type::T_KEYWORD$this->CompileValue($arg)];
  3061.             case Type::T_FUNCTION:
  3062.                 if (! \in_array($arg[1], $allowed_function)) {
  3063.                     return false;
  3064.                 }
  3065.                 if ($arg[2][0] === Type::T_LIST) {
  3066.                     foreach ($arg[2][2] as $k => $subarg) {
  3067.                         $arg[2][2][$k] = $this->cssValidArg($subarg$allowed_function$arg[1]);
  3068.                         if ($arg[2][2][$k] === false) {
  3069.                             return false;
  3070.                         }
  3071.                     }
  3072.                 }
  3073.                 return $arg;
  3074.             case Type::T_FUNCTION_CALL:
  3075.                 if (! \in_array($arg[1], $allowed_function)) {
  3076.                     return false;
  3077.                 }
  3078.                 $cssArgs = [];
  3079.                 foreach ($arg[2] as $argValue) {
  3080.                     if ($argValue === static::$null) {
  3081.                         return false;
  3082.                     }
  3083.                     $cssArg $this->cssValidArg($argValue[1], $allowed_function$arg[1]);
  3084.                     if (empty($argValue[0]) && $cssArg !== false) {
  3085.                         $cssArgs[] = [$argValue[0], $cssArg];
  3086.                     } else {
  3087.                         return false;
  3088.                     }
  3089.                 }
  3090.                 return $this->fncall([Type::T_FUNCTION$arg[1], [Type::T_LIST',', []]], $cssArgs);
  3091.             case Type::T_STRING:
  3092.             case Type::T_KEYWORD:
  3093.                 if (!$inFunction or !\in_array($inFunction, ['calc''env''var'])) {
  3094.                     return false;
  3095.                 }
  3096.                 return $this->stringifyFncallArgs($arg);
  3097.             case Type::T_LIST:
  3098.                 if (!$inFunction) {
  3099.                     return false;
  3100.                 }
  3101.                 if (empty($arg['enclosing']) and $arg[1] === '') {
  3102.                     foreach ($arg[2] as $k => $subarg) {
  3103.                         $arg[2][$k] = $this->cssValidArg($subarg$allowed_function$inFunction);
  3104.                         if ($arg[2][$k] === false) {
  3105.                             return false;
  3106.                         }
  3107.                     }
  3108.                     $arg[0] = Type::T_STRING;
  3109.                     return $arg;
  3110.                 }
  3111.                 return false;
  3112.             case Type::T_EXPRESSION:
  3113.                 if (! \in_array($arg[1], ['+''-''/''*'])) {
  3114.                     return false;
  3115.                 }
  3116.                 $arg[2] = $this->cssValidArg($arg[2], $allowed_function$inFunction);
  3117.                 $arg[3] = $this->cssValidArg($arg[3], $allowed_function$inFunction);
  3118.                 if ($arg[2] === false || $arg[3] === false) {
  3119.                     return false;
  3120.                 }
  3121.                 return $this->expToString($argtrue);
  3122.             case Type::T_VARIABLE:
  3123.             case Type::T_SELF:
  3124.             default:
  3125.                 return false;
  3126.         }
  3127.     }
  3128.     /**
  3129.      * Reformat fncall arguments to proper css function output
  3130.      *
  3131.      * @param array|Number $arg
  3132.      *
  3133.      * @return array|Number
  3134.      */
  3135.     protected function stringifyFncallArgs($arg)
  3136.     {
  3137.         if ($arg instanceof Number) {
  3138.             return $arg;
  3139.         }
  3140.         switch ($arg[0]) {
  3141.             case Type::T_LIST:
  3142.                 foreach ($arg[2] as $k => $v) {
  3143.                     $arg[2][$k] = $this->stringifyFncallArgs($v);
  3144.                 }
  3145.                 break;
  3146.             case Type::T_EXPRESSION:
  3147.                 if ($arg[1] === '/') {
  3148.                     $arg[2] = $this->stringifyFncallArgs($arg[2]);
  3149.                     $arg[3] = $this->stringifyFncallArgs($arg[3]);
  3150.                     $arg[5] = $arg[6] = false// no space around /
  3151.                     $arg $this->expToString($arg);
  3152.                 }
  3153.                 break;
  3154.             case Type::T_FUNCTION_CALL:
  3155.                 $name strtolower($arg[1]);
  3156.                 if (in_array($name, ['max''min''calc'])) {
  3157.                     $args $arg[2];
  3158.                     $arg $this->fncall([Type::T_FUNCTION$name, [Type::T_LIST',', []]], $args);
  3159.                 }
  3160.                 break;
  3161.         }
  3162.         return $arg;
  3163.     }
  3164.     /**
  3165.      * Find a function reference
  3166.      * @param string $name
  3167.      * @param bool $safeCopy
  3168.      * @return array
  3169.      */
  3170.     protected function getFunctionReference($name$safeCopy false)
  3171.     {
  3172.         // SCSS @function
  3173.         if ($func $this->get(static::$namespaces['function'] . $namefalse)) {
  3174.             if ($safeCopy) {
  3175.                 $func = clone $func;
  3176.             }
  3177.             return [Type::T_FUNCTION_REFERENCE'scss'$name$func];
  3178.         }
  3179.         // native PHP functions
  3180.         // try to find a native lib function
  3181.         $normalizedName $this->normalizeName($name);
  3182.         if (isset($this->userFunctions[$normalizedName])) {
  3183.             // see if we can find a user function
  3184.             list($f$prototype) = $this->userFunctions[$normalizedName];
  3185.             return [Type::T_FUNCTION_REFERENCE'user'$name$f$prototype];
  3186.         }
  3187.         $lowercasedName strtolower($normalizedName);
  3188.         // Special functions overriding a CSS function are case-insensitive. We normalize them as lowercase
  3189.         // to avoid the deprecation warning about the wrong case being used.
  3190.         if ($lowercasedName === 'min' || $lowercasedName === 'max') {
  3191.             $normalizedName $lowercasedName;
  3192.         }
  3193.         if (($f $this->getBuiltinFunction($normalizedName)) && \is_callable($f)) {
  3194.             $libName   $f[1];
  3195.             $prototype = isset(static::$$libName) ? static::$$libName null;
  3196.             // All core functions have a prototype defined. Not finding the
  3197.             // prototype can mean 2 things:
  3198.             // - the function comes from a child class (deprecated just after)
  3199.             // - the function was found with a different case, which relates to calling the
  3200.             //   wrong Sass function due to our camelCase usage (`fade-in()` vs `fadein()`),
  3201.             //   because PHP method names are case-insensitive while property names are
  3202.             //   case-sensitive.
  3203.             if ($prototype === null || strtolower($normalizedName) !== $normalizedName) {
  3204.                 $r = new \ReflectionMethod($this$libName);
  3205.                 $actualLibName $r->name;
  3206.                 if ($actualLibName !== $libName || strtolower($normalizedName) !== $normalizedName) {
  3207.                     $kebabCaseName preg_replace('~(?<=\\w)([A-Z])~''-$1'substr($actualLibName3));
  3208.                     assert($kebabCaseName !== null);
  3209.                     $originalName strtolower($kebabCaseName);
  3210.                     $warning "Calling built-in functions with a non-standard name is deprecated since Scssphp 1.8.0 and will not work anymore in 2.0 (they will be treated as CSS function calls instead).\nUse \"$originalName\" instead of \"$name\".";
  3211.                     @trigger_error($warningE_USER_DEPRECATED);
  3212.                     $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  3213.                     $line  $this->sourceLine;
  3214.                     Warn::deprecation("$warning\n         on line $line of $fname");
  3215.                     // Use the actual function definition
  3216.                     $prototype = isset(static::$$actualLibName) ? static::$$actualLibName null;
  3217.                     $f[1] = $libName $actualLibName;
  3218.                 }
  3219.             }
  3220.             if (\get_class($this) !== __CLASS__ && !isset($this->warnedChildFunctions[$libName])) {
  3221.                 $r = new \ReflectionMethod($this$libName);
  3222.                 $declaringClass $r->getDeclaringClass()->name;
  3223.                 $needsWarning $this->warnedChildFunctions[$libName] = $declaringClass !== __CLASS__;
  3224.                 if ($needsWarning) {
  3225.                     if (method_exists(__CLASS__$libName)) {
  3226.                         @trigger_error(sprintf('Overriding the "%s" core function by extending the Compiler is deprecated and will be unsupported in 2.0. Remove the "%s::%s" method.'$normalizedName$declaringClass$libName), E_USER_DEPRECATED);
  3227.                     } else {
  3228.                         @trigger_error(sprintf('Registering custom functions by extending the Compiler and using the lib* discovery mechanism is deprecated and will be removed in 2.0. Replace the "%s::%s" method with registering the "%s" function through "Compiler::registerFunction".'$declaringClass$libName$normalizedName), E_USER_DEPRECATED);
  3229.                     }
  3230.                 }
  3231.             }
  3232.             return [Type::T_FUNCTION_REFERENCE'native'$name$f$prototype];
  3233.         }
  3234.         return static::$null;
  3235.     }
  3236.     /**
  3237.      * Normalize name
  3238.      *
  3239.      * @param string $name
  3240.      *
  3241.      * @return string
  3242.      */
  3243.     protected function normalizeName($name)
  3244.     {
  3245.         return str_replace('-''_'$name);
  3246.     }
  3247.     /**
  3248.      * Normalize value
  3249.      *
  3250.      * @internal
  3251.      *
  3252.      * @param array|Number $value
  3253.      *
  3254.      * @return array|Number
  3255.      */
  3256.     public function normalizeValue($value)
  3257.     {
  3258.         $value $this->coerceForExpression($this->reduce($value));
  3259.         if ($value instanceof Number) {
  3260.             return $value;
  3261.         }
  3262.         switch ($value[0]) {
  3263.             case Type::T_LIST:
  3264.                 $value $this->extractInterpolation($value);
  3265.                 if ($value[0] !== Type::T_LIST) {
  3266.                     return [Type::T_KEYWORD$this->compileValue($value)];
  3267.                 }
  3268.                 foreach ($value[2] as $key => $item) {
  3269.                     $value[2][$key] = $this->normalizeValue($item);
  3270.                 }
  3271.                 if (! empty($value['enclosing'])) {
  3272.                     unset($value['enclosing']);
  3273.                 }
  3274.                 return $value;
  3275.             case Type::T_STRING:
  3276.                 return [$value[0], '"', [$this->compileStringContent($value)]];
  3277.             case Type::T_INTERPOLATE:
  3278.                 return [Type::T_KEYWORD$this->compileValue($value)];
  3279.             default:
  3280.                 return $value;
  3281.         }
  3282.     }
  3283.     /**
  3284.      * Add numbers
  3285.      *
  3286.      * @param Number $left
  3287.      * @param Number $right
  3288.      *
  3289.      * @return Number
  3290.      */
  3291.     protected function opAddNumberNumber(Number $leftNumber $right)
  3292.     {
  3293.         return $left->plus($right);
  3294.     }
  3295.     /**
  3296.      * Multiply numbers
  3297.      *
  3298.      * @param Number $left
  3299.      * @param Number $right
  3300.      *
  3301.      * @return Number
  3302.      */
  3303.     protected function opMulNumberNumber(Number $leftNumber $right)
  3304.     {
  3305.         return $left->times($right);
  3306.     }
  3307.     /**
  3308.      * Subtract numbers
  3309.      *
  3310.      * @param Number $left
  3311.      * @param Number $right
  3312.      *
  3313.      * @return Number
  3314.      */
  3315.     protected function opSubNumberNumber(Number $leftNumber $right)
  3316.     {
  3317.         return $left->minus($right);
  3318.     }
  3319.     /**
  3320.      * Divide numbers
  3321.      *
  3322.      * @param Number $left
  3323.      * @param Number $right
  3324.      *
  3325.      * @return Number
  3326.      */
  3327.     protected function opDivNumberNumber(Number $leftNumber $right)
  3328.     {
  3329.         return $left->dividedBy($right);
  3330.     }
  3331.     /**
  3332.      * Mod numbers
  3333.      *
  3334.      * @param Number $left
  3335.      * @param Number $right
  3336.      *
  3337.      * @return Number
  3338.      */
  3339.     protected function opModNumberNumber(Number $leftNumber $right)
  3340.     {
  3341.         return $left->modulo($right);
  3342.     }
  3343.     /**
  3344.      * Add strings
  3345.      *
  3346.      * @param array $left
  3347.      * @param array $right
  3348.      *
  3349.      * @return array|null
  3350.      */
  3351.     protected function opAdd($left$right)
  3352.     {
  3353.         if ($strLeft $this->coerceString($left)) {
  3354.             if ($right[0] === Type::T_STRING) {
  3355.                 $right[1] = '';
  3356.             }
  3357.             $strLeft[2][] = $right;
  3358.             return $strLeft;
  3359.         }
  3360.         if ($strRight $this->coerceString($right)) {
  3361.             if ($left[0] === Type::T_STRING) {
  3362.                 $left[1] = '';
  3363.             }
  3364.             array_unshift($strRight[2], $left);
  3365.             return $strRight;
  3366.         }
  3367.         return null;
  3368.     }
  3369.     /**
  3370.      * Boolean and
  3371.      *
  3372.      * @param array|Number $left
  3373.      * @param array|Number  $right
  3374.      * @param boolean $shouldEval
  3375.      *
  3376.      * @return array|Number|null
  3377.      */
  3378.     protected function opAnd($left$right$shouldEval)
  3379.     {
  3380.         $truthy = ($left === static::$null || $right === static::$null) ||
  3381.                   ($left === static::$false || $left === static::$true) &&
  3382.                   ($right === static::$false || $right === static::$true);
  3383.         if (! $shouldEval) {
  3384.             if (! $truthy) {
  3385.                 return null;
  3386.             }
  3387.         }
  3388.         if ($left !== static::$false && $left !== static::$null) {
  3389.             return $this->reduce($righttrue);
  3390.         }
  3391.         return $left;
  3392.     }
  3393.     /**
  3394.      * Boolean or
  3395.      *
  3396.      * @param array|Number $left
  3397.      * @param array|Number $right
  3398.      * @param boolean $shouldEval
  3399.      *
  3400.      * @return array|Number|null
  3401.      */
  3402.     protected function opOr($left$right$shouldEval)
  3403.     {
  3404.         $truthy = ($left === static::$null || $right === static::$null) ||
  3405.                   ($left === static::$false || $left === static::$true) &&
  3406.                   ($right === static::$false || $right === static::$true);
  3407.         if (! $shouldEval) {
  3408.             if (! $truthy) {
  3409.                 return null;
  3410.             }
  3411.         }
  3412.         if ($left !== static::$false && $left !== static::$null) {
  3413.             return $left;
  3414.         }
  3415.         return $this->reduce($righttrue);
  3416.     }
  3417.     /**
  3418.      * Compare colors
  3419.      *
  3420.      * @param string $op
  3421.      * @param array  $left
  3422.      * @param array  $right
  3423.      *
  3424.      * @return array
  3425.      */
  3426.     protected function opColorColor($op$left$right)
  3427.     {
  3428.         if ($op !== '==' && $op !== '!=') {
  3429.             $warning "Color arithmetic is deprecated and will be an error in future versions.\n"
  3430.                 "Consider using Sass's color functions instead.";
  3431.             $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  3432.             $line  $this->sourceLine;
  3433.             Warn::deprecation("$warning\n         on line $line of $fname");
  3434.         }
  3435.         $out = [Type::T_COLOR];
  3436.         foreach ([123] as $i) {
  3437.             $lval = isset($left[$i]) ? $left[$i] : 0;
  3438.             $rval = isset($right[$i]) ? $right[$i] : 0;
  3439.             switch ($op) {
  3440.                 case '+':
  3441.                     $out[] = $lval $rval;
  3442.                     break;
  3443.                 case '-':
  3444.                     $out[] = $lval $rval;
  3445.                     break;
  3446.                 case '*':
  3447.                     $out[] = $lval $rval;
  3448.                     break;
  3449.                 case '%':
  3450.                     if ($rval == 0) {
  3451.                         throw $this->error("color: Can't take modulo by zero");
  3452.                     }
  3453.                     $out[] = $lval $rval;
  3454.                     break;
  3455.                 case '/':
  3456.                     if ($rval == 0) {
  3457.                         throw $this->error("color: Can't divide by zero");
  3458.                     }
  3459.                     $out[] = (int) ($lval $rval);
  3460.                     break;
  3461.                 case '==':
  3462.                     return $this->opEq($left$right);
  3463.                 case '!=':
  3464.                     return $this->opNeq($left$right);
  3465.                 default:
  3466.                     throw $this->error("color: unknown op $op");
  3467.             }
  3468.         }
  3469.         if (isset($left[4])) {
  3470.             $out[4] = $left[4];
  3471.         } elseif (isset($right[4])) {
  3472.             $out[4] = $right[4];
  3473.         }
  3474.         return $this->fixColor($out);
  3475.     }
  3476.     /**
  3477.      * Compare color and number
  3478.      *
  3479.      * @param string $op
  3480.      * @param array  $left
  3481.      * @param Number  $right
  3482.      *
  3483.      * @return array
  3484.      */
  3485.     protected function opColorNumber($op$leftNumber $right)
  3486.     {
  3487.         if ($op === '==') {
  3488.             return static::$false;
  3489.         }
  3490.         if ($op === '!=') {
  3491.             return static::$true;
  3492.         }
  3493.         $value $right->getDimension();
  3494.         return $this->opColorColor(
  3495.             $op,
  3496.             $left,
  3497.             [Type::T_COLOR$value$value$value]
  3498.         );
  3499.     }
  3500.     /**
  3501.      * Compare number and color
  3502.      *
  3503.      * @param string $op
  3504.      * @param Number  $left
  3505.      * @param array  $right
  3506.      *
  3507.      * @return array
  3508.      */
  3509.     protected function opNumberColor($opNumber $left$right)
  3510.     {
  3511.         if ($op === '==') {
  3512.             return static::$false;
  3513.         }
  3514.         if ($op === '!=') {
  3515.             return static::$true;
  3516.         }
  3517.         $value $left->getDimension();
  3518.         return $this->opColorColor(
  3519.             $op,
  3520.             [Type::T_COLOR$value$value$value],
  3521.             $right
  3522.         );
  3523.     }
  3524.     /**
  3525.      * Compare number1 == number2
  3526.      *
  3527.      * @param array|Number $left
  3528.      * @param array|Number $right
  3529.      *
  3530.      * @return array
  3531.      */
  3532.     protected function opEq($left$right)
  3533.     {
  3534.         if (($lStr $this->coerceString($left)) && ($rStr $this->coerceString($right))) {
  3535.             $lStr[1] = '';
  3536.             $rStr[1] = '';
  3537.             $left $this->compileValue($lStr);
  3538.             $right $this->compileValue($rStr);
  3539.         }
  3540.         return $this->toBool($left === $right);
  3541.     }
  3542.     /**
  3543.      * Compare number1 != number2
  3544.      *
  3545.      * @param array|Number $left
  3546.      * @param array|Number $right
  3547.      *
  3548.      * @return array
  3549.      */
  3550.     protected function opNeq($left$right)
  3551.     {
  3552.         if (($lStr $this->coerceString($left)) && ($rStr $this->coerceString($right))) {
  3553.             $lStr[1] = '';
  3554.             $rStr[1] = '';
  3555.             $left $this->compileValue($lStr);
  3556.             $right $this->compileValue($rStr);
  3557.         }
  3558.         return $this->toBool($left !== $right);
  3559.     }
  3560.     /**
  3561.      * Compare number1 == number2
  3562.      *
  3563.      * @param Number $left
  3564.      * @param Number $right
  3565.      *
  3566.      * @return array
  3567.      */
  3568.     protected function opEqNumberNumber(Number $leftNumber $right)
  3569.     {
  3570.         return $this->toBool($left->equals($right));
  3571.     }
  3572.     /**
  3573.      * Compare number1 != number2
  3574.      *
  3575.      * @param Number $left
  3576.      * @param Number $right
  3577.      *
  3578.      * @return array
  3579.      */
  3580.     protected function opNeqNumberNumber(Number $leftNumber $right)
  3581.     {
  3582.         return $this->toBool(!$left->equals($right));
  3583.     }
  3584.     /**
  3585.      * Compare number1 >= number2
  3586.      *
  3587.      * @param Number $left
  3588.      * @param Number $right
  3589.      *
  3590.      * @return array
  3591.      */
  3592.     protected function opGteNumberNumber(Number $leftNumber $right)
  3593.     {
  3594.         return $this->toBool($left->greaterThanOrEqual($right));
  3595.     }
  3596.     /**
  3597.      * Compare number1 > number2
  3598.      *
  3599.      * @param Number $left
  3600.      * @param Number $right
  3601.      *
  3602.      * @return array
  3603.      */
  3604.     protected function opGtNumberNumber(Number $leftNumber $right)
  3605.     {
  3606.         return $this->toBool($left->greaterThan($right));
  3607.     }
  3608.     /**
  3609.      * Compare number1 <= number2
  3610.      *
  3611.      * @param Number $left
  3612.      * @param Number $right
  3613.      *
  3614.      * @return array
  3615.      */
  3616.     protected function opLteNumberNumber(Number $leftNumber $right)
  3617.     {
  3618.         return $this->toBool($left->lessThanOrEqual($right));
  3619.     }
  3620.     /**
  3621.      * Compare number1 < number2
  3622.      *
  3623.      * @param Number $left
  3624.      * @param Number $right
  3625.      *
  3626.      * @return array
  3627.      */
  3628.     protected function opLtNumberNumber(Number $leftNumber $right)
  3629.     {
  3630.         return $this->toBool($left->lessThan($right));
  3631.     }
  3632.     /**
  3633.      * Cast to boolean
  3634.      *
  3635.      * @api
  3636.      *
  3637.      * @param bool $thing
  3638.      *
  3639.      * @return array
  3640.      */
  3641.     public function toBool($thing)
  3642.     {
  3643.         return $thing ? static::$true : static::$false;
  3644.     }
  3645.     /**
  3646.      * Escape non printable chars in strings output as in dart-sass
  3647.      *
  3648.      * @internal
  3649.      *
  3650.      * @param string $string
  3651.      * @param bool   $inKeyword
  3652.      *
  3653.      * @return string
  3654.      */
  3655.     public function escapeNonPrintableChars($string$inKeyword false)
  3656.     {
  3657.         static $replacement = [];
  3658.         if (empty($replacement[$inKeyword])) {
  3659.             for ($i 0$i 32$i++) {
  3660.                 if ($i !== || $inKeyword) {
  3661.                     $replacement[$inKeyword][chr($i)] = '\\' dechex($i) . ($inKeyword ' ' chr(0));
  3662.                 }
  3663.             }
  3664.         }
  3665.         $string str_replace(array_keys($replacement[$inKeyword]), array_values($replacement[$inKeyword]), $string);
  3666.         // chr(0) is not a possible char from the input, so any chr(0) comes from our escaping replacement
  3667.         if (strpos($stringchr(0)) !== false) {
  3668.             if (substr($string, -1) === chr(0)) {
  3669.                 $string substr($string0, -1);
  3670.             }
  3671.             $string str_replace(
  3672.                 [chr(0) . '\\',chr(0) . ' '],
  3673.                 [ '\\'' '],
  3674.                 $string
  3675.             );
  3676.             if (strpos($stringchr(0)) !== false) {
  3677.                 $parts explode(chr(0), $string);
  3678.                 $string array_shift($parts);
  3679.                 while (count($parts)) {
  3680.                     $next array_shift($parts);
  3681.                     if (strpos("0123456789abcdefABCDEF" chr(9), $next[0]) !== false) {
  3682.                         $string .= " ";
  3683.                     }
  3684.                     $string .= $next;
  3685.                 }
  3686.             }
  3687.         }
  3688.         return $string;
  3689.     }
  3690.     /**
  3691.      * Compiles a primitive value into a CSS property value.
  3692.      *
  3693.      * Values in scssphp are typed by being wrapped in arrays, their format is
  3694.      * typically:
  3695.      *
  3696.      *     array(type, contents [, additional_contents]*)
  3697.      *
  3698.      * The input is expected to be reduced. This function will not work on
  3699.      * things like expressions and variables.
  3700.      *
  3701.      * @api
  3702.      *
  3703.      * @param array|Number $value
  3704.      * @param bool         $quote
  3705.      *
  3706.      * @return string
  3707.      */
  3708.     public function compileValue($value$quote true)
  3709.     {
  3710.         $value $this->reduce($value);
  3711.         if ($value instanceof Number) {
  3712.             return $value->output($this);
  3713.         }
  3714.         switch ($value[0]) {
  3715.             case Type::T_KEYWORD:
  3716.                 return $this->escapeNonPrintableChars($value[1], true);
  3717.             case Type::T_COLOR:
  3718.                 // [1] - red component (either number for a %)
  3719.                 // [2] - green component
  3720.                 // [3] - blue component
  3721.                 // [4] - optional alpha component
  3722.                 list(, $r$g$b) = $value;
  3723.                 $r $this->compileRGBAValue($r);
  3724.                 $g $this->compileRGBAValue($g);
  3725.                 $b $this->compileRGBAValue($b);
  3726.                 if (\count($value) === 5) {
  3727.                     $alpha $this->compileRGBAValue($value[4], true);
  3728.                     if (! is_numeric($alpha) || $alpha 1) {
  3729.                         $colorName Colors::RGBaToColorName($r$g$b$alpha);
  3730.                         if (! \is_null($colorName)) {
  3731.                             return $colorName;
  3732.                         }
  3733.                         if (is_numeric($alpha)) {
  3734.                             $a = new Number($alpha'');
  3735.                         } else {
  3736.                             $a $alpha;
  3737.                         }
  3738.                         return 'rgba(' $r ', ' $g ', ' $b ', ' $a ')';
  3739.                     }
  3740.                 }
  3741.                 if (! is_numeric($r) || ! is_numeric($g) || ! is_numeric($b)) {
  3742.                     return 'rgb(' $r ', ' $g ', ' $b ')';
  3743.                 }
  3744.                 $colorName Colors::RGBaToColorName($r$g$b);
  3745.                 if (! \is_null($colorName)) {
  3746.                     return $colorName;
  3747.                 }
  3748.                 $h sprintf('#%02x%02x%02x'$r$g$b);
  3749.                 // Converting hex color to short notation (e.g. #003399 to #039)
  3750.                 if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
  3751.                     $h '#' $h[1] . $h[3] . $h[5];
  3752.                 }
  3753.                 return $h;
  3754.             case Type::T_STRING:
  3755.                 $content $this->compileStringContent($value$quote);
  3756.                 if ($value[1] && $quote) {
  3757.                     $content str_replace('\\''\\\\'$content);
  3758.                     $content $this->escapeNonPrintableChars($content);
  3759.                     // force double quote as string quote for the output in certain cases
  3760.                     if (
  3761.                         $value[1] === "'" &&
  3762.                         (strpos($content'"') === false or strpos($content"'") !== false) &&
  3763.                         strpbrk($content'{}\\\'') !== false
  3764.                     ) {
  3765.                         $value[1] = '"';
  3766.                     } elseif (
  3767.                         $value[1] === '"' &&
  3768.                         (strpos($content'"') !== false and strpos($content"'") === false)
  3769.                     ) {
  3770.                         $value[1] = "'";
  3771.                     }
  3772.                     $content str_replace($value[1], '\\' $value[1], $content);
  3773.                 }
  3774.                 return $value[1] . $content $value[1];
  3775.             case Type::T_FUNCTION:
  3776.                 $args = ! empty($value[2]) ? $this->compileValue($value[2], $quote) : '';
  3777.                 return "$value[1]($args)";
  3778.             case Type::T_FUNCTION_REFERENCE:
  3779.                 $name = ! empty($value[2]) ? $value[2] : '';
  3780.                 return "get-function(\"$name\")";
  3781.             case Type::T_LIST:
  3782.                 $value $this->extractInterpolation($value);
  3783.                 if ($value[0] !== Type::T_LIST) {
  3784.                     return $this->compileValue($value$quote);
  3785.                 }
  3786.                 list(, $delim$items) = $value;
  3787.                 $pre $post '';
  3788.                 if (! empty($value['enclosing'])) {
  3789.                     switch ($value['enclosing']) {
  3790.                         case 'parent':
  3791.                             //$pre = '(';
  3792.                             //$post = ')';
  3793.                             break;
  3794.                         case 'forced_parent':
  3795.                             $pre '(';
  3796.                             $post ')';
  3797.                             break;
  3798.                         case 'bracket':
  3799.                         case 'forced_bracket':
  3800.                             $pre '[';
  3801.                             $post ']';
  3802.                             break;
  3803.                     }
  3804.                 }
  3805.                 $prefix_value '';
  3806.                 if ($delim !== ' ') {
  3807.                     $prefix_value ' ';
  3808.                 }
  3809.                 $filtered = [];
  3810.                 $same_string_quote null;
  3811.                 foreach ($items as $item) {
  3812.                     if (\is_null($same_string_quote)) {
  3813.                         $same_string_quote false;
  3814.                         if ($item[0] === Type::T_STRING) {
  3815.                             $same_string_quote $item[1];
  3816.                             foreach ($items as $ii) {
  3817.                                 if ($ii[0] !== Type::T_STRING) {
  3818.                                     $same_string_quote false;
  3819.                                     break;
  3820.                                 }
  3821.                             }
  3822.                         }
  3823.                     }
  3824.                     if ($item[0] === Type::T_NULL) {
  3825.                         continue;
  3826.                     }
  3827.                     if ($same_string_quote === '"' && $item[0] === Type::T_STRING && $item[1]) {
  3828.                         $item[1] = $same_string_quote;
  3829.                     }
  3830.                     $compiled $this->compileValue($item$quote);
  3831.                     if ($prefix_value && \strlen($compiled)) {
  3832.                         $compiled $prefix_value $compiled;
  3833.                     }
  3834.                     $filtered[] = $compiled;
  3835.                 }
  3836.                 return $pre substr(implode("$delim"$filtered), \strlen($prefix_value)) . $post;
  3837.             case Type::T_MAP:
  3838.                 $keys     $value[1];
  3839.                 $values   $value[2];
  3840.                 $filtered = [];
  3841.                 for ($i 0$s = \count($keys); $i $s$i++) {
  3842.                     $filtered[$this->compileValue($keys[$i], $quote)] = $this->compileValue($values[$i], $quote);
  3843.                 }
  3844.                 array_walk($filtered, function (&$value$key) {
  3845.                     $value $key ': ' $value;
  3846.                 });
  3847.                 return '(' implode(', '$filtered) . ')';
  3848.             case Type::T_INTERPOLATED:
  3849.                 // node created by extractInterpolation
  3850.                 list(, $interpolate$left$right) = $value;
  3851.                 list(,, $whiteLeft$whiteRight) = $interpolate;
  3852.                 $delim $left[1];
  3853.                 if ($delim && $delim !== ' ' && ! $whiteLeft) {
  3854.                     $delim .= ' ';
  3855.                 }
  3856.                 $left = \count($left[2]) > 0
  3857.                     ?  $this->compileValue($left$quote) . $delim $whiteLeft
  3858.                     '';
  3859.                 $delim $right[1];
  3860.                 if ($delim && $delim !== ' ') {
  3861.                     $delim .= ' ';
  3862.                 }
  3863.                 $right = \count($right[2]) > ?
  3864.                     $whiteRight $delim $this->compileValue($right$quote) : '';
  3865.                 return $left $this->compileValue($interpolate$quote) . $right;
  3866.             case Type::T_INTERPOLATE:
  3867.                 // strip quotes if it's a string
  3868.                 $reduced $this->reduce($value[1]);
  3869.                 if ($reduced instanceof Number) {
  3870.                     return $this->compileValue($reduced$quote);
  3871.                 }
  3872.                 switch ($reduced[0]) {
  3873.                     case Type::T_LIST:
  3874.                         $reduced $this->extractInterpolation($reduced);
  3875.                         if ($reduced[0] !== Type::T_LIST) {
  3876.                             break;
  3877.                         }
  3878.                         list(, $delim$items) = $reduced;
  3879.                         if ($delim !== ' ') {
  3880.                             $delim .= ' ';
  3881.                         }
  3882.                         $filtered = [];
  3883.                         foreach ($items as $item) {
  3884.                             if ($item[0] === Type::T_NULL) {
  3885.                                 continue;
  3886.                             }
  3887.                             if ($item[0] === Type::T_STRING) {
  3888.                                 $filtered[] = $this->compileStringContent($item$quote);
  3889.                             } elseif ($item[0] === Type::T_KEYWORD) {
  3890.                                 $filtered[] = $item[1];
  3891.                             } else {
  3892.                                 $filtered[] = $this->compileValue($item$quote);
  3893.                             }
  3894.                         }
  3895.                         $reduced = [Type::T_KEYWORDimplode("$delim"$filtered)];
  3896.                         break;
  3897.                     case Type::T_STRING:
  3898.                         $reduced = [Type::T_STRING'', [$this->compileStringContent($reduced)]];
  3899.                         break;
  3900.                     case Type::T_NULL:
  3901.                         $reduced = [Type::T_KEYWORD''];
  3902.                 }
  3903.                 return $this->compileValue($reduced$quote);
  3904.             case Type::T_NULL:
  3905.                 return 'null';
  3906.             case Type::T_COMMENT:
  3907.                 return $this->compileCommentValue($value);
  3908.             default:
  3909.                 throw $this->error('unknown value type: ' json_encode($value));
  3910.         }
  3911.     }
  3912.     /**
  3913.      * @param array|Number $value
  3914.      *
  3915.      * @return string
  3916.      */
  3917.     protected function compileDebugValue($value)
  3918.     {
  3919.         $value $this->reduce($valuetrue);
  3920.         if ($value instanceof Number) {
  3921.             return $this->compileValue($value);
  3922.         }
  3923.         switch ($value[0]) {
  3924.             case Type::T_STRING:
  3925.                 return $this->compileStringContent($value);
  3926.             default:
  3927.                 return $this->compileValue($value);
  3928.         }
  3929.     }
  3930.     /**
  3931.      * Flatten list
  3932.      *
  3933.      * @param array $list
  3934.      *
  3935.      * @return string
  3936.      *
  3937.      * @deprecated
  3938.      */
  3939.     protected function flattenList($list)
  3940.     {
  3941.         @trigger_error(sprintf('The "%s" method is deprecated.'__METHOD__), E_USER_DEPRECATED);
  3942.         return $this->compileValue($list);
  3943.     }
  3944.     /**
  3945.      * Gets the text of a Sass string
  3946.      *
  3947.      * Calling this method on anything else than a SassString is unsupported. Use {@see assertString} first
  3948.      * to ensure that the value is indeed a string.
  3949.      *
  3950.      * @param array $value
  3951.      *
  3952.      * @return string
  3953.      */
  3954.     public function getStringText(array $value)
  3955.     {
  3956.         if ($value[0] !== Type::T_STRING) {
  3957.             throw new \InvalidArgumentException('The argument is not a sass string. Did you forgot to use "assertString"?');
  3958.         }
  3959.         return $this->compileStringContent($value);
  3960.     }
  3961.     /**
  3962.      * Compile string content
  3963.      *
  3964.      * @param array $string
  3965.      * @param bool  $quote
  3966.      *
  3967.      * @return string
  3968.      */
  3969.     protected function compileStringContent($string$quote true)
  3970.     {
  3971.         $parts = [];
  3972.         foreach ($string[2] as $part) {
  3973.             if (\is_array($part) || $part instanceof Number) {
  3974.                 $parts[] = $this->compileValue($part$quote);
  3975.             } else {
  3976.                 $parts[] = $part;
  3977.             }
  3978.         }
  3979.         return implode($parts);
  3980.     }
  3981.     /**
  3982.      * Extract interpolation; it doesn't need to be recursive, compileValue will handle that
  3983.      *
  3984.      * @param array $list
  3985.      *
  3986.      * @return array
  3987.      */
  3988.     protected function extractInterpolation($list)
  3989.     {
  3990.         $items $list[2];
  3991.         foreach ($items as $i => $item) {
  3992.             if ($item[0] === Type::T_INTERPOLATE) {
  3993.                 $before = [Type::T_LIST$list[1], \array_slice($items0$i)];
  3994.                 $after  = [Type::T_LIST$list[1], \array_slice($items$i 1)];
  3995.                 return [Type::T_INTERPOLATED$item$before$after];
  3996.             }
  3997.         }
  3998.         return $list;
  3999.     }
  4000.     /**
  4001.      * Find the final set of selectors
  4002.      *
  4003.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4004.      * @param \ScssPhp\ScssPhp\Block                $selfParent
  4005.      *
  4006.      * @return array
  4007.      */
  4008.     protected function multiplySelectors(Environment $env$selfParent null)
  4009.     {
  4010.         $envs            $this->compactEnv($env);
  4011.         $selectors       = [];
  4012.         $parentSelectors = [[]];
  4013.         $selfParentSelectors null;
  4014.         if (! \is_null($selfParent) && $selfParent->selectors) {
  4015.             $selfParentSelectors $this->evalSelectors($selfParent->selectors);
  4016.         }
  4017.         while ($env array_pop($envs)) {
  4018.             if (empty($env->selectors)) {
  4019.                 continue;
  4020.             }
  4021.             $selectors $env->selectors;
  4022.             do {
  4023.                 $stillHasSelf  false;
  4024.                 $prevSelectors $selectors;
  4025.                 $selectors     = [];
  4026.                 foreach ($parentSelectors as $parent) {
  4027.                     foreach ($prevSelectors as $selector) {
  4028.                         if ($selfParentSelectors) {
  4029.                             foreach ($selfParentSelectors as $selfParent) {
  4030.                                 // if no '&' in the selector, each call will give same result, only add once
  4031.                                 $s $this->joinSelectors($parent$selector$stillHasSelf$selfParent);
  4032.                                 $selectors[serialize($s)] = $s;
  4033.                             }
  4034.                         } else {
  4035.                             $s $this->joinSelectors($parent$selector$stillHasSelf);
  4036.                             $selectors[serialize($s)] = $s;
  4037.                         }
  4038.                     }
  4039.                 }
  4040.             } while ($stillHasSelf);
  4041.             $parentSelectors $selectors;
  4042.         }
  4043.         $selectors array_values($selectors);
  4044.         // case we are just starting a at-root : nothing to multiply but parentSelectors
  4045.         if (! $selectors && $selfParentSelectors) {
  4046.             $selectors $selfParentSelectors;
  4047.         }
  4048.         return $selectors;
  4049.     }
  4050.     /**
  4051.      * Join selectors; looks for & to replace, or append parent before child
  4052.      *
  4053.      * @param array   $parent
  4054.      * @param array   $child
  4055.      * @param boolean $stillHasSelf
  4056.      * @param array   $selfParentSelectors
  4057.      * @return array
  4058.      */
  4059.     protected function joinSelectors($parent$child, &$stillHasSelf$selfParentSelectors null)
  4060.     {
  4061.         $setSelf false;
  4062.         $out = [];
  4063.         foreach ($child as $part) {
  4064.             $newPart = [];
  4065.             foreach ($part as $p) {
  4066.                 // only replace & once and should be recalled to be able to make combinations
  4067.                 if ($p === static::$selfSelector && $setSelf) {
  4068.                     $stillHasSelf true;
  4069.                 }
  4070.                 if ($p === static::$selfSelector && ! $setSelf) {
  4071.                     $setSelf true;
  4072.                     if (\is_null($selfParentSelectors)) {
  4073.                         $selfParentSelectors $parent;
  4074.                     }
  4075.                     foreach ($selfParentSelectors as $i => $parentPart) {
  4076.                         if ($i 0) {
  4077.                             $out[] = $newPart;
  4078.                             $newPart = [];
  4079.                         }
  4080.                         foreach ($parentPart as $pp) {
  4081.                             if (\is_array($pp)) {
  4082.                                 $flatten = [];
  4083.                                 array_walk_recursive($pp, function ($a) use (&$flatten) {
  4084.                                     $flatten[] = $a;
  4085.                                 });
  4086.                                 $pp implode($flatten);
  4087.                             }
  4088.                             $newPart[] = $pp;
  4089.                         }
  4090.                     }
  4091.                 } else {
  4092.                     $newPart[] = $p;
  4093.                 }
  4094.             }
  4095.             $out[] = $newPart;
  4096.         }
  4097.         return $setSelf $out array_merge($parent$child);
  4098.     }
  4099.     /**
  4100.      * Multiply media
  4101.      *
  4102.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4103.      * @param array                                 $childQueries
  4104.      *
  4105.      * @return array
  4106.      */
  4107.     protected function multiplyMedia(Environment $env null$childQueries null)
  4108.     {
  4109.         if (
  4110.             ! isset($env) ||
  4111.             ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
  4112.         ) {
  4113.             return $childQueries;
  4114.         }
  4115.         // plain old block, skip
  4116.         if (empty($env->block->type)) {
  4117.             return $this->multiplyMedia($env->parent$childQueries);
  4118.         }
  4119.         $parentQueries = isset($env->block->queryList)
  4120.             ? $env->block->queryList
  4121.             : [[[Type::T_MEDIA_VALUE$env->block->value]]];
  4122.         $store = [$this->env$this->storeEnv];
  4123.         $this->env      $env;
  4124.         $this->storeEnv null;
  4125.         $parentQueries  $this->evaluateMediaQuery($parentQueries);
  4126.         list($this->env$this->storeEnv) = $store;
  4127.         if (\is_null($childQueries)) {
  4128.             $childQueries $parentQueries;
  4129.         } else {
  4130.             $originalQueries $childQueries;
  4131.             $childQueries = [];
  4132.             foreach ($parentQueries as $parentQuery) {
  4133.                 foreach ($originalQueries as $childQuery) {
  4134.                     $childQueries[] = array_merge(
  4135.                         $parentQuery,
  4136.                         [[Type::T_MEDIA_TYPE, [Type::T_KEYWORD'all']]],
  4137.                         $childQuery
  4138.                     );
  4139.                 }
  4140.             }
  4141.         }
  4142.         return $this->multiplyMedia($env->parent$childQueries);
  4143.     }
  4144.     /**
  4145.      * Convert env linked list to stack
  4146.      *
  4147.      * @param Environment $env
  4148.      *
  4149.      * @return Environment[]
  4150.      *
  4151.      * @phpstan-return non-empty-array<Environment>
  4152.      */
  4153.     protected function compactEnv(Environment $env)
  4154.     {
  4155.         for ($envs = []; $env$env $env->parent) {
  4156.             $envs[] = $env;
  4157.         }
  4158.         return $envs;
  4159.     }
  4160.     /**
  4161.      * Convert env stack to singly linked list
  4162.      *
  4163.      * @param Environment[] $envs
  4164.      *
  4165.      * @return Environment
  4166.      *
  4167.      * @phpstan-param  non-empty-array<Environment> $envs
  4168.      */
  4169.     protected function extractEnv($envs)
  4170.     {
  4171.         for ($env null$e array_pop($envs);) {
  4172.             $e->parent $env;
  4173.             $env $e;
  4174.         }
  4175.         return $env;
  4176.     }
  4177.     /**
  4178.      * Push environment
  4179.      *
  4180.      * @param \ScssPhp\ScssPhp\Block $block
  4181.      *
  4182.      * @return \ScssPhp\ScssPhp\Compiler\Environment
  4183.      */
  4184.     protected function pushEnv(Block $block null)
  4185.     {
  4186.         $env = new Environment();
  4187.         $env->parent $this->env;
  4188.         $env->parentStore $this->storeEnv;
  4189.         $env->store  = [];
  4190.         $env->block  $block;
  4191.         $env->depth  = isset($this->env->depth) ? $this->env->depth 0;
  4192.         $this->env $env;
  4193.         $this->storeEnv null;
  4194.         return $env;
  4195.     }
  4196.     /**
  4197.      * Pop environment
  4198.      *
  4199.      * @return void
  4200.      */
  4201.     protected function popEnv()
  4202.     {
  4203.         $this->storeEnv $this->env->parentStore;
  4204.         $this->env $this->env->parent;
  4205.     }
  4206.     /**
  4207.      * Propagate vars from a just poped Env (used in @each and @for)
  4208.      *
  4209.      * @param array         $store
  4210.      * @param null|string[] $excludedVars
  4211.      *
  4212.      * @return void
  4213.      */
  4214.     protected function backPropagateEnv($store$excludedVars null)
  4215.     {
  4216.         foreach ($store as $key => $value) {
  4217.             if (empty($excludedVars) || ! \in_array($key$excludedVars)) {
  4218.                 $this->set($key$valuetrue);
  4219.             }
  4220.         }
  4221.     }
  4222.     /**
  4223.      * Get store environment
  4224.      *
  4225.      * @return \ScssPhp\ScssPhp\Compiler\Environment
  4226.      */
  4227.     protected function getStoreEnv()
  4228.     {
  4229.         return isset($this->storeEnv) ? $this->storeEnv $this->env;
  4230.     }
  4231.     /**
  4232.      * Set variable
  4233.      *
  4234.      * @param string                                $name
  4235.      * @param mixed                                 $value
  4236.      * @param boolean                               $shadow
  4237.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4238.      * @param mixed                                 $valueUnreduced
  4239.      *
  4240.      * @return void
  4241.      */
  4242.     protected function set($name$value$shadow falseEnvironment $env null$valueUnreduced null)
  4243.     {
  4244.         $name $this->normalizeName($name);
  4245.         if (! isset($env)) {
  4246.             $env $this->getStoreEnv();
  4247.         }
  4248.         if ($shadow) {
  4249.             $this->setRaw($name$value$env$valueUnreduced);
  4250.         } else {
  4251.             $this->setExisting($name$value$env$valueUnreduced);
  4252.         }
  4253.     }
  4254.     /**
  4255.      * Set existing variable
  4256.      *
  4257.      * @param string                                $name
  4258.      * @param mixed                                 $value
  4259.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4260.      * @param mixed                                 $valueUnreduced
  4261.      *
  4262.      * @return void
  4263.      */
  4264.     protected function setExisting($name$valueEnvironment $env$valueUnreduced null)
  4265.     {
  4266.         $storeEnv $env;
  4267.         $specialContentKey = static::$namespaces['special'] . 'content';
  4268.         $hasNamespace $name[0] === '^' || $name[0] === '@' || $name[0] === '%';
  4269.         $maxDepth 10000;
  4270.         for (;;) {
  4271.             if ($maxDepth-- <= 0) {
  4272.                 break;
  4273.             }
  4274.             if (\array_key_exists($name$env->store)) {
  4275.                 break;
  4276.             }
  4277.             if (! $hasNamespace && isset($env->marker)) {
  4278.                 if (! empty($env->store[$specialContentKey])) {
  4279.                     $env $env->store[$specialContentKey]->scope;
  4280.                     continue;
  4281.                 }
  4282.                 if (! empty($env->declarationScopeParent)) {
  4283.                     $env $env->declarationScopeParent;
  4284.                     continue;
  4285.                 } else {
  4286.                     $env $storeEnv;
  4287.                     break;
  4288.                 }
  4289.             }
  4290.             if (isset($env->parentStore)) {
  4291.                 $env $env->parentStore;
  4292.             } elseif (isset($env->parent)) {
  4293.                 $env $env->parent;
  4294.             } else {
  4295.                 $env $storeEnv;
  4296.                 break;
  4297.             }
  4298.         }
  4299.         $env->store[$name] = $value;
  4300.         if ($valueUnreduced) {
  4301.             $env->storeUnreduced[$name] = $valueUnreduced;
  4302.         }
  4303.     }
  4304.     /**
  4305.      * Set raw variable
  4306.      *
  4307.      * @param string                                $name
  4308.      * @param mixed                                 $value
  4309.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4310.      * @param mixed                                 $valueUnreduced
  4311.      *
  4312.      * @return void
  4313.      */
  4314.     protected function setRaw($name$valueEnvironment $env$valueUnreduced null)
  4315.     {
  4316.         $env->store[$name] = $value;
  4317.         if ($valueUnreduced) {
  4318.             $env->storeUnreduced[$name] = $valueUnreduced;
  4319.         }
  4320.     }
  4321.     /**
  4322.      * Get variable
  4323.      *
  4324.      * @internal
  4325.      *
  4326.      * @param string                                $name
  4327.      * @param boolean                               $shouldThrow
  4328.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4329.      * @param boolean                               $unreduced
  4330.      *
  4331.      * @return mixed|null
  4332.      */
  4333.     public function get($name$shouldThrow trueEnvironment $env null$unreduced false)
  4334.     {
  4335.         $normalizedName $this->normalizeName($name);
  4336.         $specialContentKey = static::$namespaces['special'] . 'content';
  4337.         if (! isset($env)) {
  4338.             $env $this->getStoreEnv();
  4339.         }
  4340.         $hasNamespace $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%';
  4341.         $maxDepth 10000;
  4342.         for (;;) {
  4343.             if ($maxDepth-- <= 0) {
  4344.                 break;
  4345.             }
  4346.             if (\array_key_exists($normalizedName$env->store)) {
  4347.                 if ($unreduced && isset($env->storeUnreduced[$normalizedName])) {
  4348.                     return $env->storeUnreduced[$normalizedName];
  4349.                 }
  4350.                 return $env->store[$normalizedName];
  4351.             }
  4352.             if (! $hasNamespace && isset($env->marker)) {
  4353.                 if (! empty($env->store[$specialContentKey])) {
  4354.                     $env $env->store[$specialContentKey]->scope;
  4355.                     continue;
  4356.                 }
  4357.                 if (! empty($env->declarationScopeParent)) {
  4358.                     $env $env->declarationScopeParent;
  4359.                 } else {
  4360.                     $env $this->rootEnv;
  4361.                 }
  4362.                 continue;
  4363.             }
  4364.             if (isset($env->parentStore)) {
  4365.                 $env $env->parentStore;
  4366.             } elseif (isset($env->parent)) {
  4367.                 $env $env->parent;
  4368.             } else {
  4369.                 break;
  4370.             }
  4371.         }
  4372.         if ($shouldThrow) {
  4373.             throw $this->error("Undefined variable \$$name. ($maxDepth <= ' (infinite recursion)' ''));
  4374.         }
  4375.         // found nothing
  4376.         return null;
  4377.     }
  4378.     /**
  4379.      * Has variable?
  4380.      *
  4381.      * @param string                                $name
  4382.      * @param \ScssPhp\ScssPhp\Compiler\Environment $env
  4383.      *
  4384.      * @return boolean
  4385.      */
  4386.     protected function has($nameEnvironment $env null)
  4387.     {
  4388.         return ! \is_null($this->get($namefalse$env));
  4389.     }
  4390.     /**
  4391.      * Inject variables
  4392.      *
  4393.      * @param array $args
  4394.      *
  4395.      * @return void
  4396.      */
  4397.     protected function injectVariables(array $args)
  4398.     {
  4399.         if (empty($args)) {
  4400.             return;
  4401.         }
  4402.         $parser $this->parserFactory(__METHOD__);
  4403.         foreach ($args as $name => $strValue) {
  4404.             if ($name[0] === '$') {
  4405.                 $name substr($name1);
  4406.             }
  4407.             if (!\is_string($strValue) || ! $parser->parseValue($strValue$value)) {
  4408.                 $value $this->coerceValue($strValue);
  4409.             }
  4410.             $this->set($name$value);
  4411.         }
  4412.     }
  4413.     /**
  4414.      * Replaces variables.
  4415.      *
  4416.      * @param array<string, mixed> $variables
  4417.      *
  4418.      * @return void
  4419.      */
  4420.     public function replaceVariables(array $variables)
  4421.     {
  4422.         $this->registeredVars = [];
  4423.         $this->addVariables($variables);
  4424.     }
  4425.     /**
  4426.      * Replaces variables.
  4427.      *
  4428.      * @param array<string, mixed> $variables
  4429.      *
  4430.      * @return void
  4431.      */
  4432.     public function addVariables(array $variables)
  4433.     {
  4434.         $triggerWarning false;
  4435.         foreach ($variables as $name => $value) {
  4436.             if (!$value instanceof Number && !\is_array($value)) {
  4437.                 $triggerWarning true;
  4438.             }
  4439.             $this->registeredVars[$name] = $value;
  4440.         }
  4441.         if ($triggerWarning) {
  4442.             @trigger_error('Passing raw values to as custom variables to the Compiler is deprecated. Use "\ScssPhp\ScssPhp\ValueConverter::parseValue" or "\ScssPhp\ScssPhp\ValueConverter::fromPhp" to convert them instead.'E_USER_DEPRECATED);
  4443.         }
  4444.     }
  4445.     /**
  4446.      * Set variables
  4447.      *
  4448.      * @api
  4449.      *
  4450.      * @param array $variables
  4451.      *
  4452.      * @return void
  4453.      *
  4454.      * @deprecated Use "addVariables" or "replaceVariables" instead.
  4455.      */
  4456.     public function setVariables(array $variables)
  4457.     {
  4458.         @trigger_error('The method "setVariables" of the Compiler is deprecated. Use the "addVariables" method for the equivalent behavior or "replaceVariables" if merging with previous variables was not desired.');
  4459.         $this->addVariables($variables);
  4460.     }
  4461.     /**
  4462.      * Unset variable
  4463.      *
  4464.      * @api
  4465.      *
  4466.      * @param string $name
  4467.      *
  4468.      * @return void
  4469.      */
  4470.     public function unsetVariable($name)
  4471.     {
  4472.         unset($this->registeredVars[$name]);
  4473.     }
  4474.     /**
  4475.      * Returns list of variables
  4476.      *
  4477.      * @api
  4478.      *
  4479.      * @return array
  4480.      */
  4481.     public function getVariables()
  4482.     {
  4483.         return $this->registeredVars;
  4484.     }
  4485.     /**
  4486.      * Adds to list of parsed files
  4487.      *
  4488.      * @internal
  4489.      *
  4490.      * @param string|null $path
  4491.      *
  4492.      * @return void
  4493.      */
  4494.     public function addParsedFile($path)
  4495.     {
  4496.         if (! \is_null($path) && is_file($path)) {
  4497.             $this->parsedFiles[realpath($path)] = filemtime($path);
  4498.         }
  4499.     }
  4500.     /**
  4501.      * Returns list of parsed files
  4502.      *
  4503.      * @deprecated
  4504.      * @return array<string, int>
  4505.      */
  4506.     public function getParsedFiles()
  4507.     {
  4508.         @trigger_error('The method "getParsedFiles" of the Compiler is deprecated. Use the "getIncludedFiles" method on the CompilationResult instance returned by compileString() instead. Be careful that the signature of the method is different.'E_USER_DEPRECATED);
  4509.         return $this->parsedFiles;
  4510.     }
  4511.     /**
  4512.      * Add import path
  4513.      *
  4514.      * @api
  4515.      *
  4516.      * @param string|callable $path
  4517.      *
  4518.      * @return void
  4519.      */
  4520.     public function addImportPath($path)
  4521.     {
  4522.         if (! \in_array($path$this->importPaths)) {
  4523.             $this->importPaths[] = $path;
  4524.         }
  4525.     }
  4526.     /**
  4527.      * Set import paths
  4528.      *
  4529.      * @api
  4530.      *
  4531.      * @param string|array<string|callable> $path
  4532.      *
  4533.      * @return void
  4534.      */
  4535.     public function setImportPaths($path)
  4536.     {
  4537.         $paths = (array) $path;
  4538.         $actualImportPaths array_filter($paths, function ($path) {
  4539.             return $path !== '';
  4540.         });
  4541.         $this->legacyCwdImportPath = \count($actualImportPaths) !== \count($paths);
  4542.         if ($this->legacyCwdImportPath) {
  4543.             @trigger_error('Passing an empty string in the import paths to refer to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be used directly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.'E_USER_DEPRECATED);
  4544.         }
  4545.         $this->importPaths $actualImportPaths;
  4546.     }
  4547.     /**
  4548.      * Set number precision
  4549.      *
  4550.      * @api
  4551.      *
  4552.      * @param integer $numberPrecision
  4553.      *
  4554.      * @return void
  4555.      *
  4556.      * @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
  4557.      */
  4558.     public function setNumberPrecision($numberPrecision)
  4559.     {
  4560.         @trigger_error('The number precision is not configurable anymore. '
  4561.             'The default is enough for all browsers.'E_USER_DEPRECATED);
  4562.     }
  4563.     /**
  4564.      * Sets the output style.
  4565.      *
  4566.      * @api
  4567.      *
  4568.      * @param string $style One of the OutputStyle constants
  4569.      *
  4570.      * @return void
  4571.      *
  4572.      * @phpstan-param OutputStyle::* $style
  4573.      */
  4574.     public function setOutputStyle($style)
  4575.     {
  4576.         switch ($style) {
  4577.             case OutputStyle::EXPANDED:
  4578.                 $this->formatter Expanded::class;
  4579.                 break;
  4580.             case OutputStyle::COMPRESSED:
  4581.                 $this->formatter Compressed::class;
  4582.                 break;
  4583.             default:
  4584.                 throw new \InvalidArgumentException(sprintf('Invalid output style "%s".'$style));
  4585.         }
  4586.     }
  4587.     /**
  4588.      * Set formatter
  4589.      *
  4590.      * @api
  4591.      *
  4592.      * @param string $formatterName
  4593.      *
  4594.      * @return void
  4595.      *
  4596.      * @deprecated Use {@see setOutputStyle} instead.
  4597.      */
  4598.     public function setFormatter($formatterName)
  4599.     {
  4600.         if (!\in_array($formatterName, [Expanded::class, Compressed::class], true)) {
  4601.             @trigger_error('Formatters other than Expanded and Compressed are deprecated.'E_USER_DEPRECATED);
  4602.         }
  4603.         @trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.'E_USER_DEPRECATED);
  4604.         $this->formatter $formatterName;
  4605.     }
  4606.     /**
  4607.      * Set line number style
  4608.      *
  4609.      * @api
  4610.      *
  4611.      * @param string $lineNumberStyle
  4612.      *
  4613.      * @return void
  4614.      *
  4615.      * @deprecated The line number output is not supported anymore. Use source maps instead.
  4616.      */
  4617.     public function setLineNumberStyle($lineNumberStyle)
  4618.     {
  4619.         @trigger_error('The line number output is not supported anymore. '
  4620.                        'Use source maps instead.'E_USER_DEPRECATED);
  4621.     }
  4622.     /**
  4623.      * Configures the handling of non-ASCII outputs.
  4624.      *
  4625.      * If $charset is `true`, this will include a `@charset` declaration or a
  4626.      * UTF-8 [byte-order mark][] if the stylesheet contains any non-ASCII
  4627.      * characters. Otherwise, it will never include a `@charset` declaration or a
  4628.      * byte-order mark.
  4629.      *
  4630.      * [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
  4631.      *
  4632.      * @param bool $charset
  4633.      *
  4634.      * @return void
  4635.      */
  4636.     public function setCharset($charset)
  4637.     {
  4638.         $this->charset $charset;
  4639.     }
  4640.     /**
  4641.      * Enable/disable source maps
  4642.      *
  4643.      * @api
  4644.      *
  4645.      * @param integer $sourceMap
  4646.      *
  4647.      * @return void
  4648.      *
  4649.      * @phpstan-param self::SOURCE_MAP_* $sourceMap
  4650.      */
  4651.     public function setSourceMap($sourceMap)
  4652.     {
  4653.         $this->sourceMap $sourceMap;
  4654.     }
  4655.     /**
  4656.      * Set source map options
  4657.      *
  4658.      * @api
  4659.      *
  4660.      * @param array $sourceMapOptions
  4661.      *
  4662.      * @phpstan-param  array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $sourceMapOptions
  4663.      *
  4664.      * @return void
  4665.      */
  4666.     public function setSourceMapOptions($sourceMapOptions)
  4667.     {
  4668.         $this->sourceMapOptions $sourceMapOptions;
  4669.     }
  4670.     /**
  4671.      * Register function
  4672.      *
  4673.      * @api
  4674.      *
  4675.      * @param string        $name
  4676.      * @param callable      $callback
  4677.      * @param string[]|null $argumentDeclaration
  4678.      *
  4679.      * @return void
  4680.      */
  4681.     public function registerFunction($name$callback$argumentDeclaration null)
  4682.     {
  4683.         if (self::isNativeFunction($name)) {
  4684.             @trigger_error(sprintf('The "%s" function is a core sass function. Overriding it with a custom implementation through "%s" is deprecated and won\'t be supported in ScssPhp 2.0 anymore.'$name__METHOD__), E_USER_DEPRECATED);
  4685.         }
  4686.         if ($argumentDeclaration === null) {
  4687.             @trigger_error('Omitting the argument declaration when registering custom function is deprecated and won\'t be supported in ScssPhp 2.0 anymore.'E_USER_DEPRECATED);
  4688.         }
  4689.         $this->userFunctions[$this->normalizeName($name)] = [$callback$argumentDeclaration];
  4690.     }
  4691.     /**
  4692.      * Unregister function
  4693.      *
  4694.      * @api
  4695.      *
  4696.      * @param string $name
  4697.      *
  4698.      * @return void
  4699.      */
  4700.     public function unregisterFunction($name)
  4701.     {
  4702.         unset($this->userFunctions[$this->normalizeName($name)]);
  4703.     }
  4704.     /**
  4705.      * Add feature
  4706.      *
  4707.      * @api
  4708.      *
  4709.      * @param string $name
  4710.      *
  4711.      * @return void
  4712.      *
  4713.      * @deprecated Registering additional features is deprecated.
  4714.      */
  4715.     public function addFeature($name)
  4716.     {
  4717.         @trigger_error('Registering additional features is deprecated.'E_USER_DEPRECATED);
  4718.         $this->registeredFeatures[$name] = true;
  4719.     }
  4720.     /**
  4721.      * Import file
  4722.      *
  4723.      * @param string                                 $path
  4724.      * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
  4725.      *
  4726.      * @return void
  4727.      */
  4728.     protected function importFile($pathOutputBlock $out)
  4729.     {
  4730.         $this->pushCallStack('import ' $this->getPrettyPath($path));
  4731.         // see if tree is cached
  4732.         $realPath realpath($path);
  4733.         if (substr($path, -5) === '.sass') {
  4734.             $this->sourceIndex = \count($this->sourceNames);
  4735.             $this->sourceNames[] = $path;
  4736.             $this->sourceLine 1;
  4737.             $this->sourceColumn 1;
  4738.             throw $this->error('The Sass indented syntax is not implemented.');
  4739.         }
  4740.         if (isset($this->importCache[$realPath])) {
  4741.             $this->handleImportLoop($realPath);
  4742.             $tree $this->importCache[$realPath];
  4743.         } else {
  4744.             $code   file_get_contents($path);
  4745.             $parser $this->parserFactory($path);
  4746.             $tree   $parser->parse($code);
  4747.             $this->importCache[$realPath] = $tree;
  4748.         }
  4749.         $currentDirectory $this->currentDirectory;
  4750.         $this->currentDirectory dirname($path);
  4751.         $this->compileChildrenNoReturn($tree->children$out);
  4752.         $this->currentDirectory $currentDirectory;
  4753.         $this->popCallStack();
  4754.     }
  4755.     /**
  4756.      * Save the imported files with their resolving path context
  4757.      *
  4758.      * @param string|null $currentDirectory
  4759.      * @param string      $path
  4760.      * @param string      $filePath
  4761.      *
  4762.      * @return void
  4763.      */
  4764.     private function registerImport($currentDirectory$path$filePath)
  4765.     {
  4766.         $this->resolvedImports[] = ['currentDir' => $currentDirectory'path' => $path'filePath' => $filePath];
  4767.     }
  4768.     /**
  4769.      * Detects whether the import is a CSS import.
  4770.      *
  4771.      * For legacy reasons, custom importers are called for those, allowing them
  4772.      * to replace them with an actual Sass import. However this behavior is
  4773.      * deprecated. Custom importers are expected to return null when they receive
  4774.      * a CSS import.
  4775.      *
  4776.      * @param string $url
  4777.      *
  4778.      * @return bool
  4779.      */
  4780.     public static function isCssImport($url)
  4781.     {
  4782.         return === preg_match('~\.css$|^https?://|^//~'$url);
  4783.     }
  4784.     /**
  4785.      * Return the file path for an import url if it exists
  4786.      *
  4787.      * @internal
  4788.      *
  4789.      * @param string      $url
  4790.      * @param string|null $currentDir
  4791.      *
  4792.      * @return string|null
  4793.      */
  4794.     public function findImport($url$currentDir null)
  4795.     {
  4796.         // Vanilla css and external requests. These are not meant to be Sass imports.
  4797.         // Callback importers are still called for BC.
  4798.         if (self::isCssImport($url)) {
  4799.             foreach ($this->importPaths as $dir) {
  4800.                 if (\is_string($dir)) {
  4801.                     continue;
  4802.                 }
  4803.                 if (\is_callable($dir)) {
  4804.                     // check custom callback for import path
  4805.                     $file = \call_user_func($dir$url);
  4806.                     if (! \is_null($file)) {
  4807.                         if (\is_array($dir)) {
  4808.                             $callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1];
  4809.                         } elseif ($dir instanceof \Closure) {
  4810.                             $r = new \ReflectionFunction($dir);
  4811.                             if (false !== strpos($r->name'{closure}')) {
  4812.                                 $callableDescription sprintf('closure{%s:%s}'$r->getFileName(), $r->getStartLine());
  4813.                             } elseif ($class $r->getClosureScopeClass()) {
  4814.                                 $callableDescription $class->name.'::'.$r->name;
  4815.                             } else {
  4816.                                 $callableDescription $r->name;
  4817.                             }
  4818.                         } elseif (\is_object($dir)) {
  4819.                             $callableDescription = \get_class($dir) . '::__invoke';
  4820.                         } else {
  4821.                             $callableDescription 'callable'// Fallback if we don't have a dedicated description
  4822.                         }
  4823.                         @trigger_error(sprintf('Returning a file to import for CSS or external references in custom importer callables is deprecated and will not be supported anymore in ScssPhp 2.0. This behavior is not compliant with the Sass specification. Update your "%s" importer.'$callableDescription), E_USER_DEPRECATED);
  4824.                         return $file;
  4825.                     }
  4826.                 }
  4827.             }
  4828.             return null;
  4829.         }
  4830.         if (!\is_null($currentDir)) {
  4831.             $relativePath $this->resolveImportPath($url$currentDir);
  4832.             if (!\is_null($relativePath)) {
  4833.                 return $relativePath;
  4834.             }
  4835.         }
  4836.         foreach ($this->importPaths as $dir) {
  4837.             if (\is_string($dir)) {
  4838.                 $path $this->resolveImportPath($url$dir);
  4839.                 if (!\is_null($path)) {
  4840.                     return $path;
  4841.                 }
  4842.             } elseif (\is_callable($dir)) {
  4843.                 // check custom callback for import path
  4844.                 $file = \call_user_func($dir$url);
  4845.                 if (! \is_null($file)) {
  4846.                     return $file;
  4847.                 }
  4848.             }
  4849.         }
  4850.         if ($this->legacyCwdImportPath) {
  4851.             $path $this->resolveImportPath($urlgetcwd());
  4852.             if (!\is_null($path)) {
  4853.                 @trigger_error('Resolving imports relatively to the current working directory is deprecated. If that\'s the intended behavior, the value of "getcwd()" should be added as an import path explicitly instead. If this was used for resolving relative imports of the input alongside "chdir" with the source directory, the path of the input file should be passed to "compileString()" instead.'E_USER_DEPRECATED);
  4854.                 return $path;
  4855.             }
  4856.         }
  4857.         throw $this->error("`$url` file not found for @import");
  4858.     }
  4859.     /**
  4860.      * @param string $url
  4861.      * @param string $baseDir
  4862.      *
  4863.      * @return string|null
  4864.      */
  4865.     private function resolveImportPath($url$baseDir)
  4866.     {
  4867.         $path Path::join($baseDir$url);
  4868.         $hasExtension preg_match('/.s[ac]ss$/'$url);
  4869.         if ($hasExtension) {
  4870.             return $this->checkImportPathConflicts($this->tryImportPath($path));
  4871.         }
  4872.         $result $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path));
  4873.         if (!\is_null($result)) {
  4874.             return $result;
  4875.         }
  4876.         return $this->tryImportPathAsDirectory($path);
  4877.     }
  4878.     /**
  4879.      * @param string[] $paths
  4880.      *
  4881.      * @return string|null
  4882.      */
  4883.     private function checkImportPathConflicts(array $paths)
  4884.     {
  4885.         if (\count($paths) === 0) {
  4886.             return null;
  4887.         }
  4888.         if (\count($paths) === 1) {
  4889.             return $paths[0];
  4890.         }
  4891.         $formattedPrettyPaths = [];
  4892.         foreach ($paths as $path) {
  4893.             $formattedPrettyPaths[] = '  ' $this->getPrettyPath($path);
  4894.         }
  4895.         throw $this->error("It's not clear which file to import. Found:\n" implode("\n"$formattedPrettyPaths));
  4896.     }
  4897.     /**
  4898.      * @param string $path
  4899.      *
  4900.      * @return string[]
  4901.      */
  4902.     private function tryImportPathWithExtensions($path)
  4903.     {
  4904.         $result array_merge(
  4905.             $this->tryImportPath($path.'.sass'),
  4906.             $this->tryImportPath($path.'.scss')
  4907.         );
  4908.         if ($result) {
  4909.             return $result;
  4910.         }
  4911.         return $this->tryImportPath($path.'.css');
  4912.     }
  4913.     /**
  4914.      * @param string $path
  4915.      *
  4916.      * @return string[]
  4917.      */
  4918.     private function tryImportPath($path)
  4919.     {
  4920.         $partial dirname($path).'/_'.basename($path);
  4921.         $candidates = [];
  4922.         if (is_file($partial)) {
  4923.             $candidates[] = $partial;
  4924.         }
  4925.         if (is_file($path)) {
  4926.             $candidates[] = $path;
  4927.         }
  4928.         return $candidates;
  4929.     }
  4930.     /**
  4931.      * @param string $path
  4932.      *
  4933.      * @return string|null
  4934.      */
  4935.     private function tryImportPathAsDirectory($path)
  4936.     {
  4937.         if (!is_dir($path)) {
  4938.             return null;
  4939.         }
  4940.         return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index'));
  4941.     }
  4942.     /**
  4943.      * @param string|null $path
  4944.      *
  4945.      * @return string
  4946.      */
  4947.     private function getPrettyPath($path)
  4948.     {
  4949.         if ($path === null) {
  4950.             return '(unknown file)';
  4951.         }
  4952.         $normalizedPath $path;
  4953.         $normalizedRootDirectory $this->rootDirectory.'/';
  4954.         if (\DIRECTORY_SEPARATOR === '\\') {
  4955.             $normalizedRootDirectory str_replace('\\''/'$normalizedRootDirectory);
  4956.             $normalizedPath str_replace('\\''/'$path);
  4957.         }
  4958.         if (=== strpos($normalizedPath$normalizedRootDirectory)) {
  4959.             return substr($path, \strlen($normalizedRootDirectory));
  4960.         }
  4961.         return $path;
  4962.     }
  4963.     /**
  4964.      * Set encoding
  4965.      *
  4966.      * @api
  4967.      *
  4968.      * @param string|null $encoding
  4969.      *
  4970.      * @return void
  4971.      *
  4972.      * @deprecated Non-compliant support for other encodings than UTF-8 is deprecated.
  4973.      */
  4974.     public function setEncoding($encoding)
  4975.     {
  4976.         if (!$encoding || strtolower($encoding) === 'utf-8') {
  4977.             @trigger_error(sprintf('The "%s" method is deprecated.'__METHOD__), E_USER_DEPRECATED);
  4978.         } else {
  4979.             @trigger_error(sprintf('The "%s" method is deprecated. Parsing will only support UTF-8 in ScssPhp 2.0. The non-UTF-8 parsing of ScssPhp 1.x is not spec compliant.'__METHOD__), E_USER_DEPRECATED);
  4980.         }
  4981.         $this->encoding $encoding;
  4982.     }
  4983.     /**
  4984.      * Ignore errors?
  4985.      *
  4986.      * @api
  4987.      *
  4988.      * @param boolean $ignoreErrors
  4989.      *
  4990.      * @return \ScssPhp\ScssPhp\Compiler
  4991.      *
  4992.      * @deprecated Ignoring Sass errors is not longer supported.
  4993.      */
  4994.     public function setIgnoreErrors($ignoreErrors)
  4995.     {
  4996.         @trigger_error('Ignoring Sass errors is not longer supported.'E_USER_DEPRECATED);
  4997.         return $this;
  4998.     }
  4999.     /**
  5000.      * Get source position
  5001.      *
  5002.      * @api
  5003.      *
  5004.      * @return array
  5005.      *
  5006.      * @deprecated
  5007.      */
  5008.     public function getSourcePosition()
  5009.     {
  5010.         @trigger_error(sprintf('The "%s" method is deprecated.'__METHOD__), E_USER_DEPRECATED);
  5011.         $sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
  5012.         return [$sourceFile$this->sourceLine$this->sourceColumn];
  5013.     }
  5014.     /**
  5015.      * Throw error (exception)
  5016.      *
  5017.      * @api
  5018.      *
  5019.      * @param string $msg Message with optional sprintf()-style vararg parameters
  5020.      *
  5021.      * @throws \ScssPhp\ScssPhp\Exception\CompilerException
  5022.      *
  5023.      * @deprecated use "error" and throw the exception in the caller instead.
  5024.      */
  5025.     public function throwError($msg)
  5026.     {
  5027.         @trigger_error(
  5028.             'The method "throwError" is deprecated. Use "error" and throw the exception in the caller instead',
  5029.             E_USER_DEPRECATED
  5030.         );
  5031.         throw $this->error(...func_get_args());
  5032.     }
  5033.     /**
  5034.      * Build an error (exception)
  5035.      *
  5036.      * @internal
  5037.      *
  5038.      * @param string $msg Message with optional sprintf()-style vararg parameters
  5039.      *
  5040.      * @return CompilerException
  5041.      */
  5042.     public function error($msg, ...$args)
  5043.     {
  5044.         if ($args) {
  5045.             $msg sprintf($msg, ...$args);
  5046.         }
  5047.         if (! $this->ignoreCallStackMessage) {
  5048.             $msg $this->addLocationToMessage($msg);
  5049.         }
  5050.         return new CompilerException($msg);
  5051.     }
  5052.     /**
  5053.      * @param string $msg
  5054.      *
  5055.      * @return string
  5056.      */
  5057.     private function addLocationToMessage($msg)
  5058.     {
  5059.         $line   $this->sourceLine;
  5060.         $column $this->sourceColumn;
  5061.         $loc = isset($this->sourceNames[$this->sourceIndex])
  5062.             ? $this->getPrettyPath($this->sourceNames[$this->sourceIndex]) . " on line $line, at column $column"
  5063.             "line: $line, column: $column";
  5064.         $msg "$msg$loc";
  5065.         $callStackMsg $this->callStackMessage();
  5066.         if ($callStackMsg) {
  5067.             $msg .= "\nCall Stack:\n" $callStackMsg;
  5068.         }
  5069.         return $msg;
  5070.     }
  5071.     /**
  5072.      * @param string $functionName
  5073.      * @param array $ExpectedArgs
  5074.      * @param int $nbActual
  5075.      * @return CompilerException
  5076.      *
  5077.      * @deprecated
  5078.      */
  5079.     public function errorArgsNumber($functionName$ExpectedArgs$nbActual)
  5080.     {
  5081.         @trigger_error(sprintf('The "%s" method is deprecated.'__METHOD__), E_USER_DEPRECATED);
  5082.         $nbExpected = \count($ExpectedArgs);
  5083.         if ($nbActual $nbExpected) {
  5084.             return $this->error(
  5085.                 'Error: Only %d arguments allowed in %s(), but %d were passed.',
  5086.                 $nbExpected,
  5087.                 $functionName,
  5088.                 $nbActual
  5089.             );
  5090.         } else {
  5091.             $missing = [];
  5092.             while (count($ExpectedArgs) && count($ExpectedArgs) > $nbActual) {
  5093.                 array_unshift($missingarray_pop($ExpectedArgs));
  5094.             }
  5095.             return $this->error(
  5096.                 'Error: %s() argument%s %s missing.',
  5097.                 $functionName,
  5098.                 count($missing) > 's' '',
  5099.                 implode(', '$missing)
  5100.             );
  5101.         }
  5102.     }
  5103.     /**
  5104.      * Beautify call stack for output
  5105.      *
  5106.      * @param boolean  $all
  5107.      * @param int|null $limit
  5108.      *
  5109.      * @return string
  5110.      */
  5111.     protected function callStackMessage($all false$limit null)
  5112.     {
  5113.         $callStackMsg = [];
  5114.         $ncall 0;
  5115.         if ($this->callStack) {
  5116.             foreach (array_reverse($this->callStack) as $call) {
  5117.                 if ($all || (isset($call['n']) && $call['n'])) {
  5118.                     $msg '#' $ncall++ . ' ' $call['n'] . ' ';
  5119.                     $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
  5120.                           ? $this->getPrettyPath($this->sourceNames[$call[Parser::SOURCE_INDEX]])
  5121.                           : '(unknown file)');
  5122.                     $msg .= ' on line ' $call[Parser::SOURCE_LINE];
  5123.                     $callStackMsg[] = $msg;
  5124.                     if (! \is_null($limit) && $ncall $limit) {
  5125.                         break;
  5126.                     }
  5127.                 }
  5128.             }
  5129.         }
  5130.         return implode("\n"$callStackMsg);
  5131.     }
  5132.     /**
  5133.      * Handle import loop
  5134.      *
  5135.      * @param string $name
  5136.      *
  5137.      * @throws \Exception
  5138.      */
  5139.     protected function handleImportLoop($name)
  5140.     {
  5141.         for ($env $this->env$env$env $env->parent) {
  5142.             if (! $env->block) {
  5143.                 continue;
  5144.             }
  5145.             $file $this->sourceNames[$env->block->sourceIndex];
  5146.             if ($file === null) {
  5147.                 continue;
  5148.             }
  5149.             if (realpath($file) === $name) {
  5150.                 throw $this->error('An @import loop has been found: %s imports %s'$filebasename($file));
  5151.             }
  5152.         }
  5153.     }
  5154.     /**
  5155.      * Call SCSS @function
  5156.      *
  5157.      * @param Object $func
  5158.      * @param array  $argValues
  5159.      *
  5160.      * @return array|Number
  5161.      */
  5162.     protected function callScssFunction($func$argValues)
  5163.     {
  5164.         if (! $func) {
  5165.             return static::$defaultValue;
  5166.         }
  5167.         $name $func->name;
  5168.         $this->pushEnv();
  5169.         // set the args
  5170.         if (isset($func->args)) {
  5171.             $this->applyArguments($func->args$argValues);
  5172.         }
  5173.         // throw away lines and children
  5174.         $tmp = new OutputBlock();
  5175.         $tmp->lines    = [];
  5176.         $tmp->children = [];
  5177.         $this->env->marker 'function';
  5178.         if (! empty($func->parentEnv)) {
  5179.             $this->env->declarationScopeParent $func->parentEnv;
  5180.         } else {
  5181.             throw $this->error("@function $name() without parentEnv");
  5182.         }
  5183.         $ret $this->compileChildren($func->children$tmp$this->env->marker ' ' $name);
  5184.         $this->popEnv();
  5185.         return ! isset($ret) ? static::$defaultValue $ret;
  5186.     }
  5187.     /**
  5188.      * Call built-in and registered (PHP) functions
  5189.      *
  5190.      * @param string $name
  5191.      * @param callable $function
  5192.      * @param array  $prototype
  5193.      * @param array  $args
  5194.      *
  5195.      * @return array|Number|null
  5196.      */
  5197.     protected function callNativeFunction($name$function$prototype$args)
  5198.     {
  5199.         $libName = (is_array($function) ? end($function) : null);
  5200.         $sorted_kwargs $this->sortNativeFunctionArgs($libName$prototype$args);
  5201.         if (\is_null($sorted_kwargs)) {
  5202.             return null;
  5203.         }
  5204.         @list($sorted$kwargs) = $sorted_kwargs;
  5205.         if ($name !== 'if') {
  5206.             foreach ($sorted as &$val) {
  5207.                 if ($val !== null) {
  5208.                     $val $this->reduce($valtrue);
  5209.                 }
  5210.             }
  5211.         }
  5212.         $returnValue = \call_user_func($function$sorted$kwargs);
  5213.         if (! isset($returnValue)) {
  5214.             return null;
  5215.         }
  5216.         if (\is_array($returnValue) || $returnValue instanceof Number) {
  5217.             return $returnValue;
  5218.         }
  5219.         @trigger_error(sprintf('Returning a PHP value from the "%s" custom function is deprecated. A sass value must be returned instead.'$name), E_USER_DEPRECATED);
  5220.         return $this->coerceValue($returnValue);
  5221.     }
  5222.     /**
  5223.      * Get built-in function
  5224.      *
  5225.      * @param string $name Normalized name
  5226.      *
  5227.      * @return array
  5228.      */
  5229.     protected function getBuiltinFunction($name)
  5230.     {
  5231.         $libName self::normalizeNativeFunctionName($name);
  5232.         return [$this$libName];
  5233.     }
  5234.     /**
  5235.      * Normalize native function name
  5236.      *
  5237.      * @internal
  5238.      *
  5239.      * @param string $name
  5240.      *
  5241.      * @return string
  5242.      */
  5243.     public static function normalizeNativeFunctionName($name)
  5244.     {
  5245.         $name str_replace("-""_"$name);
  5246.         $libName 'lib' preg_replace_callback(
  5247.             '/_(.)/',
  5248.             function ($m) {
  5249.                 return ucfirst($m[1]);
  5250.             },
  5251.             ucfirst($name)
  5252.         );
  5253.         return $libName;
  5254.     }
  5255.     /**
  5256.      * Check if a function is a native built-in scss function, for css parsing
  5257.      *
  5258.      * @internal
  5259.      *
  5260.      * @param string $name
  5261.      *
  5262.      * @return bool
  5263.      */
  5264.     public static function isNativeFunction($name)
  5265.     {
  5266.         return method_exists(Compiler::class, self::normalizeNativeFunctionName($name));
  5267.     }
  5268.     /**
  5269.      * Sorts keyword arguments
  5270.      *
  5271.      * @param string $functionName
  5272.      * @param array|null  $prototypes
  5273.      * @param array  $args
  5274.      *
  5275.      * @return array|null
  5276.      */
  5277.     protected function sortNativeFunctionArgs($functionName$prototypes$args)
  5278.     {
  5279.         static $parser null;
  5280.         if (! isset($prototypes)) {
  5281.             $keyArgs = [];
  5282.             $posArgs = [];
  5283.             if (\is_array($args) && \count($args) && \end($args) === static::$null) {
  5284.                 array_pop($args);
  5285.             }
  5286.             // separate positional and keyword arguments
  5287.             foreach ($args as $arg) {
  5288.                 list($key$value) = $arg;
  5289.                 if (empty($key) or empty($key[1])) {
  5290.                     $posArgs[] = empty($arg[2]) ? $value $arg;
  5291.                 } else {
  5292.                     $keyArgs[$key[1]] = $value;
  5293.                 }
  5294.             }
  5295.             return [$posArgs$keyArgs];
  5296.         }
  5297.         // specific cases ?
  5298.         if (\in_array($functionName, ['libRgb''libRgba''libHsl''libHsla'])) {
  5299.             // notation 100 127 255 / 0 is in fact a simple list of 4 values
  5300.             foreach ($args as $k => $arg) {
  5301.                 if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
  5302.                     $args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]);
  5303.                 }
  5304.             }
  5305.         }
  5306.         list($positionalArgs$namedArgs$names$separator$hasSplat) = $this->evaluateArguments($argsfalse);
  5307.         if (! \is_array(reset($prototypes))) {
  5308.             $prototypes = [$prototypes];
  5309.         }
  5310.         $parsedPrototypes array_map([$this'parseFunctionPrototype'], $prototypes);
  5311.         assert(!empty($parsedPrototypes));
  5312.         $matchedPrototype $this->selectFunctionPrototype($parsedPrototypes, \count($positionalArgs), $names);
  5313.         $this->verifyPrototype($matchedPrototype, \count($positionalArgs), $names$hasSplat);
  5314.         $vars $this->applyArgumentsToDeclaration($matchedPrototype$positionalArgs$namedArgs$separator);
  5315.         $finalArgs = [];
  5316.         $keyArgs = [];
  5317.         foreach ($matchedPrototype['arguments'] as $argument) {
  5318.             list($normalizedName$originalName$default) = $argument;
  5319.             if (isset($vars[$normalizedName])) {
  5320.                 $value $vars[$normalizedName];
  5321.             } else {
  5322.                 $value $default;
  5323.             }
  5324.             // special null value as default: translate to real null here
  5325.             if ($value === [Type::T_KEYWORD'null']) {
  5326.                 $value null;
  5327.             }
  5328.             $finalArgs[] = $value;
  5329.             $keyArgs[$originalName] = $value;
  5330.         }
  5331.         if ($matchedPrototype['rest_argument'] !== null) {
  5332.             $value $vars[$matchedPrototype['rest_argument']];
  5333.             $finalArgs[] = $value;
  5334.             $keyArgs[$matchedPrototype['rest_argument']] = $value;
  5335.         }
  5336.         return [$finalArgs$keyArgs];
  5337.     }
  5338.     /**
  5339.      * Parses a function prototype to the internal representation of arguments.
  5340.      *
  5341.      * The input is an array of strings describing each argument, as supported
  5342.      * in {@see registerFunction}. Argument names don't include the `$`.
  5343.      * The output contains the list of positional argument, with their normalized
  5344.      * name (underscores are replaced by dashes), their original name (to be used
  5345.      * in case of error reporting) and their default value. The output also contains
  5346.      * the normalized name of the rest argument, or null if the function prototype
  5347.      * is not variadic.
  5348.      *
  5349.      * @param string[] $prototype
  5350.      *
  5351.      * @return array
  5352.      * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
  5353.      */
  5354.     private function parseFunctionPrototype(array $prototype)
  5355.     {
  5356.         static $parser null;
  5357.         $arguments = [];
  5358.         $restArgument null;
  5359.         foreach ($prototype as $p) {
  5360.             if (null !== $restArgument) {
  5361.                 throw new \InvalidArgumentException('The argument declaration is invalid. The rest argument must be the last one.');
  5362.             }
  5363.             $default null;
  5364.             $p explode(':'$p2);
  5365.             $name str_replace('_''-'$p[0]);
  5366.             if (isset($p[1])) {
  5367.                 $defaultSource trim($p[1]);
  5368.                 if ($defaultSource === 'null') {
  5369.                     // differentiate this null from the static::$null
  5370.                     $default = [Type::T_KEYWORD'null'];
  5371.                 } else {
  5372.                     if (\is_null($parser)) {
  5373.                         $parser $this->parserFactory(__METHOD__);
  5374.                     }
  5375.                     $parser->parseValue($defaultSource$default);
  5376.                 }
  5377.             }
  5378.             if (substr($name, -3) === '...') {
  5379.                 $restArgument substr($name0, -3);
  5380.             } else {
  5381.                 $arguments[] = [$name$p[0], $default];
  5382.             }
  5383.         }
  5384.         return [
  5385.             'arguments' => $arguments,
  5386.             'rest_argument' => $restArgument,
  5387.         ];
  5388.     }
  5389.     /**
  5390.      * Returns the function prototype for the given positional and named arguments.
  5391.      *
  5392.      * If no exact match is found, finds the closest approximation. Note that this
  5393.      * doesn't guarantee that $positional and $names are valid for the returned
  5394.      * prototype.
  5395.      *
  5396.      * @param array[]               $prototypes
  5397.      * @param int                   $positional
  5398.      * @param array<string, string> $names A set of names, as both keys and values
  5399.      *
  5400.      * @return array
  5401.      *
  5402.      * @phpstan-param non-empty-list<array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}> $prototypes
  5403.      * @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
  5404.      */
  5405.     private function selectFunctionPrototype(array $prototypes$positional, array $names)
  5406.     {
  5407.         $fuzzyMatch null;
  5408.         $minMismatchDistance null;
  5409.         foreach ($prototypes as $prototype) {
  5410.             // Ideally, find an exact match.
  5411.             if ($this->checkPrototypeMatches($prototype$positional$names)) {
  5412.                 return $prototype;
  5413.             }
  5414.             $mismatchDistance = \count($prototype['arguments']) - $positional;
  5415.             if ($minMismatchDistance !== null) {
  5416.                 if (abs($mismatchDistance) > abs($minMismatchDistance)) {
  5417.                     continue;
  5418.                 }
  5419.                 // If two overloads have the same mismatch distance, favor the overload
  5420.                 // that has more arguments.
  5421.                 if (abs($mismatchDistance) === abs($minMismatchDistance) && $mismatchDistance 0) {
  5422.                     continue;
  5423.                 }
  5424.             }
  5425.             $minMismatchDistance $mismatchDistance;
  5426.             $fuzzyMatch $prototype;
  5427.         }
  5428.         return $fuzzyMatch;
  5429.     }
  5430.     /**
  5431.      * Checks whether the argument invocation matches the callable prototype.
  5432.      *
  5433.      * The rules are similar to {@see verifyPrototype}. The boolean return value
  5434.      * avoids the overhead of building and catching exceptions when the reason of
  5435.      * not matching the prototype does not need to be known.
  5436.      *
  5437.      * @param array                 $prototype
  5438.      * @param int                   $positional
  5439.      * @param array<string, string> $names
  5440.      *
  5441.      * @return bool
  5442.      *
  5443.      * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
  5444.      */
  5445.     private function checkPrototypeMatches(array $prototype$positional, array $names)
  5446.     {
  5447.         $nameUsed 0;
  5448.         foreach ($prototype['arguments'] as $i => $argument) {
  5449.             list ($name$originalName$default) = $argument;
  5450.             if ($i $positional) {
  5451.                 if (isset($names[$name])) {
  5452.                     return false;
  5453.                 }
  5454.             } elseif (isset($names[$name])) {
  5455.                 $nameUsed++;
  5456.             } elseif ($default === null) {
  5457.                 return false;
  5458.             }
  5459.         }
  5460.         if ($prototype['rest_argument'] !== null) {
  5461.             return true;
  5462.         }
  5463.         if ($positional > \count($prototype['arguments'])) {
  5464.             return false;
  5465.         }
  5466.         if ($nameUsed < \count($names)) {
  5467.             return false;
  5468.         }
  5469.         return true;
  5470.     }
  5471.     /**
  5472.      * Verifies that the argument invocation is valid for the callable prototype.
  5473.      *
  5474.      * @param array                 $prototype
  5475.      * @param int                   $positional
  5476.      * @param array<string, string> $names
  5477.      * @param bool                  $hasSplat
  5478.      *
  5479.      * @return void
  5480.      *
  5481.      * @throws SassScriptException
  5482.      *
  5483.      * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
  5484.      */
  5485.     private function verifyPrototype(array $prototype$positional, array $names$hasSplat)
  5486.     {
  5487.         $nameUsed 0;
  5488.         foreach ($prototype['arguments'] as $i => $argument) {
  5489.             list ($name$originalName$default) = $argument;
  5490.             if ($i $positional) {
  5491.                 if (isset($names[$name])) {
  5492.                     throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.'$originalName));
  5493.                 }
  5494.             } elseif (isset($names[$name])) {
  5495.                 $nameUsed++;
  5496.             } elseif ($default === null) {
  5497.                 throw new SassScriptException(sprintf('Missing argument $%s'$originalName));
  5498.             }
  5499.         }
  5500.         if ($prototype['rest_argument'] !== null) {
  5501.             return;
  5502.         }
  5503.         if ($positional > \count($prototype['arguments'])) {
  5504.             $message sprintf(
  5505.                 'Only %d %sargument%s allowed, but %d %s passed.',
  5506.                 \count($prototype['arguments']),
  5507.                 empty($names) ? '' 'positional ',
  5508.                 \count($prototype['arguments']) === '' 's',
  5509.                 $positional,
  5510.                 $positional === 'was' 'were'
  5511.             );
  5512.             if (!$hasSplat) {
  5513.                 throw new SassScriptException($message);
  5514.             }
  5515.             $message $this->addLocationToMessage($message);
  5516.             $message .= "\nThis will be an error in future versions of Sass.";
  5517.             $this->logger->warn($messagetrue);
  5518.         }
  5519.         if ($nameUsed < \count($names)) {
  5520.             $unknownNames array_values(array_diff($namesarray_column($prototype['arguments'], 0)));
  5521.             $lastName array_pop($unknownNames);
  5522.             $message sprintf(
  5523.                 'No argument%s named $%s%s.',
  5524.                 $unknownNames 's' '',
  5525.                 $unknownNames implode(', $'$unknownNames) . ' or $' '',
  5526.                 $lastName
  5527.             );
  5528.             throw new SassScriptException($message);
  5529.         }
  5530.     }
  5531.     /**
  5532.      * Evaluates the argument from the invocation.
  5533.      *
  5534.      * This returns several things about this invocation:
  5535.      * - the list of positional arguments
  5536.      * - the map of named arguments, indexed by normalized names
  5537.      * - the set of names used in the arguments (that's an array using the normalized names as keys for O(1) access)
  5538.      * - the separator used by the list using the splat operator, if any
  5539.      * - a boolean indicator whether any splat argument (list or map) was used, to support the incomplete error reporting.
  5540.      *
  5541.      * @param array[] $args
  5542.      * @param bool    $reduce Whether arguments should be reduced to their value
  5543.      *
  5544.      * @return array
  5545.      *
  5546.      * @throws SassScriptException
  5547.      *
  5548.      * @phpstan-return array{0: list<array|Number>, 1: array<string, array|Number>, 2: array<string, string>, 3: string|null, 4: bool}
  5549.      */
  5550.     private function evaluateArguments(array $args$reduce true)
  5551.     {
  5552.         // this represents trailing commas
  5553.         if (\count($args) && end($args) === static::$null) {
  5554.             array_pop($args);
  5555.         }
  5556.         $splatSeparator null;
  5557.         $keywordArgs = [];
  5558.         $names = [];
  5559.         $positionalArgs = [];
  5560.         $hasKeywordArgument false;
  5561.         $hasSplat false;
  5562.         foreach ($args as $arg) {
  5563.             if (!empty($arg[0])) {
  5564.                 $hasKeywordArgument true;
  5565.                 assert(\is_string($arg[0][1]));
  5566.                 $name str_replace('_''-'$arg[0][1]);
  5567.                 if (isset($keywordArgs[$name])) {
  5568.                     throw new SassScriptException(sprintf('Duplicate named argument $%s.'$arg[0][1]));
  5569.                 }
  5570.                 $keywordArgs[$name] = $this->maybeReduce($reduce$arg[1]);
  5571.                 $names[$name] = $name;
  5572.             } elseif (! empty($arg[2])) {
  5573.                 // $arg[2] means a var followed by ... in the arg ($list... )
  5574.                 $val $this->reduce($arg[1], true);
  5575.                 $hasSplat true;
  5576.                 if ($val[0] === Type::T_LIST) {
  5577.                     foreach ($val[2] as $item) {
  5578.                         if (\is_null($splatSeparator)) {
  5579.                             $splatSeparator $val[1];
  5580.                         }
  5581.                         $positionalArgs[] = $this->maybeReduce($reduce$item);
  5582.                     }
  5583.                     if (isset($val[3]) && \is_array($val[3])) {
  5584.                         foreach ($val[3] as $name => $item) {
  5585.                             assert(\is_string($name));
  5586.                             $normalizedName str_replace('_''-'$name);
  5587.                             if (isset($keywordArgs[$normalizedName])) {
  5588.                                 throw new SassScriptException(sprintf('Duplicate named argument $%s.'$name));
  5589.                             }
  5590.                             $keywordArgs[$normalizedName] = $this->maybeReduce($reduce$item);
  5591.                             $names[$normalizedName] = $normalizedName;
  5592.                             $hasKeywordArgument true;
  5593.                         }
  5594.                     }
  5595.                 } elseif ($val[0] === Type::T_MAP) {
  5596.                     foreach ($val[1] as $i => $name) {
  5597.                         $name $this->compileStringContent($this->coerceString($name));
  5598.                         $item $val[2][$i];
  5599.                         if (! is_numeric($name)) {
  5600.                             $normalizedName str_replace('_''-'$name);
  5601.                             if (isset($keywordArgs[$normalizedName])) {
  5602.                                 throw new SassScriptException(sprintf('Duplicate named argument $%s.'$name));
  5603.                             }
  5604.                             $keywordArgs[$normalizedName] = $this->maybeReduce($reduce$item);
  5605.                             $names[$normalizedName] = $normalizedName;
  5606.                             $hasKeywordArgument true;
  5607.                         } else {
  5608.                             if (\is_null($splatSeparator)) {
  5609.                                 $splatSeparator $val[1];
  5610.                             }
  5611.                             $positionalArgs[] = $this->maybeReduce($reduce$item);
  5612.                         }
  5613.                     }
  5614.                 } elseif ($val[0] !== Type::T_NULL) { // values other than null are treated a single-element lists, while null is the empty list
  5615.                     $positionalArgs[] = $this->maybeReduce($reduce$val);
  5616.                 }
  5617.             } elseif ($hasKeywordArgument) {
  5618.                 throw new SassScriptException('Positional arguments must come before keyword arguments.');
  5619.             } else {
  5620.                 $positionalArgs[] = $this->maybeReduce($reduce$arg[1]);
  5621.             }
  5622.         }
  5623.         return [$positionalArgs$keywordArgs$names$splatSeparator$hasSplat];
  5624.     }
  5625.     /**
  5626.      * @param bool         $reduce
  5627.      * @param array|Number $value
  5628.      *
  5629.      * @return array|Number
  5630.      */
  5631.     private function maybeReduce($reduce$value)
  5632.     {
  5633.         if ($reduce) {
  5634.             return $this->reduce($valuetrue);
  5635.         }
  5636.         return $value;
  5637.     }
  5638.     /**
  5639.      * Apply argument values per definition
  5640.      *
  5641.      * @param array[]    $argDef
  5642.      * @param array|null $argValues
  5643.      * @param boolean $storeInEnv
  5644.      * @param boolean $reduce
  5645.      *   only used if $storeInEnv = false
  5646.      *
  5647.      * @return array<string, array|Number>
  5648.      *
  5649.      * @phpstan-param list<array{0: string, 1: array|Number|null, 2: bool}> $argDef
  5650.      *
  5651.      * @throws \Exception
  5652.      */
  5653.     protected function applyArguments($argDef$argValues$storeInEnv true$reduce true)
  5654.     {
  5655.         $output = [];
  5656.         if (\is_null($argValues)) {
  5657.             $argValues = [];
  5658.         }
  5659.         if ($storeInEnv) {
  5660.             $storeEnv $this->getStoreEnv();
  5661.             $env = new Environment();
  5662.             $env->store $storeEnv->store;
  5663.         }
  5664.         $prototype = ['arguments' => [], 'rest_argument' => null];
  5665.         $originalRestArgumentName null;
  5666.         foreach ($argDef as $i => $arg) {
  5667.             list($name$default$isVariable) = $arg;
  5668.             $normalizedName str_replace('_''-'$name);
  5669.             if ($isVariable) {
  5670.                 $originalRestArgumentName $name;
  5671.                 $prototype['rest_argument'] = $normalizedName;
  5672.             } else {
  5673.                 $prototype['arguments'][] = [$normalizedName$name, !empty($default) ? $default null];
  5674.             }
  5675.         }
  5676.         list($positionalArgs$namedArgs$names$splatSeparator$hasSplat) = $this->evaluateArguments($argValues$reduce);
  5677.         $this->verifyPrototype($prototype, \count($positionalArgs), $names$hasSplat);
  5678.         $vars $this->applyArgumentsToDeclaration($prototype$positionalArgs$namedArgs$splatSeparator);
  5679.         foreach ($prototype['arguments'] as $argument) {
  5680.             list($normalizedName$name) = $argument;
  5681.             if (!isset($vars[$normalizedName])) {
  5682.                 continue;
  5683.             }
  5684.             $val $vars[$normalizedName];
  5685.             if ($storeInEnv) {
  5686.                 $this->set($name$this->reduce($valtrue), true$env);
  5687.             } else {
  5688.                 $output[$name] = ($reduce $this->reduce($valtrue) : $val);
  5689.             }
  5690.         }
  5691.         if ($prototype['rest_argument'] !== null) {
  5692.             assert($originalRestArgumentName !== null);
  5693.             $name $originalRestArgumentName;
  5694.             $val $vars[$prototype['rest_argument']];
  5695.             if ($storeInEnv) {
  5696.                 $this->set($name$this->reduce($valtrue), true$env);
  5697.             } else {
  5698.                 $output[$name] = ($reduce $this->reduce($valtrue) : $val);
  5699.             }
  5700.         }
  5701.         if ($storeInEnv) {
  5702.             $storeEnv->store $env->store;
  5703.         }
  5704.         foreach ($prototype['arguments'] as $argument) {
  5705.             list($normalizedName$name$default) = $argument;
  5706.             if (isset($vars[$normalizedName])) {
  5707.                 continue;
  5708.             }
  5709.             assert($default !== null);
  5710.             if ($storeInEnv) {
  5711.                 $this->set($name$this->reduce($defaulttrue), true);
  5712.             } else {
  5713.                 $output[$name] = ($reduce $this->reduce($defaulttrue) : $default);
  5714.             }
  5715.         }
  5716.         return $output;
  5717.     }
  5718.     /**
  5719.      * Apply argument values per definition.
  5720.      *
  5721.      * This method assumes that the arguments are valid for the provided prototype.
  5722.      * The validation with {@see verifyPrototype} must have been run before calling
  5723.      * it.
  5724.      * Arguments are returned as a map from the normalized argument names to the
  5725.      * value. Additional arguments are collected in a sass argument list available
  5726.      * under the name of the rest argument in the result.
  5727.      *
  5728.      * Defaults are not applied as they are resolved in a different environment.
  5729.      *
  5730.      * @param array                       $prototype
  5731.      * @param array<array|Number>         $positionalArgs
  5732.      * @param array<string, array|Number> $namedArgs
  5733.      * @param string|null                 $splatSeparator
  5734.      *
  5735.      * @return array<string, array|Number>
  5736.      *
  5737.      * @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
  5738.      */
  5739.     private function applyArgumentsToDeclaration(array $prototype, array $positionalArgs, array $namedArgs$splatSeparator)
  5740.     {
  5741.         $output = [];
  5742.         $minLength min(\count($positionalArgs), \count($prototype['arguments']));
  5743.         for ($i 0$i $minLength$i++) {
  5744.             list($name) = $prototype['arguments'][$i];
  5745.             $val $positionalArgs[$i];
  5746.             $output[$name] = $val;
  5747.         }
  5748.         $restNamed $namedArgs;
  5749.         for ($i = \count($positionalArgs); $i < \count($prototype['arguments']); $i++) {
  5750.             $argument $prototype['arguments'][$i];
  5751.             list($name) = $argument;
  5752.             if (isset($namedArgs[$name])) {
  5753.                 $val $namedArgs[$name];
  5754.                 unset($restNamed[$name]);
  5755.             } else {
  5756.                 continue;
  5757.             }
  5758.             $output[$name] = $val;
  5759.         }
  5760.         if ($prototype['rest_argument'] !== null) {
  5761.             $name $prototype['rest_argument'];
  5762.             $rest array_values(array_slice($positionalArgs, \count($prototype['arguments'])));
  5763.             $val = [Type::T_LIST, \is_null($splatSeparator) ? ',' $splatSeparator $rest$restNamed];
  5764.             $output[$name] = $val;
  5765.         }
  5766.         return $output;
  5767.     }
  5768.     /**
  5769.      * Coerce a php value into a scss one
  5770.      *
  5771.      * @param mixed $value
  5772.      *
  5773.      * @return array|Number
  5774.      */
  5775.     protected function coerceValue($value)
  5776.     {
  5777.         if (\is_array($value) || $value instanceof Number) {
  5778.             return $value;
  5779.         }
  5780.         if (\is_bool($value)) {
  5781.             return $this->toBool($value);
  5782.         }
  5783.         if (\is_null($value)) {
  5784.             return static::$null;
  5785.         }
  5786.         if (is_numeric($value)) {
  5787.             return new Number($value'');
  5788.         }
  5789.         if ($value === '') {
  5790.             return static::$emptyString;
  5791.         }
  5792.         $value = [Type::T_KEYWORD$value];
  5793.         $color $this->coerceColor($value);
  5794.         if ($color) {
  5795.             return $color;
  5796.         }
  5797.         return $value;
  5798.     }
  5799.     /**
  5800.      * Coerce something to map
  5801.      *
  5802.      * @param array|Number $item
  5803.      *
  5804.      * @return array|Number
  5805.      */
  5806.     protected function coerceMap($item)
  5807.     {
  5808.         if ($item[0] === Type::T_MAP) {
  5809.             return $item;
  5810.         }
  5811.         if (
  5812.             $item[0] === Type::T_LIST &&
  5813.             $item[2] === []
  5814.         ) {
  5815.             return static::$emptyMap;
  5816.         }
  5817.         return $item;
  5818.     }
  5819.     /**
  5820.      * Coerce something to list
  5821.      *
  5822.      * @param array|Number $item
  5823.      * @param string       $delim
  5824.      * @param boolean      $removeTrailingNull
  5825.      *
  5826.      * @return array
  5827.      */
  5828.     protected function coerceList($item$delim ','$removeTrailingNull false)
  5829.     {
  5830.         if ($item instanceof Number) {
  5831.             return [Type::T_LIST$delim, [$item]];
  5832.         }
  5833.         if ($item[0] === Type::T_LIST) {
  5834.             // remove trailing null from the list
  5835.             if ($removeTrailingNull && end($item[2]) === static::$null) {
  5836.                 array_pop($item[2]);
  5837.             }
  5838.             return $item;
  5839.         }
  5840.         if ($item[0] === Type::T_MAP) {
  5841.             $keys $item[1];
  5842.             $values $item[2];
  5843.             $list = [];
  5844.             for ($i 0$s = \count($keys); $i $s$i++) {
  5845.                 $key $keys[$i];
  5846.                 $value $values[$i];
  5847.                 switch ($key[0]) {
  5848.                     case Type::T_LIST:
  5849.                     case Type::T_MAP:
  5850.                     case Type::T_STRING:
  5851.                     case Type::T_NULL:
  5852.                         break;
  5853.                     default:
  5854.                         $key = [Type::T_KEYWORD$this->compileStringContent($this->coerceString($key))];
  5855.                         break;
  5856.                 }
  5857.                 $list[] = [
  5858.                     Type::T_LIST,
  5859.                     '',
  5860.                     [$key$value]
  5861.                 ];
  5862.             }
  5863.             return [Type::T_LIST','$list];
  5864.         }
  5865.         return [Type::T_LIST$delim, [$item]];
  5866.     }
  5867.     /**
  5868.      * Coerce color for expression
  5869.      *
  5870.      * @param array|Number $value
  5871.      *
  5872.      * @return array|Number
  5873.      */
  5874.     protected function coerceForExpression($value)
  5875.     {
  5876.         if ($color $this->coerceColor($value)) {
  5877.             return $color;
  5878.         }
  5879.         return $value;
  5880.     }
  5881.     /**
  5882.      * Coerce value to color
  5883.      *
  5884.      * @param array|Number $value
  5885.      * @param bool         $inRGBFunction
  5886.      *
  5887.      * @return array|null
  5888.      */
  5889.     protected function coerceColor($value$inRGBFunction false)
  5890.     {
  5891.         if ($value instanceof Number) {
  5892.             return null;
  5893.         }
  5894.         switch ($value[0]) {
  5895.             case Type::T_COLOR:
  5896.                 for ($i 1$i <= 3$i++) {
  5897.                     if (! is_numeric($value[$i])) {
  5898.                         $cv $this->compileRGBAValue($value[$i]);
  5899.                         if (! is_numeric($cv)) {
  5900.                             return null;
  5901.                         }
  5902.                         $value[$i] = $cv;
  5903.                     }
  5904.                     if (isset($value[4])) {
  5905.                         if (! is_numeric($value[4])) {
  5906.                             $cv $this->compileRGBAValue($value[4], true);
  5907.                             if (! is_numeric($cv)) {
  5908.                                 return null;
  5909.                             }
  5910.                             $value[4] = $cv;
  5911.                         }
  5912.                     }
  5913.                 }
  5914.                 return $value;
  5915.             case Type::T_LIST:
  5916.                 if ($inRGBFunction) {
  5917.                     if (\count($value[2]) == || \count($value[2]) == 4) {
  5918.                         $color $value[2];
  5919.                         array_unshift($colorType::T_COLOR);
  5920.                         return $this->coerceColor($color);
  5921.                     }
  5922.                 }
  5923.                 return null;
  5924.             case Type::T_KEYWORD:
  5925.                 if (! \is_string($value[1])) {
  5926.                     return null;
  5927.                 }
  5928.                 $name strtolower($value[1]);
  5929.                 // hexa color?
  5930.                 if (preg_match('/^#([0-9a-f]+)$/i'$name$m)) {
  5931.                     $nofValues = \strlen($m[1]);
  5932.                     if (\in_array($nofValues, [3468])) {
  5933.                         $nbChannels 3;
  5934.                         $color      = [];
  5935.                         $num        hexdec($m[1]);
  5936.                         switch ($nofValues) {
  5937.                             case 4:
  5938.                                 $nbChannels 4;
  5939.                                 // then continuing with the case 3:
  5940.                             case 3:
  5941.                                 for ($i 0$i $nbChannels$i++) {
  5942.                                     $t $num 0xf;
  5943.                                     array_unshift($color$t << $t);
  5944.                                     $num >>= 4;
  5945.                                 }
  5946.                                 break;
  5947.                             case 8:
  5948.                                 $nbChannels 4;
  5949.                                 // then continuing with the case 6:
  5950.                             case 6:
  5951.                                 for ($i 0$i $nbChannels$i++) {
  5952.                                     array_unshift($color$num 0xff);
  5953.                                     $num >>= 8;
  5954.                                 }
  5955.                                 break;
  5956.                         }
  5957.                         if ($nbChannels === 4) {
  5958.                             if ($color[3] === 255) {
  5959.                                 $color[3] = 1// fully opaque
  5960.                             } else {
  5961.                                 $color[3] = round($color[3] / 255Number::PRECISION);
  5962.                             }
  5963.                         }
  5964.                         array_unshift($colorType::T_COLOR);
  5965.                         return $color;
  5966.                     }
  5967.                 }
  5968.                 if ($rgba Colors::colorNameToRGBa($name)) {
  5969.                     return isset($rgba[3])
  5970.                         ? [Type::T_COLOR$rgba[0], $rgba[1], $rgba[2], $rgba[3]]
  5971.                         : [Type::T_COLOR$rgba[0], $rgba[1], $rgba[2]];
  5972.                 }
  5973.                 return null;
  5974.         }
  5975.         return null;
  5976.     }
  5977.     /**
  5978.      * @param integer|Number $value
  5979.      * @param boolean        $isAlpha
  5980.      *
  5981.      * @return integer|mixed
  5982.      */
  5983.     protected function compileRGBAValue($value$isAlpha false)
  5984.     {
  5985.         if ($isAlpha) {
  5986.             return $this->compileColorPartValue($value01false);
  5987.         }
  5988.         return $this->compileColorPartValue($value0255true);
  5989.     }
  5990.     /**
  5991.      * @param mixed         $value
  5992.      * @param integer|float $min
  5993.      * @param integer|float $max
  5994.      * @param boolean       $isInt
  5995.      *
  5996.      * @return integer|mixed
  5997.      */
  5998.     protected function compileColorPartValue($value$min$max$isInt true)
  5999.     {
  6000.         if (! is_numeric($value)) {
  6001.             if (\is_array($value)) {
  6002.                 $reduced $this->reduce($value);
  6003.                 if ($reduced instanceof Number) {
  6004.                     $value $reduced;
  6005.                 }
  6006.             }
  6007.             if ($value instanceof Number) {
  6008.                 if ($value->unitless()) {
  6009.                     $num $value->getDimension();
  6010.                 } elseif ($value->hasUnit('%')) {
  6011.                     $num $max $value->getDimension() / 100;
  6012.                 } else {
  6013.                     throw $this->error('Expected %s to have no units or "%%".'$value);
  6014.                 }
  6015.                 $value $num;
  6016.             } elseif (\is_array($value)) {
  6017.                 $value $this->compileValue($value);
  6018.             }
  6019.         }
  6020.         if (is_numeric($value)) {
  6021.             if ($isInt) {
  6022.                 $value round($value);
  6023.             }
  6024.             $value min($maxmax($min$value));
  6025.             return $value;
  6026.         }
  6027.         return $value;
  6028.     }
  6029.     /**
  6030.      * Coerce value to string
  6031.      *
  6032.      * @param array|Number $value
  6033.      *
  6034.      * @return array
  6035.      */
  6036.     protected function coerceString($value)
  6037.     {
  6038.         if ($value[0] === Type::T_STRING) {
  6039.             return $value;
  6040.         }
  6041.         return [Type::T_STRING'', [$this->compileValue($value)]];
  6042.     }
  6043.     /**
  6044.      * Assert value is a string
  6045.      *
  6046.      * This method deals with internal implementation details of the value
  6047.      * representation where unquoted strings can sometimes be stored under
  6048.      * other types.
  6049.      * The returned value is always using the T_STRING type.
  6050.      *
  6051.      * @api
  6052.      *
  6053.      * @param array|Number $value
  6054.      * @param string|null  $varName
  6055.      *
  6056.      * @return array
  6057.      *
  6058.      * @throws SassScriptException
  6059.      */
  6060.     public function assertString($value$varName null)
  6061.     {
  6062.         // case of url(...) parsed a a function
  6063.         if ($value[0] === Type::T_FUNCTION) {
  6064.             $value $this->coerceString($value);
  6065.         }
  6066.         if (! \in_array($value[0], [Type::T_STRINGType::T_KEYWORD])) {
  6067.             $value $this->compileValue($value);
  6068.             throw SassScriptException::forArgument("$value is not a string."$varName);
  6069.         }
  6070.         return $this->coerceString($value);
  6071.     }
  6072.     /**
  6073.      * Coerce value to a percentage
  6074.      *
  6075.      * @param array|Number $value
  6076.      *
  6077.      * @return integer|float
  6078.      *
  6079.      * @deprecated
  6080.      */
  6081.     protected function coercePercent($value)
  6082.     {
  6083.         @trigger_error(sprintf('"%s" is deprecated since 1.7.0.'__METHOD__), E_USER_DEPRECATED);
  6084.         if ($value instanceof Number) {
  6085.             if ($value->hasUnit('%')) {
  6086.                 return $value->getDimension() / 100;
  6087.             }
  6088.             return $value->getDimension();
  6089.         }
  6090.         return 0;
  6091.     }
  6092.     /**
  6093.      * Assert value is a map
  6094.      *
  6095.      * @api
  6096.      *
  6097.      * @param array|Number $value
  6098.      * @param string|null  $varName
  6099.      *
  6100.      * @return array
  6101.      *
  6102.      * @throws SassScriptException
  6103.      */
  6104.     public function assertMap($value$varName null)
  6105.     {
  6106.         $value $this->coerceMap($value);
  6107.         if ($value[0] !== Type::T_MAP) {
  6108.             $value $this->compileValue($value);
  6109.             throw SassScriptException::forArgument("$value is not a map."$varName);
  6110.         }
  6111.         return $value;
  6112.     }
  6113.     /**
  6114.      * Assert value is a list
  6115.      *
  6116.      * @api
  6117.      *
  6118.      * @param array|Number $value
  6119.      *
  6120.      * @return array
  6121.      *
  6122.      * @throws \Exception
  6123.      */
  6124.     public function assertList($value)
  6125.     {
  6126.         if ($value[0] !== Type::T_LIST) {
  6127.             throw $this->error('expecting list, %s received'$value[0]);
  6128.         }
  6129.         return $value;
  6130.     }
  6131.     /**
  6132.      * Gets the keywords of an argument list.
  6133.      *
  6134.      * Keys in the returned array are normalized names (underscores are replaced with dashes)
  6135.      * without the leading `$`.
  6136.      * Calling this helper with anything that an argument list received for a rest argument
  6137.      * of the function argument declaration is not supported.
  6138.      *
  6139.      * @param array|Number $value
  6140.      *
  6141.      * @return array<string, array|Number>
  6142.      */
  6143.     public function getArgumentListKeywords($value)
  6144.     {
  6145.         if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
  6146.             throw new \InvalidArgumentException('The argument is not a sass argument list.');
  6147.         }
  6148.         return $value[3];
  6149.     }
  6150.     /**
  6151.      * Assert value is a color
  6152.      *
  6153.      * @api
  6154.      *
  6155.      * @param array|Number $value
  6156.      * @param string|null  $varName
  6157.      *
  6158.      * @return array
  6159.      *
  6160.      * @throws SassScriptException
  6161.      */
  6162.     public function assertColor($value$varName null)
  6163.     {
  6164.         if ($color $this->coerceColor($value)) {
  6165.             return $color;
  6166.         }
  6167.         $value $this->compileValue($value);
  6168.         throw SassScriptException::forArgument("$value is not a color."$varName);
  6169.     }
  6170.     /**
  6171.      * Assert value is a number
  6172.      *
  6173.      * @api
  6174.      *
  6175.      * @param array|Number $value
  6176.      * @param string|null  $varName
  6177.      *
  6178.      * @return Number
  6179.      *
  6180.      * @throws SassScriptException
  6181.      */
  6182.     public function assertNumber($value$varName null)
  6183.     {
  6184.         if (!$value instanceof Number) {
  6185.             $value $this->compileValue($value);
  6186.             throw SassScriptException::forArgument("$value is not a number."$varName);
  6187.         }
  6188.         return $value;
  6189.     }
  6190.     /**
  6191.      * Assert value is a integer
  6192.      *
  6193.      * @api
  6194.      *
  6195.      * @param array|Number $value
  6196.      * @param string|null  $varName
  6197.      *
  6198.      * @return integer
  6199.      *
  6200.      * @throws SassScriptException
  6201.      */
  6202.     public function assertInteger($value$varName null)
  6203.     {
  6204.         $value $this->assertNumber($value$varName)->getDimension();
  6205.         if (round($value - \intval($value), Number::PRECISION) > 0) {
  6206.             throw SassScriptException::forArgument("$value is not an integer."$varName);
  6207.         }
  6208.         return intval($value);
  6209.     }
  6210.     /**
  6211.      * Extract the  ... / alpha on the last argument of channel arg
  6212.      * in color functions
  6213.      *
  6214.      * @param array $args
  6215.      * @return array
  6216.      */
  6217.     private function extractSlashAlphaInColorFunction($args)
  6218.     {
  6219.         $last end($args);
  6220.         if (\count($args) === && $last[0] === Type::T_EXPRESSION && $last[1] === '/') {
  6221.             array_pop($args);
  6222.             $args[] = $last[2];
  6223.             $args[] = $last[3];
  6224.         }
  6225.         return $args;
  6226.     }
  6227.     /**
  6228.      * Make sure a color's components don't go out of bounds
  6229.      *
  6230.      * @param array $c
  6231.      *
  6232.      * @return array
  6233.      */
  6234.     protected function fixColor($c)
  6235.     {
  6236.         foreach ([123] as $i) {
  6237.             if ($c[$i] < 0) {
  6238.                 $c[$i] = 0;
  6239.             }
  6240.             if ($c[$i] > 255) {
  6241.                 $c[$i] = 255;
  6242.             }
  6243.             if (!\is_int($c[$i])) {
  6244.                 $c[$i] = round($c[$i]);
  6245.             }
  6246.         }
  6247.         return $c;
  6248.     }
  6249.     /**
  6250.      * Convert RGB to HSL
  6251.      *
  6252.      * @internal
  6253.      *
  6254.      * @param integer $red
  6255.      * @param integer $green
  6256.      * @param integer $blue
  6257.      *
  6258.      * @return array
  6259.      */
  6260.     public function toHSL($red$green$blue)
  6261.     {
  6262.         $min min($red$green$blue);
  6263.         $max max($red$green$blue);
  6264.         $l $min $max;
  6265.         $d $max $min;
  6266.         if ((int) $d === 0) {
  6267.             $h $s 0;
  6268.         } else {
  6269.             if ($l 255) {
  6270.                 $s $d $l;
  6271.             } else {
  6272.                 $s $d / (510 $l);
  6273.             }
  6274.             if ($red == $max) {
  6275.                 $h 60 * ($green $blue) / $d;
  6276.             } elseif ($green == $max) {
  6277.                 $h 60 * ($blue $red) / $d 120;
  6278.             } elseif ($blue == $max) {
  6279.                 $h 60 * ($red $green) / $d 240;
  6280.             }
  6281.         }
  6282.         return [Type::T_HSLfmod($h 360360), $s 100$l 5.1];
  6283.     }
  6284.     /**
  6285.      * Hue to RGB helper
  6286.      *
  6287.      * @param float $m1
  6288.      * @param float $m2
  6289.      * @param float $h
  6290.      *
  6291.      * @return float
  6292.      */
  6293.     protected function hueToRGB($m1$m2$h)
  6294.     {
  6295.         if ($h 0) {
  6296.             $h += 1;
  6297.         } elseif ($h 1) {
  6298.             $h -= 1;
  6299.         }
  6300.         if ($h 1) {
  6301.             return $m1 + ($m2 $m1) * $h 6;
  6302.         }
  6303.         if ($h 1) {
  6304.             return $m2;
  6305.         }
  6306.         if ($h 2) {
  6307.             return $m1 + ($m2 $m1) * ($h) * 6;
  6308.         }
  6309.         return $m1;
  6310.     }
  6311.     /**
  6312.      * Convert HSL to RGB
  6313.      *
  6314.      * @internal
  6315.      *
  6316.      * @param int|float $hue        H from 0 to 360
  6317.      * @param int|float $saturation S from 0 to 100
  6318.      * @param int|float $lightness  L from 0 to 100
  6319.      *
  6320.      * @return array
  6321.      */
  6322.     public function toRGB($hue$saturation$lightness)
  6323.     {
  6324.         if ($hue 0) {
  6325.             $hue += 360;
  6326.         }
  6327.         $h $hue 360;
  6328.         $s min(100max(0$saturation)) / 100;
  6329.         $l min(100max(0$lightness)) / 100;
  6330.         $m2 $l <= 0.5 $l * ($s 1) : $l $s $l $s;
  6331.         $m1 $l $m2;
  6332.         $r $this->hueToRGB($m1$m2$h 3) * 255;
  6333.         $g $this->hueToRGB($m1$m2$h) * 255;
  6334.         $b $this->hueToRGB($m1$m2$h 3) * 255;
  6335.         $out = [Type::T_COLOR$r$g$b];
  6336.         return $out;
  6337.     }
  6338.     /**
  6339.      * Convert HWB to RGB
  6340.      * https://www.w3.org/TR/css-color-4/#hwb-to-rgb
  6341.      *
  6342.      * @api
  6343.      *
  6344.      * @param integer $hue        H from 0 to 360
  6345.      * @param integer $whiteness  W from 0 to 100
  6346.      * @param integer $blackness  B from 0 to 100
  6347.      *
  6348.      * @return array
  6349.      */
  6350.     private function HWBtoRGB($hue$whiteness$blackness)
  6351.     {
  6352.         $w min(100max(0$whiteness)) / 100;
  6353.         $b min(100max(0$blackness)) / 100;
  6354.         $sum $w $b;
  6355.         if ($sum 1.0) {
  6356.             $w $w $sum;
  6357.             $b $b $sum;
  6358.         }
  6359.         $b min(1.0 $w$b);
  6360.         $rgb $this->toRGB($hue10050);
  6361.         for($i 1$i 4$i++) {
  6362.           $rgb[$i] *= (1.0 $w $b);
  6363.           $rgb[$i] = round($rgb[$i] + 255 $w 0.0001);
  6364.         }
  6365.         return $rgb;
  6366.     }
  6367.     /**
  6368.      * Convert RGB to HWB
  6369.      *
  6370.      * @api
  6371.      *
  6372.      * @param integer $red
  6373.      * @param integer $green
  6374.      * @param integer $blue
  6375.      *
  6376.      * @return array
  6377.      */
  6378.     private function RGBtoHWB($red$green$blue)
  6379.     {
  6380.         $min min($red$green$blue);
  6381.         $max max($red$green$blue);
  6382.         $d $max $min;
  6383.         if ((int) $d === 0) {
  6384.             $h 0;
  6385.         } else {
  6386.             if ($red == $max) {
  6387.                 $h 60 * ($green $blue) / $d;
  6388.             } elseif ($green == $max) {
  6389.                 $h 60 * ($blue $red) / $d 120;
  6390.             } elseif ($blue == $max) {
  6391.                 $h 60 * ($red $green) / $d 240;
  6392.             }
  6393.         }
  6394.         return [Type::T_HWBfmod($h360), $min 255 100100 $max 255 *100];
  6395.     }
  6396.     // Built in functions
  6397.     protected static $libCall = ['function''args...'];
  6398.     protected function libCall($args)
  6399.     {
  6400.         $functionReference $args[0];
  6401.         if (in_array($functionReference[0], [Type::T_STRINGType::T_KEYWORD])) {
  6402.             $name $this->compileStringContent($this->coerceString($functionReference));
  6403.             $warning "Passing a string to call() is deprecated and will be illegal\n"
  6404.                 "in Sass 4.0. Use call(function-reference($name)) instead.";
  6405.             Warn::deprecation($warning);
  6406.             $functionReference $this->libGetFunction([$this->assertString($functionReference'function')]);
  6407.         }
  6408.         if ($functionReference === static::$null) {
  6409.             return static::$null;
  6410.         }
  6411.         if (! in_array($functionReference[0], [Type::T_FUNCTION_REFERENCEType::T_FUNCTION])) {
  6412.             throw $this->error('Function reference expected, got ' $functionReference[0]);
  6413.         }
  6414.         $callArgs = [
  6415.             [null$args[1], true]
  6416.         ];
  6417.         return $this->reduce([Type::T_FUNCTION_CALL$functionReference$callArgs]);
  6418.     }
  6419.     protected static $libGetFunction = [
  6420.         ['name'],
  6421.         ['name''css']
  6422.     ];
  6423.     protected function libGetFunction($args)
  6424.     {
  6425.         $name $this->compileStringContent($this->assertString(array_shift($args), 'name'));
  6426.         $isCss false;
  6427.         if (count($args)) {
  6428.             $isCss array_shift($args);
  6429.             $isCss = (($isCss === static::$true) ? true false);
  6430.         }
  6431.         if ($isCss) {
  6432.             return [Type::T_FUNCTION$name, [Type::T_LIST',', []]];
  6433.         }
  6434.         return $this->getFunctionReference($nametrue);
  6435.     }
  6436.     protected static $libIf = ['condition''if-true''if-false:'];
  6437.     protected function libIf($args)
  6438.     {
  6439.         list($cond$t$f) = $args;
  6440.         if (! $this->isTruthy($this->reduce($condtrue))) {
  6441.             return $this->reduce($ftrue);
  6442.         }
  6443.         return $this->reduce($ttrue);
  6444.     }
  6445.     protected static $libIndex = ['list''value'];
  6446.     protected function libIndex($args)
  6447.     {
  6448.         list($list$value) = $args;
  6449.         if (
  6450.             $list[0] === Type::T_MAP ||
  6451.             $list[0] === Type::T_STRING ||
  6452.             $list[0] === Type::T_KEYWORD ||
  6453.             $list[0] === Type::T_INTERPOLATE
  6454.         ) {
  6455.             $list $this->coerceList($list' ');
  6456.         }
  6457.         if ($list[0] !== Type::T_LIST) {
  6458.             return static::$null;
  6459.         }
  6460.         // Numbers are represented with value objects, for which the PHP equality operator does not
  6461.         // match the Sass rules (and we cannot overload it). As they are the only type of values
  6462.         // represented with a value object for now, they require a special case.
  6463.         if ($value instanceof Number) {
  6464.             $key 0;
  6465.             foreach ($list[2] as $item) {
  6466.                 $key++;
  6467.                 $itemValue $this->normalizeValue($item);
  6468.                 if ($itemValue instanceof Number && $value->equals($itemValue)) {
  6469.                     return new Number($key'');
  6470.                 }
  6471.             }
  6472.             return static::$null;
  6473.         }
  6474.         $values = [];
  6475.         foreach ($list[2] as $item) {
  6476.             $values[] = $this->normalizeValue($item);
  6477.         }
  6478.         $key array_search($this->normalizeValue($value), $values);
  6479.         return false === $key ? static::$null : new Number($key 1'');
  6480.     }
  6481.     protected static $libRgb = [
  6482.         ['color'],
  6483.         ['color''alpha'],
  6484.         ['channels'],
  6485.         ['red''green''blue'],
  6486.         ['red''green''blue''alpha'] ];
  6487.     protected function libRgb($args$kwargs$funcName 'rgb')
  6488.     {
  6489.         switch (\count($args)) {
  6490.             case 1:
  6491.                 if (! $color $this->coerceColor($args[0], true)) {
  6492.                     $color = [Type::T_STRING'', [$funcName '('$args[0], ')']];
  6493.                 }
  6494.                 break;
  6495.             case 3:
  6496.                 $color = [Type::T_COLOR$args[0], $args[1], $args[2]];
  6497.                 if (! $color $this->coerceColor($color)) {
  6498.                     $color = [Type::T_STRING'', [$funcName '('$args[0], ', '$args[1], ', '$args[2], ')']];
  6499.                 }
  6500.                 return $color;
  6501.             case 2:
  6502.                 if ($color $this->coerceColor($args[0], true)) {
  6503.                     $alpha $this->compileRGBAValue($args[1], true);
  6504.                     if (is_numeric($alpha)) {
  6505.                         $color[4] = $alpha;
  6506.                     } else {
  6507.                         $color = [Type::T_STRING'',
  6508.                             [$funcName '('$color[1], ', '$color[2], ', '$color[3], ', '$alpha')']];
  6509.                     }
  6510.                 } else {
  6511.                     $color = [Type::T_STRING'', [$funcName '('$args[0], ', '$args[1], ')']];
  6512.                 }
  6513.                 break;
  6514.             case 4:
  6515.             default:
  6516.                 $color = [Type::T_COLOR$args[0], $args[1], $args[2], $args[3]];
  6517.                 if (! $color $this->coerceColor($color)) {
  6518.                     $color = [Type::T_STRING'',
  6519.                         [$funcName '('$args[0], ', '$args[1], ', '$args[2], ', '$args[3], ')']];
  6520.                 }
  6521.                 break;
  6522.         }
  6523.         return $color;
  6524.     }
  6525.     protected static $libRgba = [
  6526.         ['color'],
  6527.         ['color''alpha'],
  6528.         ['channels'],
  6529.         ['red''green''blue'],
  6530.         ['red''green''blue''alpha'] ];
  6531.     protected function libRgba($args$kwargs)
  6532.     {
  6533.         return $this->libRgb($args$kwargs'rgba');
  6534.     }
  6535.     /**
  6536.      * Helper function for adjust_color, change_color, and scale_color
  6537.      *
  6538.      * @param array<array|Number> $args
  6539.      * @param string $operation
  6540.      * @param callable $fn
  6541.      *
  6542.      * @return array
  6543.      *
  6544.      * @phpstan-param callable(float|int, float|int|null, float|int): (float|int) $fn
  6545.      */
  6546.     protected function alterColor(array $args$operation$fn)
  6547.     {
  6548.         $color $this->assertColor($args[0], 'color');
  6549.         if ($args[1][2]) {
  6550.             throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.');
  6551.         }
  6552.         $kwargs $this->getArgumentListKeywords($args[1]);
  6553.         $scale $operation === 'scale';
  6554.         $change $operation === 'change';
  6555.         /**
  6556.          * @param string $name
  6557.          * @param float|int $max
  6558.          * @param bool $checkPercent
  6559.          * @param bool $assertPercent
  6560.          *
  6561.          * @return float|int|null
  6562.          */
  6563.         $getParam = function ($name$max$checkPercent false$assertPercent false) use (&$kwargs$scale$change) {
  6564.             if (!isset($kwargs[$name])) {
  6565.                 return null;
  6566.             }
  6567.             $number $this->assertNumber($kwargs[$name], $name);
  6568.             unset($kwargs[$name]);
  6569.             if (!$scale && $checkPercent) {
  6570.                 if (!$number->hasUnit('%')) {
  6571.                     $warning $this->error("{$name} Passing a number `$number` without unit % is deprecated.");
  6572.                     $this->logger->warn($warning->getMessage(), true);
  6573.                 }
  6574.             }
  6575.             if ($scale || $assertPercent) {
  6576.                 $number->assertUnit('%'$name);
  6577.             }
  6578.             if ($scale) {
  6579.                 $max 100;
  6580.             }
  6581.             return $number->valueInRange($change : -$max$max$name);
  6582.         };
  6583.         $alpha $getParam('alpha'1);
  6584.         $red $getParam('red'255);
  6585.         $green $getParam('green'255);
  6586.         $blue $getParam('blue'255);
  6587.         if ($scale || !isset($kwargs['hue'])) {
  6588.             $hue null;
  6589.         } else {
  6590.             $hueNumber $this->assertNumber($kwargs['hue'], 'hue');
  6591.             unset($kwargs['hue']);
  6592.             $hue $hueNumber->getDimension();
  6593.         }
  6594.         $saturation $getParam('saturation'100true);
  6595.         $lightness $getParam('lightness'100true);
  6596.         $whiteness $getParam('whiteness'100falsetrue);
  6597.         $blackness $getParam('blackness'100falsetrue);
  6598.         if (!empty($kwargs)) {
  6599.             $unknownNames array_keys($kwargs);
  6600.             $lastName array_pop($unknownNames);
  6601.             $message sprintf(
  6602.                 'No argument%s named $%s%s.',
  6603.                 $unknownNames 's' '',
  6604.                 $unknownNames implode(', $'$unknownNames) . ' or $' '',
  6605.                 $lastName
  6606.             );
  6607.             throw new SassScriptException($message);
  6608.         }
  6609.         $hasRgb $red !== null || $green !== null || $blue !== null;
  6610.         $hasSL $saturation !== null || $lightness !== null;
  6611.         $hasWB $whiteness !== null || $blackness !== null;
  6612.         $found false;
  6613.         if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) {
  6614.             throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.'$hasWB 'HWB' 'HSL'));
  6615.         }
  6616.         if ($hasWB && $hasSL) {
  6617.             throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.');
  6618.         }
  6619.         if ($hasRgb) {
  6620.             $color[1] = round($fn($color[1], $red255));
  6621.             $color[2] = round($fn($color[2], $green255));
  6622.             $color[3] = round($fn($color[3], $blue255));
  6623.         } elseif ($hasWB) {
  6624.             $hwb $this->RGBtoHWB($color[1], $color[2], $color[3]);
  6625.             if ($hue !== null) {
  6626.                 $hwb[1] = $change $hue $hwb[1] + $hue;
  6627.             }
  6628.             $hwb[2] = $fn($hwb[2], $whiteness100);
  6629.             $hwb[3] = $fn($hwb[3], $blackness100);
  6630.             $rgb $this->HWBtoRGB($hwb[1], $hwb[2], $hwb[3]);
  6631.             if (isset($color[4])) {
  6632.                 $rgb[4] = $color[4];
  6633.             }
  6634.             $color $rgb;
  6635.         } elseif ($hue !== null || $hasSL) {
  6636.             $hsl $this->toHSL($color[1], $color[2], $color[3]);
  6637.             if ($hue !== null) {
  6638.                 $hsl[1] = $change $hue $hsl[1] + $hue;
  6639.             }
  6640.             $hsl[2] = $fn($hsl[2], $saturation100);
  6641.             $hsl[3] = $fn($hsl[3], $lightness100);
  6642.             $rgb $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
  6643.             if (isset($color[4])) {
  6644.                 $rgb[4] = $color[4];
  6645.             }
  6646.             $color $rgb;
  6647.         }
  6648.         if ($alpha !== null) {
  6649.             $existingAlpha = isset($color[4]) ? $color[4] : 1;
  6650.             $color[4] = $fn($existingAlpha$alpha1);
  6651.         }
  6652.         return $color;
  6653.     }
  6654.     protected static $libAdjustColor = ['color''kwargs...'];
  6655.     protected function libAdjustColor($args)
  6656.     {
  6657.         return $this->alterColor($args'adjust', function ($base$alter$max) {
  6658.             if ($alter === null) {
  6659.                 return $base;
  6660.             }
  6661.             $new $base $alter;
  6662.             if ($new 0) {
  6663.                 return 0;
  6664.             }
  6665.             if ($new $max) {
  6666.                 return $max;
  6667.             }
  6668.             return $new;
  6669.         });
  6670.     }
  6671.     protected static $libChangeColor = ['color''kwargs...'];
  6672.     protected function libChangeColor($args)
  6673.     {
  6674.         return $this->alterColor($args,'change', function ($base$alter$max) {
  6675.             if ($alter === null) {
  6676.                 return $base;
  6677.             }
  6678.             return $alter;
  6679.         });
  6680.     }
  6681.     protected static $libScaleColor = ['color''kwargs...'];
  6682.     protected function libScaleColor($args)
  6683.     {
  6684.         return $this->alterColor($args'scale', function ($base$scale$max) {
  6685.             if ($scale === null) {
  6686.                 return $base;
  6687.             }
  6688.             $scale $scale 100;
  6689.             if ($scale 0) {
  6690.                 return $base $scale $base;
  6691.             }
  6692.             return ($max $base) * $scale $base;
  6693.         });
  6694.     }
  6695.     protected static $libIeHexStr = ['color'];
  6696.     protected function libIeHexStr($args)
  6697.     {
  6698.         $color $this->coerceColor($args[0]);
  6699.         if (\is_null($color)) {
  6700.             throw $this->error('Error: argument `$color` of `ie-hex-str($color)` must be a color');
  6701.         }
  6702.         $color[4] = isset($color[4]) ? round(255 $color[4]) : 255;
  6703.         return [Type::T_STRING'', [sprintf('#%02X%02X%02X%02X'$color[4], $color[1], $color[2], $color[3])]];
  6704.     }
  6705.     protected static $libRed = ['color'];
  6706.     protected function libRed($args)
  6707.     {
  6708.         $color $this->coerceColor($args[0]);
  6709.         if (\is_null($color)) {
  6710.             throw $this->error('Error: argument `$color` of `red($color)` must be a color');
  6711.         }
  6712.         return new Number((int) $color[1], '');
  6713.     }
  6714.     protected static $libGreen = ['color'];
  6715.     protected function libGreen($args)
  6716.     {
  6717.         $color $this->coerceColor($args[0]);
  6718.         if (\is_null($color)) {
  6719.             throw $this->error('Error: argument `$color` of `green($color)` must be a color');
  6720.         }
  6721.         return new Number((int) $color[2], '');
  6722.     }
  6723.     protected static $libBlue = ['color'];
  6724.     protected function libBlue($args)
  6725.     {
  6726.         $color $this->coerceColor($args[0]);
  6727.         if (\is_null($color)) {
  6728.             throw $this->error('Error: argument `$color` of `blue($color)` must be a color');
  6729.         }
  6730.         return new Number((int) $color[3], '');
  6731.     }
  6732.     protected static $libAlpha = ['color'];
  6733.     protected function libAlpha($args)
  6734.     {
  6735.         if ($color $this->coerceColor($args[0])) {
  6736.             return new Number(isset($color[4]) ? $color[4] : 1'');
  6737.         }
  6738.         // this might be the IE function, so return value unchanged
  6739.         return null;
  6740.     }
  6741.     protected static $libOpacity = ['color'];
  6742.     protected function libOpacity($args)
  6743.     {
  6744.         $value $args[0];
  6745.         if ($value instanceof Number) {
  6746.             return null;
  6747.         }
  6748.         return $this->libAlpha($args);
  6749.     }
  6750.     // mix two colors
  6751.     protected static $libMix = [
  6752.         ['color1''color2''weight:50%'],
  6753.         ['color-1''color-2''weight:50%']
  6754.         ];
  6755.     protected function libMix($args)
  6756.     {
  6757.         list($first$second$weight) = $args;
  6758.         $first $this->assertColor($first'color1');
  6759.         $second $this->assertColor($second'color2');
  6760.         $weightScale $this->assertNumber($weight'weight')->valueInRange(0100'weight') / 100;
  6761.         $firstAlpha = isset($first[4]) ? $first[4] : 1;
  6762.         $secondAlpha = isset($second[4]) ? $second[4] : 1;
  6763.         $normalizedWeight $weightScale 1;
  6764.         $alphaDistance $firstAlpha $secondAlpha;
  6765.         $combinedWeight $normalizedWeight $alphaDistance == -$normalizedWeight : ($normalizedWeight $alphaDistance) / ($normalizedWeight $alphaDistance);
  6766.         $weight1 = ($combinedWeight 1) / 2.0;
  6767.         $weight2 1.0 $weight1;
  6768.         $new = [Type::T_COLOR,
  6769.             $weight1 $first[1] + $weight2 $second[1],
  6770.             $weight1 $first[2] + $weight2 $second[2],
  6771.             $weight1 $first[3] + $weight2 $second[3],
  6772.         ];
  6773.         if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
  6774.             $new[] = $firstAlpha $weightScale $secondAlpha * ($weightScale);
  6775.         }
  6776.         return $this->fixColor($new);
  6777.     }
  6778.     protected static $libHsl = [
  6779.         ['channels'],
  6780.         ['hue''saturation'],
  6781.         ['hue''saturation''lightness'],
  6782.         ['hue''saturation''lightness''alpha'] ];
  6783.     protected function libHsl($args$kwargs$funcName 'hsl')
  6784.     {
  6785.         $args_to_check $args;
  6786.         if (\count($args) == 1) {
  6787.             if ($args[0][0] !== Type::T_LIST || \count($args[0][2]) < || \count($args[0][2]) > 4) {
  6788.                 return [Type::T_STRING'', [$funcName '('$args[0], ')']];
  6789.             }
  6790.             $args $args[0][2];
  6791.             $args_to_check $kwargs['channels'][2];
  6792.         }
  6793.         if (\count($args) === 2) {
  6794.             // if var() is used as an argument, return as a css function
  6795.             foreach ($args as $arg) {
  6796.                 if ($arg[0] === Type::T_FUNCTION && in_array($arg[1], ['var'])) {
  6797.                     return null;
  6798.                 }
  6799.             }
  6800.             throw new SassScriptException('Missing argument $lightness.');
  6801.         }
  6802.         foreach ($kwargs as $k => $arg) {
  6803.             if (in_array($arg[0], [Type::T_FUNCTION_CALLType::T_FUNCTION]) && in_array($arg[1], ['min''max'])) {
  6804.                 return null;
  6805.             }
  6806.         }
  6807.         foreach ($args_to_check as $k => $arg) {
  6808.             if (in_array($arg[0], [Type::T_FUNCTION_CALLType::T_FUNCTION]) && in_array($arg[1], ['min''max'])) {
  6809.                 if (count($kwargs) > || ($k >= && count($args) === 4)) {
  6810.                     return null;
  6811.                 }
  6812.                 $args[$k] = $this->stringifyFncallArgs($arg);
  6813.             }
  6814.             if (
  6815.                 $k >= && count($args) === &&
  6816.                 in_array($arg[0], [Type::T_FUNCTION_CALLType::T_FUNCTION]) &&
  6817.                 in_array($arg[1], ['calc','env'])
  6818.             ) {
  6819.                 return null;
  6820.             }
  6821.         }
  6822.         $hue $this->reduce($args[0]);
  6823.         $saturation $this->reduce($args[1]);
  6824.         $lightness $this->reduce($args[2]);
  6825.         $alpha null;
  6826.         if (\count($args) === 4) {
  6827.             $alpha $this->compileColorPartValue($args[3], 0100false);
  6828.             if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number || ! is_numeric($alpha)) {
  6829.                 return [Type::T_STRING'',
  6830.                     [$funcName '('$args[0], ', '$args[1], ', '$args[2], ', '$args[3], ')']];
  6831.             }
  6832.         } else {
  6833.             if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number) {
  6834.                 return [Type::T_STRING'', [$funcName '('$args[0], ', '$args[1], ', '$args[2], ')']];
  6835.             }
  6836.         }
  6837.         $hueValue fmod($hue->getDimension(), 360);
  6838.         while ($hueValue 0) {
  6839.             $hueValue += 360;
  6840.         }
  6841.         $color $this->toRGB($hueValuemax(0min($saturation->getDimension(), 100)), max(0min($lightness->getDimension(), 100)));
  6842.         if (! \is_null($alpha)) {
  6843.             $color[4] = $alpha;
  6844.         }
  6845.         return $color;
  6846.     }
  6847.     protected static $libHsla = [
  6848.             ['channels'],
  6849.             ['hue''saturation'],
  6850.             ['hue''saturation''lightness'],
  6851.             ['hue''saturation''lightness''alpha']];
  6852.     protected function libHsla($args$kwargs)
  6853.     {
  6854.         return $this->libHsl($args$kwargs'hsla');
  6855.     }
  6856.     protected static $libHue = ['color'];
  6857.     protected function libHue($args)
  6858.     {
  6859.         $color $this->assertColor($args[0], 'color');
  6860.         $hsl $this->toHSL($color[1], $color[2], $color[3]);
  6861.         return new Number($hsl[1], 'deg');
  6862.     }
  6863.     protected static $libSaturation = ['color'];
  6864.     protected function libSaturation($args)
  6865.     {
  6866.         $color $this->assertColor($args[0], 'color');
  6867.         $hsl $this->toHSL($color[1], $color[2], $color[3]);
  6868.         return new Number($hsl[2], '%');
  6869.     }
  6870.     protected static $libLightness = ['color'];
  6871.     protected function libLightness($args)
  6872.     {
  6873.         $color $this->assertColor($args[0], 'color');
  6874.         $hsl $this->toHSL($color[1], $color[2], $color[3]);
  6875.         return new Number($hsl[3], '%');
  6876.     }
  6877.     /*
  6878.      * Todo : a integrer dans le futur module color
  6879.     protected static $libHwb = [
  6880.         ['channels'],
  6881.         ['hue', 'whiteness', 'blackness'],
  6882.         ['hue', 'whiteness', 'blackness', 'alpha'] ];
  6883.     protected function libHwb($args, $kwargs, $funcName = 'hwb')
  6884.     {
  6885.         $args_to_check = $args;
  6886.         if (\count($args) == 1) {
  6887.             if ($args[0][0] !== Type::T_LIST) {
  6888.                 throw $this->error("Missing elements \$whiteness and \$blackness");
  6889.             }
  6890.             if (\trim($args[0][1])) {
  6891.                 throw $this->error("\$channels must be a space-separated list.");
  6892.             }
  6893.             if (! empty($args[0]['enclosing'])) {
  6894.                 throw $this->error("\$channels must be an unbracketed list.");
  6895.             }
  6896.             $args = $args[0][2];
  6897.             if (\count($args) > 3) {
  6898.                 throw $this->error("hwb() : Only 3 elements are allowed but ". \count($args) . "were passed");
  6899.             }
  6900.             $args_to_check = $this->extractSlashAlphaInColorFunction($kwargs['channels'][2]);
  6901.             if (\count($args_to_check) !== \count($kwargs['channels'][2])) {
  6902.                 $args = $args_to_check;
  6903.             }
  6904.         }
  6905.         if (\count($args_to_check) < 2) {
  6906.             throw $this->error("Missing elements \$whiteness and \$blackness");
  6907.         }
  6908.         if (\count($args_to_check) < 3) {
  6909.             throw $this->error("Missing element \$blackness");
  6910.         }
  6911.         if (\count($args_to_check) > 4) {
  6912.             throw $this->error("hwb() : Only 4 elements are allowed but ". \count($args) . "were passed");
  6913.         }
  6914.         foreach ($kwargs as $k => $arg) {
  6915.             if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
  6916.                 return null;
  6917.             }
  6918.         }
  6919.         foreach ($args_to_check as $k => $arg) {
  6920.             if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
  6921.                 if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
  6922.                     return null;
  6923.                 }
  6924.                 $args[$k] = $this->stringifyFncallArgs($arg);
  6925.             }
  6926.             if (
  6927.                 $k >= 2 && count($args) === 4 &&
  6928.                 in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
  6929.                 in_array($arg[1], ['calc','env'])
  6930.             ) {
  6931.                 return null;
  6932.             }
  6933.         }
  6934.         $hue = $this->reduce($args[0]);
  6935.         $whiteness = $this->reduce($args[1]);
  6936.         $blackness = $this->reduce($args[2]);
  6937.         $alpha = null;
  6938.         if (\count($args) === 4) {
  6939.             $alpha = $this->compileColorPartValue($args[3], 0, 1, false);
  6940.             if (! \is_numeric($alpha)) {
  6941.                 $val = $this->compileValue($args[3]);
  6942.                 throw $this->error("\$alpha: $val is not a number");
  6943.             }
  6944.         }
  6945.         $this->assertNumber($hue, 'hue');
  6946.         $this->assertUnit($whiteness, ['%'], 'whiteness');
  6947.         $this->assertUnit($blackness, ['%'], 'blackness');
  6948.         $this->assertRange($whiteness, 0, 100, "0% and 100%", "whiteness");
  6949.         $this->assertRange($blackness, 0, 100, "0% and 100%", "blackness");
  6950.         $w = $whiteness->getDimension();
  6951.         $b = $blackness->getDimension();
  6952.         $hueValue = $hue->getDimension() % 360;
  6953.         while ($hueValue < 0) {
  6954.             $hueValue += 360;
  6955.         }
  6956.         $color = $this->HWBtoRGB($hueValue, $w, $b);
  6957.         if (! \is_null($alpha)) {
  6958.             $color[4] = $alpha;
  6959.         }
  6960.         return $color;
  6961.     }
  6962.     protected static $libWhiteness = ['color'];
  6963.     protected function libWhiteness($args, $kwargs, $funcName = 'whiteness') {
  6964.         $color = $this->assertColor($args[0]);
  6965.         $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
  6966.         return new Number($hwb[2], '%');
  6967.     }
  6968.     protected static $libBlackness = ['color'];
  6969.     protected function libBlackness($args, $kwargs, $funcName = 'blackness') {
  6970.         $color = $this->assertColor($args[0]);
  6971.         $hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
  6972.         return new Number($hwb[3], '%');
  6973.     }
  6974.     */
  6975.     protected function adjustHsl($color$idx$amount)
  6976.     {
  6977.         $hsl $this->toHSL($color[1], $color[2], $color[3]);
  6978.         $hsl[$idx] += $amount;
  6979.         if ($idx !== 1) {
  6980.             // Clamp the saturation and lightness
  6981.             $hsl[$idx] = min(max(0$hsl[$idx]), 100);
  6982.         }
  6983.         $out $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
  6984.         if (isset($color[4])) {
  6985.             $out[4] = $color[4];
  6986.         }
  6987.         return $out;
  6988.     }
  6989.     protected static $libAdjustHue = ['color''degrees'];
  6990.     protected function libAdjustHue($args)
  6991.     {
  6992.         $color $this->assertColor($args[0], 'color');
  6993.         $degrees $this->assertNumber($args[1], 'degrees')->getDimension();
  6994.         return $this->adjustHsl($color1$degrees);
  6995.     }
  6996.     protected static $libLighten = ['color''amount'];
  6997.     protected function libLighten($args)
  6998.     {
  6999.         $color $this->assertColor($args[0], 'color');
  7000.         $amount Util::checkRange('amount', new Range(0100), $args[1], '%');
  7001.         return $this->adjustHsl($color3$amount);
  7002.     }
  7003.     protected static $libDarken = ['color''amount'];
  7004.     protected function libDarken($args)
  7005.     {
  7006.         $color $this->assertColor($args[0], 'color');
  7007.         $amount Util::checkRange('amount', new Range(0100), $args[1], '%');
  7008.         return $this->adjustHsl($color3, -$amount);
  7009.     }
  7010.     protected static $libSaturate = [['color''amount'], ['amount']];
  7011.     protected function libSaturate($args)
  7012.     {
  7013.         $value $args[0];
  7014.         if (count($args) === 1) {
  7015.             $this->assertNumber($args[0], 'amount');
  7016.             return null;
  7017.         }
  7018.         $color $this->assertColor($args[0], 'color');
  7019.         $amount $this->assertNumber($args[1], 'amount');
  7020.         return $this->adjustHsl($color2$amount->valueInRange(0100'amount'));
  7021.     }
  7022.     protected static $libDesaturate = ['color''amount'];
  7023.     protected function libDesaturate($args)
  7024.     {
  7025.         $color $this->assertColor($args[0], 'color');
  7026.         $amount $this->assertNumber($args[1], 'amount');
  7027.         return $this->adjustHsl($color2, -$amount->valueInRange(0100'amount'));
  7028.     }
  7029.     protected static $libGrayscale = ['color'];
  7030.     protected function libGrayscale($args)
  7031.     {
  7032.         $value $args[0];
  7033.         if ($value instanceof Number) {
  7034.             return null;
  7035.         }
  7036.         return $this->adjustHsl($this->assertColor($value'color'), 2, -100);
  7037.     }
  7038.     protected static $libComplement = ['color'];
  7039.     protected function libComplement($args)
  7040.     {
  7041.         return $this->adjustHsl($this->assertColor($args[0], 'color'), 1180);
  7042.     }
  7043.     protected static $libInvert = ['color''weight:100%'];
  7044.     protected function libInvert($args)
  7045.     {
  7046.         $value $args[0];
  7047.         $weight $this->assertNumber($args[1], 'weight');
  7048.         if ($value instanceof Number) {
  7049.             if ($weight->getDimension() != 100 || !$weight->hasUnit('%')) {
  7050.                 throw new SassScriptException('Only one argument may be passed to the plain-CSS invert() function.');
  7051.             }
  7052.             return null;
  7053.         }
  7054.         $color $this->assertColor($value'color');
  7055.         $inverted $color;
  7056.         $inverted[1] = 255 $inverted[1];
  7057.         $inverted[2] = 255 $inverted[2];
  7058.         $inverted[3] = 255 $inverted[3];
  7059.         return $this->libMix([$inverted$color$weight]);
  7060.     }
  7061.     // increases opacity by amount
  7062.     protected static $libOpacify = ['color''amount'];
  7063.     protected function libOpacify($args)
  7064.     {
  7065.         $color $this->assertColor($args[0], 'color');
  7066.         $amount $this->assertNumber($args[1], 'amount');
  7067.         $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount->valueInRange(01'amount');
  7068.         $color[4] = min(1max(0$color[4]));
  7069.         return $color;
  7070.     }
  7071.     protected static $libFadeIn = ['color''amount'];
  7072.     protected function libFadeIn($args)
  7073.     {
  7074.         return $this->libOpacify($args);
  7075.     }
  7076.     // decreases opacity by amount
  7077.     protected static $libTransparentize = ['color''amount'];
  7078.     protected function libTransparentize($args)
  7079.     {
  7080.         $color $this->assertColor($args[0], 'color');
  7081.         $amount $this->assertNumber($args[1], 'amount');
  7082.         $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount->valueInRange(01'amount');
  7083.         $color[4] = min(1max(0$color[4]));
  7084.         return $color;
  7085.     }
  7086.     protected static $libFadeOut = ['color''amount'];
  7087.     protected function libFadeOut($args)
  7088.     {
  7089.         return $this->libTransparentize($args);
  7090.     }
  7091.     protected static $libUnquote = ['string'];
  7092.     protected function libUnquote($args)
  7093.     {
  7094.         try {
  7095.             $str $this->assertString($args[0], 'string');
  7096.         } catch (SassScriptException $e) {
  7097.             $value $this->compileValue($args[0]);
  7098.             $fname $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
  7099.             $line  $this->sourceLine;
  7100.             $message "Passing $value, a non-string value, to unquote()
  7101. will be an error in future versions of Sass.\n         on line $line of $fname";
  7102.             $this->logger->warn($messagetrue);
  7103.             return $args[0];
  7104.         }
  7105.         $str[1] = '';
  7106.         return $str;
  7107.     }
  7108.     protected static $libQuote = ['string'];
  7109.     protected function libQuote($args)
  7110.     {
  7111.         $value $this->assertString($args[0], 'string');
  7112.         $value[1] = '"';
  7113.         return $value;
  7114.     }
  7115.     protected static $libPercentage = ['number'];
  7116.     protected function libPercentage($args)
  7117.     {
  7118.         $num $this->assertNumber($args[0], 'number');
  7119.         $num->assertNoUnits('number');
  7120.         return new Number($num->getDimension() * 100'%');
  7121.     }
  7122.     protected static $libRound = ['number'];
  7123.     protected function libRound($args)
  7124.     {
  7125.         $num $this->assertNumber($args[0], 'number');
  7126.         return new Number(round($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
  7127.     }
  7128.     protected static $libFloor = ['number'];
  7129.     protected function libFloor($args)
  7130.     {
  7131.         $num $this->assertNumber($args[0], 'number');
  7132.         return new Number(floor($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
  7133.     }
  7134.     protected static $libCeil = ['number'];
  7135.     protected function libCeil($args)
  7136.     {
  7137.         $num $this->assertNumber($args[0], 'number');
  7138.         return new Number(ceil($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
  7139.     }
  7140.     protected static $libAbs = ['number'];
  7141.     protected function libAbs($args)
  7142.     {
  7143.         $num $this->assertNumber($args[0], 'number');
  7144.         return new Number(abs($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
  7145.     }
  7146.     protected static $libMin = ['numbers...'];
  7147.     protected function libMin($args)
  7148.     {
  7149.         /**
  7150.          * @var Number|null
  7151.          */
  7152.         $min null;
  7153.         foreach ($args[0][2] as $arg) {
  7154.             $number $this->assertNumber($arg);
  7155.             if (\is_null($min) || $min->greaterThan($number)) {
  7156.                 $min $number;
  7157.             }
  7158.         }
  7159.         if (!\is_null($min)) {
  7160.             return $min;
  7161.         }
  7162.         throw $this->error('At least one argument must be passed.');
  7163.     }
  7164.     protected static $libMax = ['numbers...'];
  7165.     protected function libMax($args)
  7166.     {
  7167.         /**
  7168.          * @var Number|null
  7169.          */
  7170.         $max null;
  7171.         foreach ($args[0][2] as $arg) {
  7172.             $number $this->assertNumber($arg);
  7173.             if (\is_null($max) || $max->lessThan($number)) {
  7174.                 $max $number;
  7175.             }
  7176.         }
  7177.         if (!\is_null($max)) {
  7178.             return $max;
  7179.         }
  7180.         throw $this->error('At least one argument must be passed.');
  7181.     }
  7182.     protected static $libLength = ['list'];
  7183.     protected function libLength($args)
  7184.     {
  7185.         $list $this->coerceList($args[0], ','true);
  7186.         return new Number(\count($list[2]), '');
  7187.     }
  7188.     protected static $libListSeparator = ['list'];
  7189.     protected function libListSeparator($args)
  7190.     {
  7191.         if (! \in_array($args[0][0], [Type::T_LISTType::T_MAP])) {
  7192.             return [Type::T_KEYWORD'space'];
  7193.         }
  7194.         $list $this->coerceList($args[0]);
  7195.         if (\count($list[2]) <= && empty($list['enclosing'])) {
  7196.             return [Type::T_KEYWORD'space'];
  7197.         }
  7198.         if ($list[1] === ',') {
  7199.             return [Type::T_KEYWORD'comma'];
  7200.         }
  7201.         return [Type::T_KEYWORD'space'];
  7202.     }
  7203.     protected static $libNth = ['list''n'];
  7204.     protected function libNth($args)
  7205.     {
  7206.         $list $this->coerceList($args[0], ','false);
  7207.         $n $this->assertNumber($args[1])->getDimension();
  7208.         if ($n 0) {
  7209.             $n--;
  7210.         } elseif ($n 0) {
  7211.             $n += \count($list[2]);
  7212.         }
  7213.         return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue;
  7214.     }
  7215.     protected static $libSetNth = ['list''n''value'];
  7216.     protected function libSetNth($args)
  7217.     {
  7218.         $list $this->coerceList($args[0]);
  7219.         $n $this->assertNumber($args[1])->getDimension();
  7220.         if ($n 0) {
  7221.             $n--;
  7222.         } elseif ($n 0) {
  7223.             $n += \count($list[2]);
  7224.         }
  7225.         if (! isset($list[2][$n])) {
  7226.             throw $this->error('Invalid argument for "n"');
  7227.         }
  7228.         $list[2][$n] = $args[2];
  7229.         return $list;
  7230.     }
  7231.     protected static $libMapGet = ['map''key'];
  7232.     protected function libMapGet($args)
  7233.     {
  7234.         $map $this->assertMap($args[0], 'map');
  7235.         $key $args[1];
  7236.         if (! \is_null($key)) {
  7237.             $key $this->compileStringContent($this->coerceString($key));
  7238.             for ($i = \count($map[1]) - 1$i >= 0$i--) {
  7239.                 if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
  7240.                     return $map[2][$i];
  7241.                 }
  7242.             }
  7243.         }
  7244.         return static::$null;
  7245.     }
  7246.     protected static $libMapKeys = ['map'];
  7247.     protected function libMapKeys($args)
  7248.     {
  7249.         $map $this->assertMap($args[0], 'map');
  7250.         $keys $map[1];
  7251.         return [Type::T_LIST','$keys];
  7252.     }
  7253.     protected static $libMapValues = ['map'];
  7254.     protected function libMapValues($args)
  7255.     {
  7256.         $map $this->assertMap($args[0], 'map');
  7257.         $values $map[2];
  7258.         return [Type::T_LIST','$values];
  7259.     }
  7260.     protected static $libMapRemove = [
  7261.         ['map'],
  7262.         ['map''key''keys...'],
  7263.     ];
  7264.     protected function libMapRemove($args)
  7265.     {
  7266.         $map $this->assertMap($args[0], 'map');
  7267.         if (\count($args) === 1) {
  7268.             return $map;
  7269.         }
  7270.         $keys = [];
  7271.         $keys[] = $this->compileStringContent($this->coerceString($args[1]));
  7272.         foreach ($args[2][2] as $key) {
  7273.             $keys[] = $this->compileStringContent($this->coerceString($key));
  7274.         }
  7275.         for ($i = \count($map[1]) - 1$i >= 0$i--) {
  7276.             if (in_array($this->compileStringContent($this->coerceString($map[1][$i])), $keys)) {
  7277.                 array_splice($map[1], $i1);
  7278.                 array_splice($map[2], $i1);
  7279.             }
  7280.         }
  7281.         return $map;
  7282.     }
  7283.     protected static $libMapHasKey = ['map''key'];
  7284.     protected function libMapHasKey($args)
  7285.     {
  7286.         $map $this->assertMap($args[0], 'map');
  7287.         return $this->toBool($this->mapHasKey($map$args[1]));
  7288.     }
  7289.     /**
  7290.      * @param array|Number $keyValue
  7291.      *
  7292.      * @return bool
  7293.      */
  7294.     private function mapHasKey(array $map$keyValue)
  7295.     {
  7296.         $key $this->compileStringContent($this->coerceString($keyValue));
  7297.         for ($i = \count($map[1]) - 1$i >= 0$i--) {
  7298.             if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
  7299.                 return true;
  7300.             }
  7301.         }
  7302.         return false;
  7303.     }
  7304.     protected static $libMapMerge = [
  7305.         ['map1''map2'],
  7306.         ['map-1''map-2']
  7307.     ];
  7308.     protected function libMapMerge($args)
  7309.     {
  7310.         $map1 $this->assertMap($args[0], 'map1');
  7311.         $map2 $this->assertMap($args[1], 'map2');
  7312.         foreach ($map2[1] as $i2 => $key2) {
  7313.             $key $this->compileStringContent($this->coerceString($key2));
  7314.             foreach ($map1[1] as $i1 => $key1) {
  7315.                 if ($key === $this->compileStringContent($this->coerceString($key1))) {
  7316.                     $map1[2][$i1] = $map2[2][$i2];
  7317.                     continue 2;
  7318.                 }
  7319.             }
  7320.             $map1[1][] = $map2[1][$i2];
  7321.             $map1[2][] = $map2[2][$i2];
  7322.         }
  7323.         return $map1;
  7324.     }
  7325.     protected static $libKeywords = ['args'];
  7326.     protected function libKeywords($args)
  7327.     {
  7328.         $value $args[0];
  7329.         if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
  7330.             $compiledValue $this->compileValue($value);
  7331.             throw SassScriptException::forArgument($compiledValue ' is not an argument list.''args');
  7332.         }
  7333.         $keys = [];
  7334.         $values = [];
  7335.         foreach ($this->getArgumentListKeywords($value) as $name => $arg) {
  7336.             $keys[] = [Type::T_KEYWORD$name];
  7337.             $values[] = $arg;
  7338.         }
  7339.         return [Type::T_MAP$keys$values];
  7340.     }
  7341.     protected static $libIsBracketed = ['list'];
  7342.     protected function libIsBracketed($args)
  7343.     {
  7344.         $list $args[0];
  7345.         $this->coerceList($list' ');
  7346.         if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
  7347.             return self::$true;
  7348.         }
  7349.         return self::$false;
  7350.     }
  7351.     /**
  7352.      * @param array $list1
  7353.      * @param array|Number|null $sep
  7354.      *
  7355.      * @return string
  7356.      * @throws CompilerException
  7357.      */
  7358.     protected function listSeparatorForJoin($list1$sep)
  7359.     {
  7360.         if (! isset($sep)) {
  7361.             return $list1[1];
  7362.         }
  7363.         switch ($this->compileValue($sep)) {
  7364.             case 'comma':
  7365.                 return ',';
  7366.             case 'space':
  7367.                 return ' ';
  7368.             default:
  7369.                 return $list1[1];
  7370.         }
  7371.     }
  7372.     protected static $libJoin = ['list1''list2''separator:null''bracketed:auto'];
  7373.     protected function libJoin($args)
  7374.     {
  7375.         list($list1$list2$sep$bracketed) = $args;
  7376.         $list1 $this->coerceList($list1' 'true);
  7377.         $list2 $this->coerceList($list2' 'true);
  7378.         $sep   $this->listSeparatorForJoin($list1$sep);
  7379.         if ($bracketed === static::$true) {
  7380.             $bracketed true;
  7381.         } elseif ($bracketed === static::$false) {
  7382.             $bracketed false;
  7383.         } elseif ($bracketed === [Type::T_KEYWORD'auto']) {
  7384.             $bracketed 'auto';
  7385.         } elseif ($bracketed === static::$null) {
  7386.             $bracketed false;
  7387.         } else {
  7388.             $bracketed $this->compileValue($bracketed);
  7389.             $bracketed = ! ! $bracketed;
  7390.             if ($bracketed === true) {
  7391.                 $bracketed true;
  7392.             }
  7393.         }
  7394.         if ($bracketed === 'auto') {
  7395.             $bracketed false;
  7396.             if (! empty($list1['enclosing']) && $list1['enclosing'] === 'bracket') {
  7397.                 $bracketed true;
  7398.             }
  7399.         }
  7400.         $res = [Type::T_LIST$separray_merge($list1[2], $list2[2])];
  7401.         if (isset($list1['enclosing'])) {
  7402.             $res['enlcosing'] = $list1['enclosing'];
  7403.         }
  7404.         if ($bracketed) {
  7405.             $res['enclosing'] = 'bracket';
  7406.         }
  7407.         return $res;
  7408.     }
  7409.     protected static $libAppend = ['list''val''separator:null'];
  7410.     protected function libAppend($args)
  7411.     {
  7412.         list($list1$value$sep) = $args;
  7413.         $list1 $this->coerceList($list1' 'true);
  7414.         $sep   $this->listSeparatorForJoin($list1$sep);
  7415.         $res   = [Type::T_LIST$separray_merge($list1[2], [$value])];
  7416.         if (isset($list1['enclosing'])) {
  7417.             $res['enclosing'] = $list1['enclosing'];
  7418.         }
  7419.         return $res;
  7420.     }
  7421.     protected static $libZip = ['lists...'];
  7422.     protected function libZip($args)
  7423.     {
  7424.         $argLists = [];
  7425.         foreach ($args[0][2] as $arg) {
  7426.             $argLists[] = $this->coerceList($arg);
  7427.         }
  7428.         $lists = [];
  7429.         $firstList array_shift($argLists);
  7430.         $result = [Type::T_LIST','$lists];
  7431.         if (! \is_null($firstList)) {
  7432.             foreach ($firstList[2] as $key => $item) {
  7433.                 $list = [Type::T_LIST'', [$item]];
  7434.                 foreach ($argLists as $arg) {
  7435.                     if (isset($arg[2][$key])) {
  7436.                         $list[2][] = $arg[2][$key];
  7437.                     } else {
  7438.                         break 2;
  7439.                     }
  7440.                 }
  7441.                 $lists[] = $list;
  7442.             }
  7443.             $result[2] = $lists;
  7444.         } else {
  7445.             $result['enclosing'] = 'parent';
  7446.         }
  7447.         return $result;
  7448.     }
  7449.     protected static $libTypeOf = ['value'];
  7450.     protected function libTypeOf($args)
  7451.     {
  7452.         $value $args[0];
  7453.         return [Type::T_KEYWORD$this->getTypeOf($value)];
  7454.     }
  7455.     /**
  7456.      * @param array|Number $value
  7457.      *
  7458.      * @return string
  7459.      */
  7460.     private function getTypeOf($value)
  7461.     {
  7462.         switch ($value[0]) {
  7463.             case Type::T_KEYWORD:
  7464.                 if ($value === static::$true || $value === static::$false) {
  7465.                     return 'bool';
  7466.                 }
  7467.                 if ($this->coerceColor($value)) {
  7468.                     return 'color';
  7469.                 }
  7470.                 // fall-thru
  7471.             case Type::T_FUNCTION:
  7472.                 return 'string';
  7473.             case Type::T_FUNCTION_REFERENCE:
  7474.                 return 'function';
  7475.             case Type::T_LIST:
  7476.                 if (isset($value[3]) && \is_array($value[3])) {
  7477.                     return 'arglist';
  7478.                 }
  7479.                 // fall-thru
  7480.             default:
  7481.                 return $value[0];
  7482.         }
  7483.     }
  7484.     protected static $libUnit = ['number'];
  7485.     protected function libUnit($args)
  7486.     {
  7487.         $num $this->assertNumber($args[0], 'number');
  7488.         return [Type::T_STRING'"', [$num->unitStr()]];
  7489.     }
  7490.     protected static $libUnitless = ['number'];
  7491.     protected function libUnitless($args)
  7492.     {
  7493.         $value $this->assertNumber($args[0], 'number');
  7494.         return $this->toBool($value->unitless());
  7495.     }
  7496.     protected static $libComparable = [
  7497.         ['number1''number2'],
  7498.         ['number-1''number-2']
  7499.     ];
  7500.     protected function libComparable($args)
  7501.     {
  7502.         list($number1$number2) = $args;
  7503.         if (
  7504.             ! $number1 instanceof Number ||
  7505.             ! $number2 instanceof Number
  7506.         ) {
  7507.             throw $this->error('Invalid argument(s) for "comparable"');
  7508.         }
  7509.         return $this->toBool($number1->isComparableTo($number2));
  7510.     }
  7511.     protected static $libStrIndex = ['string''substring'];
  7512.     protected function libStrIndex($args)
  7513.     {
  7514.         $string $this->assertString($args[0], 'string');
  7515.         $stringContent $this->compileStringContent($string);
  7516.         $substring $this->assertString($args[1], 'substring');
  7517.         $substringContent $this->compileStringContent($substring);
  7518.         if (! \strlen($substringContent)) {
  7519.             $result 0;
  7520.         } else {
  7521.             $result Util::mbStrpos($stringContent$substringContent);
  7522.         }
  7523.         return $result === false ? static::$null : new Number($result 1'');
  7524.     }
  7525.     protected static $libStrInsert = ['string''insert''index'];
  7526.     protected function libStrInsert($args)
  7527.     {
  7528.         $string $this->assertString($args[0], 'string');
  7529.         $stringContent $this->compileStringContent($string);
  7530.         $insert $this->assertString($args[1], 'insert');
  7531.         $insertContent $this->compileStringContent($insert);
  7532.         $index $this->assertInteger($args[2], 'index');
  7533.         if ($index 0) {
  7534.             $index $index 1;
  7535.         }
  7536.         if ($index 0) {
  7537.             $index Util::mbStrlen($stringContent) + $index;
  7538.         }
  7539.         $string[2] = [
  7540.             Util::mbSubstr($stringContent0$index),
  7541.             $insertContent,
  7542.             Util::mbSubstr($stringContent$index)
  7543.         ];
  7544.         return $string;
  7545.     }
  7546.     protected static $libStrLength = ['string'];
  7547.     protected function libStrLength($args)
  7548.     {
  7549.         $string $this->assertString($args[0], 'string');
  7550.         $stringContent $this->compileStringContent($string);
  7551.         return new Number(Util::mbStrlen($stringContent), '');
  7552.     }
  7553.     protected static $libStrSlice = ['string''start-at''end-at:-1'];
  7554.     protected function libStrSlice($args)
  7555.     {
  7556.         $string $this->assertString($args[0], 'string');
  7557.         $stringContent $this->compileStringContent($string);
  7558.         $start $this->assertNumber($args[1], 'start-at');
  7559.         $start->assertNoUnits('start-at');
  7560.         $startInt $this->assertInteger($start'start-at');
  7561.         $end $this->assertNumber($args[2], 'end-at');
  7562.         $end->assertNoUnits('end-at');
  7563.         $endInt $this->assertInteger($end'end-at');
  7564.         if ($endInt === 0) {
  7565.             return [Type::T_STRING$string[1], []];
  7566.         }
  7567.         if ($startInt 0) {
  7568.             $startInt--;
  7569.         }
  7570.         if ($endInt 0) {
  7571.             $endInt Util::mbStrlen($stringContent) + $endInt;
  7572.         } else {
  7573.             $endInt--;
  7574.         }
  7575.         if ($endInt $startInt) {
  7576.             return [Type::T_STRING$string[1], []];
  7577.         }
  7578.         $length $endInt $startInt 1// The end of the slice is inclusive
  7579.         $string[2] = [Util::mbSubstr($stringContent$startInt$length)];
  7580.         return $string;
  7581.     }
  7582.     protected static $libToLowerCase = ['string'];
  7583.     protected function libToLowerCase($args)
  7584.     {
  7585.         $string $this->assertString($args[0], 'string');
  7586.         $stringContent $this->compileStringContent($string);
  7587.         $string[2] = [$this->stringTransformAsciiOnly($stringContent'strtolower')];
  7588.         return $string;
  7589.     }
  7590.     protected static $libToUpperCase = ['string'];
  7591.     protected function libToUpperCase($args)
  7592.     {
  7593.         $string $this->assertString($args[0], 'string');
  7594.         $stringContent $this->compileStringContent($string);
  7595.         $string[2] = [$this->stringTransformAsciiOnly($stringContent'strtoupper')];
  7596.         return $string;
  7597.     }
  7598.     /**
  7599.      * Apply a filter on a string content, only on ascii chars
  7600.      * let extended chars untouched
  7601.      *
  7602.      * @param string $stringContent
  7603.      * @param callable $filter
  7604.      * @return string
  7605.      */
  7606.     protected function stringTransformAsciiOnly($stringContent$filter)
  7607.     {
  7608.         $mblength Util::mbStrlen($stringContent);
  7609.         if ($mblength === strlen($stringContent)) {
  7610.             return $filter($stringContent);
  7611.         }
  7612.         $filteredString "";
  7613.         for ($i 0$i $mblength$i++) {
  7614.             $char Util::mbSubstr($stringContent$i1);
  7615.             if (strlen($char) > 1) {
  7616.                 $filteredString .= $char;
  7617.             } else {
  7618.                 $filteredString .= $filter($char);
  7619.             }
  7620.         }
  7621.         return $filteredString;
  7622.     }
  7623.     protected static $libFeatureExists = ['feature'];
  7624.     protected function libFeatureExists($args)
  7625.     {
  7626.         $string $this->assertString($args[0], 'feature');
  7627.         $name $this->compileStringContent($string);
  7628.         return $this->toBool(
  7629.             \array_key_exists($name$this->registeredFeatures) ? $this->registeredFeatures[$name] : false
  7630.         );
  7631.     }
  7632.     protected static $libFunctionExists = ['name'];
  7633.     protected function libFunctionExists($args)
  7634.     {
  7635.         $string $this->assertString($args[0], 'name');
  7636.         $name $this->compileStringContent($string);
  7637.         // user defined functions
  7638.         if ($this->has(static::$namespaces['function'] . $name)) {
  7639.             return self::$true;
  7640.         }
  7641.         $name $this->normalizeName($name);
  7642.         if (isset($this->userFunctions[$name])) {
  7643.             return self::$true;
  7644.         }
  7645.         // built-in functions
  7646.         $f $this->getBuiltinFunction($name);
  7647.         return $this->toBool(\is_callable($f));
  7648.     }
  7649.     protected static $libGlobalVariableExists = ['name'];
  7650.     protected function libGlobalVariableExists($args)
  7651.     {
  7652.         $string $this->assertString($args[0], 'name');
  7653.         $name $this->compileStringContent($string);
  7654.         return $this->toBool($this->has($name$this->rootEnv));
  7655.     }
  7656.     protected static $libMixinExists = ['name'];
  7657.     protected function libMixinExists($args)
  7658.     {
  7659.         $string $this->assertString($args[0], 'name');
  7660.         $name $this->compileStringContent($string);
  7661.         return $this->toBool($this->has(static::$namespaces['mixin'] . $name));
  7662.     }
  7663.     protected static $libVariableExists = ['name'];
  7664.     protected function libVariableExists($args)
  7665.     {
  7666.         $string $this->assertString($args[0], 'name');
  7667.         $name $this->compileStringContent($string);
  7668.         return $this->toBool($this->has($name));
  7669.     }
  7670.     protected static $libCounter = ['args...'];
  7671.     /**
  7672.      * Workaround IE7's content counter bug.
  7673.      *
  7674.      * @param array $args
  7675.      *
  7676.      * @return array
  7677.      */
  7678.     protected function libCounter($args)
  7679.     {
  7680.         $list array_map([$this'compileValue'], $args[0][2]);
  7681.         return [Type::T_STRING'', ['counter(' implode(','$list) . ')']];
  7682.     }
  7683.     protected static $libRandom = ['limit:null'];
  7684.     protected function libRandom($args)
  7685.     {
  7686.         if (isset($args[0]) && $args[0] !== static::$null) {
  7687.             $n $this->assertInteger($args[0], 'limit');
  7688.             if ($n 1) {
  7689.                 throw new SassScriptException("\$limit: Must be greater than 0, was $n.");
  7690.             }
  7691.             return new Number(mt_rand(1$n), '');
  7692.         }
  7693.         $max mt_getrandmax();
  7694.         return new Number(mt_rand(0$max 1) / $max'');
  7695.     }
  7696.     protected static $libUniqueId = [];
  7697.     protected function libUniqueId()
  7698.     {
  7699.         static $id;
  7700.         if (! isset($id)) {
  7701.             $id PHP_INT_SIZE === 4
  7702.                 mt_rand(0pow(365)) . str_pad(mt_rand(0pow(365)) % 100000007'0'STR_PAD_LEFT)
  7703.                 : mt_rand(0pow(368));
  7704.         }
  7705.         $id += mt_rand(010) + 1;
  7706.         return [Type::T_STRING'', ['u' str_pad(base_convert($id1036), 8'0'STR_PAD_LEFT)]];
  7707.     }
  7708.     /**
  7709.      * @param array|Number $value
  7710.      * @param bool         $force_enclosing_display
  7711.      *
  7712.      * @return array
  7713.      */
  7714.     protected function inspectFormatValue($value$force_enclosing_display false)
  7715.     {
  7716.         if ($value === static::$null) {
  7717.             $value = [Type::T_KEYWORD'null'];
  7718.         }
  7719.         $stringValue = [$value];
  7720.         if ($value instanceof Number) {
  7721.             return [Type::T_STRING''$stringValue];
  7722.         }
  7723.         if ($value[0] === Type::T_LIST) {
  7724.             if (end($value[2]) === static::$null) {
  7725.                 array_pop($value[2]);
  7726.                 $value[2][] = [Type::T_STRING'', ['']];
  7727.                 $force_enclosing_display true;
  7728.             }
  7729.             if (
  7730.                 ! empty($value['enclosing']) &&
  7731.                 ($force_enclosing_display ||
  7732.                     ($value['enclosing'] === 'bracket') ||
  7733.                     ! \count($value[2]))
  7734.             ) {
  7735.                 $value['enclosing'] = 'forced_' $value['enclosing'];
  7736.                 $force_enclosing_display true;
  7737.             }
  7738.             foreach ($value[2] as $k => $listelement) {
  7739.                 $value[2][$k] = $this->inspectFormatValue($listelement$force_enclosing_display);
  7740.             }
  7741.             $stringValue = [$value];
  7742.         }
  7743.         return [Type::T_STRING''$stringValue];
  7744.     }
  7745.     protected static $libInspect = ['value'];
  7746.     protected function libInspect($args)
  7747.     {
  7748.         $value $args[0];
  7749.         return $this->inspectFormatValue($value);
  7750.     }
  7751.     /**
  7752.      * Preprocess selector args
  7753.      *
  7754.      * @param array       $arg
  7755.      * @param string|null $varname
  7756.      * @param bool        $allowParent
  7757.      *
  7758.      * @return array
  7759.      */
  7760.     protected function getSelectorArg($arg$varname null$allowParent false)
  7761.     {
  7762.         static $parser null;
  7763.         if (\is_null($parser)) {
  7764.             $parser $this->parserFactory(__METHOD__);
  7765.         }
  7766.         if (! $this->checkSelectorArgType($arg)) {
  7767.             $var_value $this->compileValue($arg);
  7768.             throw SassScriptException::forArgument("$var_value is not a valid selector: it must be a string, a list of strings, or a list of lists of strings"$varname);
  7769.         }
  7770.         if ($arg[0] === Type::T_STRING) {
  7771.             $arg[1] = '';
  7772.         }
  7773.         $arg $this->compileValue($arg);
  7774.         $parsedSelector = [];
  7775.         if ($parser->parseSelector($arg$parsedSelectortrue)) {
  7776.             $selector $this->evalSelectors($parsedSelector);
  7777.             $gluedSelector $this->glueFunctionSelectors($selector);
  7778.             if (! $allowParent) {
  7779.                 foreach ($gluedSelector as $selector) {
  7780.                     foreach ($selector as $s) {
  7781.                         if (in_array(static::$selfSelector$s)) {
  7782.                             throw SassScriptException::forArgument("Parent selectors aren't allowed here."$varname);
  7783.                         }
  7784.                     }
  7785.                 }
  7786.             }
  7787.             return $gluedSelector;
  7788.         }
  7789.         throw SassScriptException::forArgument("expected more input, invalid selector."$varname);
  7790.     }
  7791.     /**
  7792.      * Check variable type for getSelectorArg() function
  7793.      * @param array $arg
  7794.      * @param int $maxDepth
  7795.      * @return bool
  7796.      */
  7797.     protected function checkSelectorArgType($arg$maxDepth 2)
  7798.     {
  7799.         if ($arg[0] === Type::T_LIST && $maxDepth 0) {
  7800.             foreach ($arg[2] as $elt) {
  7801.                 if (! $this->checkSelectorArgType($elt$maxDepth 1)) {
  7802.                     return false;
  7803.                 }
  7804.             }
  7805.             return true;
  7806.         }
  7807.         if (!in_array($arg[0], [Type::T_STRINGType::T_KEYWORD])) {
  7808.             return false;
  7809.         }
  7810.         return true;
  7811.     }
  7812.     /**
  7813.      * Postprocess selector to output in right format
  7814.      *
  7815.      * @param array $selectors
  7816.      *
  7817.      * @return array
  7818.      */
  7819.     protected function formatOutputSelector($selectors)
  7820.     {
  7821.         $selectors $this->collapseSelectorsAsList($selectors);
  7822.         return $selectors;
  7823.     }
  7824.     protected static $libIsSuperselector = ['super''sub'];
  7825.     protected function libIsSuperselector($args)
  7826.     {
  7827.         list($super$sub) = $args;
  7828.         $super $this->getSelectorArg($super'super');
  7829.         $sub $this->getSelectorArg($sub'sub');
  7830.         return $this->toBool($this->isSuperSelector($super$sub));
  7831.     }
  7832.     /**
  7833.      * Test a $super selector again $sub
  7834.      *
  7835.      * @param array $super
  7836.      * @param array $sub
  7837.      *
  7838.      * @return boolean
  7839.      */
  7840.     protected function isSuperSelector($super$sub)
  7841.     {
  7842.         // one and only one selector for each arg
  7843.         if (! $super) {
  7844.             throw $this->error('Invalid super selector for isSuperSelector()');
  7845.         }
  7846.         if (! $sub) {
  7847.             throw $this->error('Invalid sub selector for isSuperSelector()');
  7848.         }
  7849.         if (count($sub) > 1) {
  7850.             foreach ($sub as $s) {
  7851.                 if (! $this->isSuperSelector($super, [$s])) {
  7852.                     return false;
  7853.                 }
  7854.             }
  7855.             return true;
  7856.         }
  7857.         if (count($super) > 1) {
  7858.             foreach ($super as $s) {
  7859.                 if ($this->isSuperSelector([$s], $sub)) {
  7860.                     return true;
  7861.                 }
  7862.             }
  7863.             return false;
  7864.         }
  7865.         $super reset($super);
  7866.         $sub reset($sub);
  7867.         $i 0;
  7868.         $nextMustMatch false;
  7869.         foreach ($super as $node) {
  7870.             $compound '';
  7871.             array_walk_recursive(
  7872.                 $node,
  7873.                 function ($value$key) use (&$compound) {
  7874.                     $compound .= $value;
  7875.                 }
  7876.             );
  7877.             if ($this->isImmediateRelationshipCombinator($compound)) {
  7878.                 if ($node !== $sub[$i]) {
  7879.                     return false;
  7880.                 }
  7881.                 $nextMustMatch true;
  7882.                 $i++;
  7883.             } else {
  7884.                 while ($i < \count($sub) && ! $this->isSuperPart($node$sub[$i])) {
  7885.                     if ($nextMustMatch) {
  7886.                         return false;
  7887.                     }
  7888.                     $i++;
  7889.                 }
  7890.                 if ($i >= \count($sub)) {
  7891.                     return false;
  7892.                 }
  7893.                 $nextMustMatch false;
  7894.                 $i++;
  7895.             }
  7896.         }
  7897.         return true;
  7898.     }
  7899.     /**
  7900.      * Test a part of super selector again a part of sub selector
  7901.      *
  7902.      * @param array $superParts
  7903.      * @param array $subParts
  7904.      *
  7905.      * @return boolean
  7906.      */
  7907.     protected function isSuperPart($superParts$subParts)
  7908.     {
  7909.         $i 0;
  7910.         foreach ($superParts as $superPart) {
  7911.             while ($i < \count($subParts) && $subParts[$i] !== $superPart) {
  7912.                 $i++;
  7913.             }
  7914.             if ($i >= \count($subParts)) {
  7915.                 return false;
  7916.             }
  7917.             $i++;
  7918.         }
  7919.         return true;
  7920.     }
  7921.     protected static $libSelectorAppend = ['selector...'];
  7922.     protected function libSelectorAppend($args)
  7923.     {
  7924.         // get the selector... list
  7925.         $args reset($args);
  7926.         $args $args[2];
  7927.         if (\count($args) < 1) {
  7928.             throw $this->error('selector-append() needs at least 1 argument');
  7929.         }
  7930.         $selectors = [];
  7931.         foreach ($args as $arg) {
  7932.             $selectors[] = $this->getSelectorArg($arg'selector');
  7933.         }
  7934.         return $this->formatOutputSelector($this->selectorAppend($selectors));
  7935.     }
  7936.     /**
  7937.      * Append parts of the last selector in the list to the previous, recursively
  7938.      *
  7939.      * @param array $selectors
  7940.      *
  7941.      * @return array
  7942.      *
  7943.      * @throws \ScssPhp\ScssPhp\Exception\CompilerException
  7944.      */
  7945.     protected function selectorAppend($selectors)
  7946.     {
  7947.         $lastSelectors array_pop($selectors);
  7948.         if (! $lastSelectors) {
  7949.             throw $this->error('Invalid selector list in selector-append()');
  7950.         }
  7951.         while (\count($selectors)) {
  7952.             $previousSelectors array_pop($selectors);
  7953.             if (! $previousSelectors) {
  7954.                 throw $this->error('Invalid selector list in selector-append()');
  7955.             }
  7956.             // do the trick, happening $lastSelector to $previousSelector
  7957.             $appended = [];
  7958.             foreach ($lastSelectors as $lastSelector) {
  7959.                 $previous $previousSelectors;
  7960.                 foreach ($lastSelector as $lastSelectorParts) {
  7961.                     foreach ($lastSelectorParts as $lastSelectorPart) {
  7962.                         foreach ($previous as $i => $previousSelector) {
  7963.                             foreach ($previousSelector as $j => $previousSelectorParts) {
  7964.                                 $previous[$i][$j][] = $lastSelectorPart;
  7965.                             }
  7966.                         }
  7967.                     }
  7968.                 }
  7969.                 foreach ($previous as $ps) {
  7970.                     $appended[] = $ps;
  7971.                 }
  7972.             }
  7973.             $lastSelectors $appended;
  7974.         }
  7975.         return $lastSelectors;
  7976.     }
  7977.     protected static $libSelectorExtend = [
  7978.         ['selector''extendee''extender'],
  7979.         ['selectors''extendee''extender']
  7980.     ];
  7981.     protected function libSelectorExtend($args)
  7982.     {
  7983.         list($selectors$extendee$extender) = $args;
  7984.         $selectors $this->getSelectorArg($selectors'selector');
  7985.         $extendee  $this->getSelectorArg($extendee'extendee');
  7986.         $extender  $this->getSelectorArg($extender'extender');
  7987.         if (! $selectors || ! $extendee || ! $extender) {
  7988.             throw $this->error('selector-extend() invalid arguments');
  7989.         }
  7990.         $extended $this->extendOrReplaceSelectors($selectors$extendee$extender);
  7991.         return $this->formatOutputSelector($extended);
  7992.     }
  7993.     protected static $libSelectorReplace = [
  7994.         ['selector''original''replacement'],
  7995.         ['selectors''original''replacement']
  7996.     ];
  7997.     protected function libSelectorReplace($args)
  7998.     {
  7999.         list($selectors$original$replacement) = $args;
  8000.         $selectors   $this->getSelectorArg($selectors'selector');
  8001.         $original    $this->getSelectorArg($original'original');
  8002.         $replacement $this->getSelectorArg($replacement'replacement');
  8003.         if (! $selectors || ! $original || ! $replacement) {
  8004.             throw $this->error('selector-replace() invalid arguments');
  8005.         }
  8006.         $replaced $this->extendOrReplaceSelectors($selectors$original$replacementtrue);
  8007.         return $this->formatOutputSelector($replaced);
  8008.     }
  8009.     /**
  8010.      * Extend/replace in selectors
  8011.      * used by selector-extend and selector-replace that use the same logic
  8012.      *
  8013.      * @param array   $selectors
  8014.      * @param array   $extendee
  8015.      * @param array   $extender
  8016.      * @param boolean $replace
  8017.      *
  8018.      * @return array
  8019.      */
  8020.     protected function extendOrReplaceSelectors($selectors$extendee$extender$replace false)
  8021.     {
  8022.         $saveExtends $this->extends;
  8023.         $saveExtendsMap $this->extendsMap;
  8024.         $this->extends = [];
  8025.         $this->extendsMap = [];
  8026.         foreach ($extendee as $es) {
  8027.             if (\count($es) !== 1) {
  8028.                 throw $this->error('Can\'t extend complex selector.');
  8029.             }
  8030.             // only use the first one
  8031.             $this->pushExtends(reset($es), $extendernull);
  8032.         }
  8033.         $extended = [];
  8034.         foreach ($selectors as $selector) {
  8035.             if (! $replace) {
  8036.                 $extended[] = $selector;
  8037.             }
  8038.             $n = \count($extended);
  8039.             $this->matchExtends($selector$extended);
  8040.             // if didnt match, keep the original selector if we are in a replace operation
  8041.             if ($replace && \count($extended) === $n) {
  8042.                 $extended[] = $selector;
  8043.             }
  8044.         }
  8045.         $this->extends $saveExtends;
  8046.         $this->extendsMap $saveExtendsMap;
  8047.         return $extended;
  8048.     }
  8049.     protected static $libSelectorNest = ['selector...'];
  8050.     protected function libSelectorNest($args)
  8051.     {
  8052.         // get the selector... list
  8053.         $args reset($args);
  8054.         $args $args[2];
  8055.         if (\count($args) < 1) {
  8056.             throw $this->error('selector-nest() needs at least 1 argument');
  8057.         }
  8058.         $selectorsMap = [];
  8059.         foreach ($args as $arg) {
  8060.             $selectorsMap[] = $this->getSelectorArg($arg'selector'true);
  8061.         }
  8062.         $envs = [];
  8063.         foreach ($selectorsMap as $selectors) {
  8064.             $env = new Environment();
  8065.             $env->selectors $selectors;
  8066.             $envs[] = $env;
  8067.         }
  8068.         $envs            array_reverse($envs);
  8069.         $env             $this->extractEnv($envs);
  8070.         $outputSelectors $this->multiplySelectors($env);
  8071.         return $this->formatOutputSelector($outputSelectors);
  8072.     }
  8073.     protected static $libSelectorParse = [
  8074.         ['selector'],
  8075.         ['selectors']
  8076.     ];
  8077.     protected function libSelectorParse($args)
  8078.     {
  8079.         $selectors reset($args);
  8080.         $selectors $this->getSelectorArg($selectors'selector');
  8081.         return $this->formatOutputSelector($selectors);
  8082.     }
  8083.     protected static $libSelectorUnify = ['selectors1''selectors2'];
  8084.     protected function libSelectorUnify($args)
  8085.     {
  8086.         list($selectors1$selectors2) = $args;
  8087.         $selectors1 $this->getSelectorArg($selectors1'selectors1');
  8088.         $selectors2 $this->getSelectorArg($selectors2'selectors2');
  8089.         if (! $selectors1 || ! $selectors2) {
  8090.             throw $this->error('selector-unify() invalid arguments');
  8091.         }
  8092.         // only consider the first compound of each
  8093.         $compound1 reset($selectors1);
  8094.         $compound2 reset($selectors2);
  8095.         // unify them and that's it
  8096.         $unified $this->unifyCompoundSelectors($compound1$compound2);
  8097.         return $this->formatOutputSelector($unified);
  8098.     }
  8099.     /**
  8100.      * The selector-unify magic as its best
  8101.      * (at least works as expected on test cases)
  8102.      *
  8103.      * @param array $compound1
  8104.      * @param array $compound2
  8105.      *
  8106.      * @return array
  8107.      */
  8108.     protected function unifyCompoundSelectors($compound1$compound2)
  8109.     {
  8110.         if (! \count($compound1)) {
  8111.             return $compound2;
  8112.         }
  8113.         if (! \count($compound2)) {
  8114.             return $compound1;
  8115.         }
  8116.         // check that last part are compatible
  8117.         $lastPart1 array_pop($compound1);
  8118.         $lastPart2 array_pop($compound2);
  8119.         $last      $this->mergeParts($lastPart1$lastPart2);
  8120.         if (! $last) {
  8121.             return [[]];
  8122.         }
  8123.         $unifiedCompound = [$last];
  8124.         $unifiedSelectors = [$unifiedCompound];
  8125.         // do the rest
  8126.         while (\count($compound1) || \count($compound2)) {
  8127.             $part1 end($compound1);
  8128.             $part2 end($compound2);
  8129.             if ($part1 && ($match2 $this->matchPartInCompound($part1$compound2))) {
  8130.                 list($compound2$part2$after2) = $match2;
  8131.                 if ($after2) {
  8132.                     $unifiedSelectors $this->prependSelectors($unifiedSelectors$after2);
  8133.                 }
  8134.                 $c $this->mergeParts($part1$part2);
  8135.                 $unifiedSelectors $this->prependSelectors($unifiedSelectors, [$c]);
  8136.                 $part1 $part2 null;
  8137.                 array_pop($compound1);
  8138.             }
  8139.             if ($part2 && ($match1 $this->matchPartInCompound($part2$compound1))) {
  8140.                 list($compound1$part1$after1) = $match1;
  8141.                 if ($after1) {
  8142.                     $unifiedSelectors $this->prependSelectors($unifiedSelectors$after1);
  8143.                 }
  8144.                 $c $this->mergeParts($part2$part1);
  8145.                 $unifiedSelectors $this->prependSelectors($unifiedSelectors, [$c]);
  8146.                 $part1 $part2 null;
  8147.                 array_pop($compound2);
  8148.             }
  8149.             $new = [];
  8150.             if ($part1 && $part2) {
  8151.                 array_pop($compound1);
  8152.                 array_pop($compound2);
  8153.                 $s   $this->prependSelectors($unifiedSelectors, [$part2]);
  8154.                 $new array_merge($new$this->prependSelectors($s, [$part1]));
  8155.                 $s   $this->prependSelectors($unifiedSelectors, [$part1]);
  8156.                 $new array_merge($new$this->prependSelectors($s, [$part2]));
  8157.             } elseif ($part1) {
  8158.                 array_pop($compound1);
  8159.                 $new array_merge($new$this->prependSelectors($unifiedSelectors, [$part1]));
  8160.             } elseif ($part2) {
  8161.                 array_pop($compound2);
  8162.                 $new array_merge($new$this->prependSelectors($unifiedSelectors, [$part2]));
  8163.             }
  8164.             if ($new) {
  8165.                 $unifiedSelectors $new;
  8166.             }
  8167.         }
  8168.         return $unifiedSelectors;
  8169.     }
  8170.     /**
  8171.      * Prepend each selector from $selectors with $parts
  8172.      *
  8173.      * @param array $selectors
  8174.      * @param array $parts
  8175.      *
  8176.      * @return array
  8177.      */
  8178.     protected function prependSelectors($selectors$parts)
  8179.     {
  8180.         $new = [];
  8181.         foreach ($selectors as $compoundSelector) {
  8182.             array_unshift($compoundSelector$parts);
  8183.             $new[] = $compoundSelector;
  8184.         }
  8185.         return $new;
  8186.     }
  8187.     /**
  8188.      * Try to find a matching part in a compound:
  8189.      * - with same html tag name
  8190.      * - with some class or id or something in common
  8191.      *
  8192.      * @param array $part
  8193.      * @param array $compound
  8194.      *
  8195.      * @return array|false
  8196.      */
  8197.     protected function matchPartInCompound($part$compound)
  8198.     {
  8199.         $partTag $this->findTagName($part);
  8200.         $before  $compound;
  8201.         $after   = [];
  8202.         // try to find a match by tag name first
  8203.         while (\count($before)) {
  8204.             $p array_pop($before);
  8205.             if ($partTag && $partTag !== '*' && $partTag == $this->findTagName($p)) {
  8206.                 return [$before$p$after];
  8207.             }
  8208.             $after[] = $p;
  8209.         }
  8210.         // try again matching a non empty intersection and a compatible tagname
  8211.         $before $compound;
  8212.         $after = [];
  8213.         while (\count($before)) {
  8214.             $p array_pop($before);
  8215.             if ($this->checkCompatibleTags($partTag$this->findTagName($p))) {
  8216.                 if (\count(array_intersect($part$p))) {
  8217.                     return [$before$p$after];
  8218.                 }
  8219.             }
  8220.             $after[] = $p;
  8221.         }
  8222.         return false;
  8223.     }
  8224.     /**
  8225.      * Merge two part list taking care that
  8226.      * - the html tag is coming first - if any
  8227.      * - the :something are coming last
  8228.      *
  8229.      * @param array $parts1
  8230.      * @param array $parts2
  8231.      *
  8232.      * @return array
  8233.      */
  8234.     protected function mergeParts($parts1$parts2)
  8235.     {
  8236.         $tag1 $this->findTagName($parts1);
  8237.         $tag2 $this->findTagName($parts2);
  8238.         $tag  $this->checkCompatibleTags($tag1$tag2);
  8239.         // not compatible tags
  8240.         if ($tag === false) {
  8241.             return [];
  8242.         }
  8243.         if ($tag) {
  8244.             if ($tag1) {
  8245.                 $parts1 array_diff($parts1, [$tag1]);
  8246.             }
  8247.             if ($tag2) {
  8248.                 $parts2 array_diff($parts2, [$tag2]);
  8249.             }
  8250.         }
  8251.         $mergedParts array_merge($parts1$parts2);
  8252.         $mergedOrderedParts = [];
  8253.         foreach ($mergedParts as $part) {
  8254.             if (strpos($part':') === 0) {
  8255.                 $mergedOrderedParts[] = $part;
  8256.             }
  8257.         }
  8258.         $mergedParts array_diff($mergedParts$mergedOrderedParts);
  8259.         $mergedParts array_merge($mergedParts$mergedOrderedParts);
  8260.         if ($tag) {
  8261.             array_unshift($mergedParts$tag);
  8262.         }
  8263.         return $mergedParts;
  8264.     }
  8265.     /**
  8266.      * Check the compatibility between two tag names:
  8267.      * if both are defined they should be identical or one has to be '*'
  8268.      *
  8269.      * @param string $tag1
  8270.      * @param string $tag2
  8271.      *
  8272.      * @return array|false
  8273.      */
  8274.     protected function checkCompatibleTags($tag1$tag2)
  8275.     {
  8276.         $tags = [$tag1$tag2];
  8277.         $tags array_unique($tags);
  8278.         $tags array_filter($tags);
  8279.         if (\count($tags) > 1) {
  8280.             $tags array_diff($tags, ['*']);
  8281.         }
  8282.         // not compatible nodes
  8283.         if (\count($tags) > 1) {
  8284.             return false;
  8285.         }
  8286.         return $tags;
  8287.     }
  8288.     /**
  8289.      * Find the html tag name in a selector parts list
  8290.      *
  8291.      * @param string[] $parts
  8292.      *
  8293.      * @return string
  8294.      */
  8295.     protected function findTagName($parts)
  8296.     {
  8297.         foreach ($parts as $part) {
  8298.             if (! preg_match('/^[\[.:#%_-]/'$part)) {
  8299.                 return $part;
  8300.             }
  8301.         }
  8302.         return '';
  8303.     }
  8304.     protected static $libSimpleSelectors = ['selector'];
  8305.     protected function libSimpleSelectors($args)
  8306.     {
  8307.         $selector reset($args);
  8308.         $selector $this->getSelectorArg($selector'selector');
  8309.         // remove selectors list layer, keeping the first one
  8310.         $selector reset($selector);
  8311.         // remove parts list layer, keeping the first part
  8312.         $part reset($selector);
  8313.         $listParts = [];
  8314.         foreach ($part as $p) {
  8315.             $listParts[] = [Type::T_STRING'', [$p]];
  8316.         }
  8317.         return [Type::T_LIST','$listParts];
  8318.     }
  8319.     protected static $libScssphpGlob = ['pattern'];
  8320.     protected function libScssphpGlob($args)
  8321.     {
  8322.         @trigger_error(sprintf('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0. Register your own alternative through "%s::registerFunction'__CLASS__), E_USER_DEPRECATED);
  8323.         $this->logger->warn('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0.'true);
  8324.         $string $this->assertString($args[0], 'pattern');
  8325.         $pattern $this->compileStringContent($string);
  8326.         $matches glob($pattern);
  8327.         $listParts = [];
  8328.         foreach ($matches as $match) {
  8329.             if (! is_file($match)) {
  8330.                 continue;
  8331.             }
  8332.             $listParts[] = [Type::T_STRING'"', [$match]];
  8333.         }
  8334.         return [Type::T_LIST','$listParts];
  8335.     }
  8336. }