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

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