예제 #1
0
 /**
  * Default function
  *
  * @return KeywordNode|null
  * @throws Exception
  */
 public function defaultFunc()
 {
     return DefaultFunc::compile();
 }
예제 #2
0
 /**
  * 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;
 }
예제 #3
0
 /**
  * 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);
     }
 }