Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
41 / 41
Wikimedia\CSS\Grammar\UnorderedGroup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
13
100.00% covered (success)
100.00%
41 / 41
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 allOf
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 someOf
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 generateMatches
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
35 / 35
<?php
/**
 * @file
 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
 */
namespace Wikimedia\CSS\Grammar;
use Wikimedia\CSS\Objects\ComponentValueList;
use Wikimedia\CSS\Util;
/**
 * Matcher that groups other matchers without ordering ("&&" and "||" combiners)
 * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#component-combinators
 */
class UnorderedGroup extends Matcher {
    /** @var Matcher[] */
    protected $matchers;
    /** @var bool Whether all matchers must be used */
    protected $all;
    /**
     * @param Matcher[] $matchers
     * @param bool $all Whether all matchers must be used
     */
    public function __construct( array $matchers, $all ) {
        Util::assertAllInstanceOf( $matchers, Matcher::class, '$matchers' );
        $this->matchers = $matchers;
        $this->all = (bool)$all;
    }
    /**
     * Implements "&&": All of the options, in any order
     * @param Matcher[] $matchers
     * @return static
     */
    public static function allOf( array $matchers ) {
        return new static( $matchers, true );
    }
    /**
     * Implements "||": One or more of the options, in any order
     * @param Matcher[] $matchers
     * @return static
     */
    public static function someOf( array $matchers ) {
        return new static( $matchers, false );
    }
    /** @inheritDoc */
    protected function generateMatches( ComponentValueList $values, $start, array $options ) {
        $used = [];
        // As each Matcher is used, push it onto the stack along with the set
        // of remaining matchers.
        $stack = [
            [
                new Match( $values, $start, 0 ),
                $this->matchers,
                new \ArrayIterator( $this->matchers ),
                null,
                new \EmptyIterator
            ]
        ];
        do {
            /** @var $lastMatch Match */
            /** @var $matchers Matcher[] */
            /** @var $matcherIter \Iterator<Matcher> */
            /** @var $curMatcher Matcher|null */
            /** @var $iter \Iterator<Match> */
            list( $lastMatch, $matchers, $matcherIter, $curMatcher, $iter ) = $stack[count( $stack ) - 1];
            // If the top of the stack has more matches, process the next one.
            if ( $iter->valid() ) {
                $match = $iter->current();
                $iter->next();
                // If we have unused matchers to try after this one, do so.
                // Otherwise yield and continue with the current one.
                if ( $matchers ) {
                    $stack[] = [ $match, $matchers, new \ArrayIterator( $matchers ), null, new \EmptyIterator ];
                } else {
                    $newMatch = $this->makeMatch( $values, $start, $match->getNext(), $match, $stack );
                    $mid = $newMatch->getUniqueID();
                    if ( !isset( $used[$mid] ) ) {
                        $used[$mid] = 1;
                        yield $newMatch;
                    }
                }
                continue;
            }
            // We ran out of matches for the current top of the stack. Pop it,
            // and put $curMatcher back into $matchers so it can be tried again
            // at a later position.
            array_pop( $stack );
            if ( $curMatcher ) {
                $matchers[$matcherIter->key()] = $curMatcher;
                $matcherIter->next();
            }
            $fromPos = $lastMatch->getNext();
            // If there are more matchers to try, pull the next one out of
            // $matchers and try it at the current position. Otherwise, maybe
            // yield the current position and backtrack.
            if ( $matcherIter->valid() ) {
                $curMatcher = $matcherIter->current();
                unset( $matchers[$matcherIter->key()] );
                $iter = $curMatcher->generateMatches( $values, $fromPos, $options );
                $stack[] = [ $lastMatch, $matchers, $matcherIter, $curMatcher, $iter ];
            } else {
                if ( $stack && !$this->all ) {
                    $newMatch = $this->makeMatch( $values, $start, $fromPos, $lastMatch, $stack );
                    $mid = $newMatch->getUniqueID();
                    if ( !isset( $used[$mid] ) ) {
                        $used[$mid] = 1;
                        yield $newMatch;
                    }
                }
            }
        } while ( $stack );
    }
}