/** * Default function * * @return KeywordNode|null * @throws Exception */ public function defaultFunc() { return DefaultFunc::compile(); }
/** * Compiles the node. * * @param Context $context The context * @param array|null $arguments Array of arguments * @param bool|null $important Important flag * * @throws ParserException * * @return RulesetNode */ public function compile(Context $context, $arguments = null, $important = null) { // compile selectors $selectors = []; $hasOnePassingSelector = false; if ($count = count($this->selectors)) { DefaultFunc::error(new ParserException('it is currently only allowed in parametric mixin guards')); for ($i = 0; $i < $count; ++$i) { $selector = $this->selectors[$i]->compile($context); /* @var $selector SelectorNode */ $selectors[] = $selector; if ($selector->compiledCondition) { $hasOnePassingSelector = true; } } DefaultFunc::reset(); } else { $hasOnePassingSelector = true; } $ruleset = new self($selectors, $this->rules, $this->strictImports); $ruleset->originalRuleset = $this; $ruleset->root = $this->root; $ruleset->firstRoot = $this->firstRoot; $ruleset->allowImports = $this->allowImports; if ($this->debugInfo) { $ruleset->debugInfo = $this->debugInfo; } if (!$hasOnePassingSelector) { $ruleset->rules = []; } // inherit a function registry from the frames stack when possible; // otherwise from the global registry $found = null; foreach ($context->frames as $i => $frame) { if ($frame->functionRegistry) { $found = $frame->functionRegistry; break; } } $registry = $found ? $found : $context->getFunctionRegistry(); $ruleset->functionRegistry = $registry->inherit(); // push the current ruleset to the frames stack $context->unshiftFrame($ruleset); // current selectors array_unshift($context->selectors, $this->selectors); // Evaluate imports if ($ruleset->root || $ruleset->allowImports || !$ruleset->strictImports) { $ruleset->compileImports($context); } // count after compile imports was called $rulesetCount = count($ruleset->rules); // Store the frames around mixin definitions, // so they can be evaluated like closures when the time comes. foreach ($ruleset->rules as $i => $rule) { /* @var $rule RuleNode */ if ($rule && $rule->compileFirst()) { $ruleset->rules[$i] = $rule->compile($context); } } $mediaBlockCount = count($context->mediaBlocks); // Evaluate mixin calls. for ($i = 0; $i < $rulesetCount; ++$i) { $rule = $ruleset->rules[$i]; if ($rule instanceof MixinCallNode) { $rule = $rule->compile($context); $temp = []; foreach ($rule as $r) { if ($r instanceof RuleNode && $r->variable) { // do not pollute the scope if the variable is // already there. consider returning false here // but we need a way to "return" variable from mixins if (!$ruleset->variable($r->name)) { $temp[] = $r; } } else { $temp[] = $r; } } $tempCount = count($temp) - 1; array_splice($ruleset->rules, $i, 1, $temp); $rulesetCount += $tempCount; $i += $tempCount; $ruleset->resetCache(); } elseif ($rule instanceof RulesetCallNode) { $rule = $rule->compile($context); $rules = []; foreach ($rule->rules as $r) { if ($r instanceof RuleNode && $r->variable) { continue; } $rules[] = $r; } array_splice($ruleset->rules, $i, 1, $rules); $tempCount = count($rules); $rulesetCount += $tempCount - 1; $i += $tempCount - 1; $ruleset->resetCache(); } } // Evaluate everything else for ($i = 0; $i < count($ruleset->rules); ++$i) { $rule = $ruleset->rules[$i]; /* @var $rule Node */ if ($rule && !$rule->compileFirst()) { $ruleset->rules[$i] = $rule instanceof CompilableInterface ? $rule->compile($context) : $rule; } } // Evaluate everything else for ($i = 0; $i < count($ruleset->rules); ++$i) { $rule = $ruleset->rules[$i]; // for rulesets, check if it is a css guard and can be removed if ($rule instanceof self && count($rule->selectors) === 1) { // check if it can be folded in (e.g. & where) if ($rule->selectors[0]->isJustParentSelector()) { array_splice($ruleset->rules, $i--, 1); for ($j = 0; $j < count($rule->rules); ++$j) { $subRule = $rule->rules[$j]; if (!$subRule instanceof RuleNode || !$subRule->variable) { array_splice($ruleset->rules, ++$i, 0, [$subRule]); } } } } } // Pop the stack $context->shiftFrame(); array_shift($context->selectors); if ($mediaBlockCount) { for ($i = $mediaBlockCount, $count = count($context->mediaBlocks); $i < $count; ++$i) { $context->mediaBlocks[$i]->bubbleSelectors($selectors); } } return $ruleset; }
/** * Compiles the node. * * @param Context $context The context * @param array|null $arguments Array of arguments * @param bool|null $important Important flag * * @return Node * * @throws */ public function compile(Context $context, $arguments = null, $important = null) { $rules = []; $match = false; $isOneFound = false; $candidates = []; $conditionResult = []; $args = []; foreach ($this->arguments as $a) { $aValue = $a['value']->compile($context); if ($a['expand'] && is_array($aValue->value)) { $aValue = $aValue->value; for ($m = 0; $m < count($aValue); ++$m) { $args[] = ['value' => $aValue[$m]]; } } else { $args[] = ['name' => $a['name'], 'value' => $aValue]; } } /* * @param $rule * @return bool */ $noArgumentsFilter = function ($rule) use($context) { /* @var $rule RulesetNode */ return $rule->matchArgs([], $context); }; // return values for the function $defFalseEitherCase = -1; $defNone = 0; $defTrue = 1; $defFalse = 2; /* * Calculate * @param $mixin * @param $mixinPath * @return int */ $calcDefGroup = function ($mixin, $mixinPath) use($context, $args, $defTrue, $defFalse, $defNone, $defFalseEitherCase, &$conditionResult) { $namespace = null; for ($f = 0; $f < 2; ++$f) { $conditionResult[$f] = true; DefaultFunc::value($f); for ($p = 0; $p < count($mixinPath) && $conditionResult[$f]; ++$p) { $namespace = $mixinPath[$p]; if ($namespace instanceof ConditionMatchableInterface) { $conditionResult[$f] = $conditionResult[$f] && $namespace->matchCondition([], $context); } } if ($mixin instanceof ConditionMatchableInterface) { $conditionResult[$f] = $conditionResult[$f] && $mixin->matchCondition($args, $context); } } if ($conditionResult[0] || $conditionResult[1]) { if ($conditionResult[0] != $conditionResult[1]) { return $conditionResult[1] ? $defTrue : $defFalse; } return $defNone; } return $defFalseEitherCase; }; foreach ($context->frames as $frame) { /* @var $frame RulesetNode */ $mixins = $frame->find($this->selector, $context, null, $noArgumentsFilter); if ($mixins) { $isOneFound = true; for ($m = 0; $m < count($mixins); ++$m) { $mixin = $mixins[$m]['rule']; $mixinPath = $mixins[$m]['path']; $isRecursive = false; foreach ($context->frames as $recurFrame) { if (!$mixin instanceof MixinDefinitionNode && ($mixin === $recurFrame->originalRuleset || $mixin === $recurFrame)) { $isRecursive = true; break; } } if ($isRecursive) { continue; } if ($mixin->matchArgs($args, $context)) { $candidate = ['mixin' => $mixin, 'group' => $calcDefGroup($mixin, $mixinPath)]; if ($candidate['group'] !== $defFalseEitherCase) { $candidates[] = $candidate; } $match = true; } } DefaultFunc::reset(); $count = [0, 0, 0]; for ($m = 0; $m < count($candidates); ++$m) { ++$count[$candidates[$m]['group']]; } if ($count[$defNone] > 0) { $defaultResult = $defFalse; } else { $defaultResult = $defTrue; if ($count[$defTrue] + $count[$defFalse] > 1) { throw new ParserException(sprintf('Ambiguous use of `default()` found when matching for `%s`', $this->formatArgs($args)), $this->index, $this->currentFileInfo); } } for ($m = 0; $m < count($candidates); ++$m) { $candidate = $candidates[$m]['group']; if ($candidate === $defNone || $candidate === $defaultResult) { try { $mixin = $candidates[$m]['mixin']; if (!$mixin instanceof MixinDefinitionNode) { $originalRuleset = $mixin->originalRuleset ? $mixin->originalRuleset : $mixin; $mixin = new MixinDefinitionNode('', [], $mixin->rules, null, false); $mixin->originalRuleset = $originalRuleset; } $compiled = $mixin->compileCall($context, $args, $this->important); $rules = array_merge($rules, $compiled->rules); } catch (Exception $e) { throw new CompilerException($e->getMessage(), $this->index, $this->currentFileInfo, $e); } } } if ($match) { if (!$this->currentFileInfo || !$this->currentFileInfo->reference) { foreach ($rules as $rule) { if ($rule instanceof MarkableAsReferencedInterface) { $rule->markReferenced(); } } } return $rules; } } } if ($isOneFound) { throw new CompilerException(sprintf('No matching definition was found for `%s`', $this->formatArgs($args)), $this->index, $this->currentFileInfo); } else { throw new CompilerException(sprintf('%s is undefined', trim($this->selector->toCSS($context))), $this->index, $this->currentFileInfo); } }