Returns the index of the closing parenthesis that matches the opening
parenthesis at index 0 of the given expression.

@param array $expression
@return int
198495
protected function getGroupNodeClosingIndex(array $expression)
    {
        $parenthesis = 1;
        $index = 0;

        while ($parenthesis > 0) {
            $index++;
            if ($index > count($expression)) {
                $this->addError('Parenthesis not correctly closed.', self::ERROR_CODE_CLOSING_PARENTHESIS_NOT_FOUND);
            }

            if ('(' === $expression[$index]) {
                $parenthesis++;
            } elseif (')' === $expression[$index]) {
                $parenthesis--;
            }
        }

        return $index;
    }
988346
public function match($expression)
    {
        if (is_callable($expression)) {
            $expressionConfigurator = $expression;
            $expression = new Expression();
            call_user_func($expressionConfigurator, $expression);
        }

        if ($expression instanceof Expression) {
            $expression = $expression->toArray();
        } elseif (!is_array($expression)) {
            throw new Exception('Must be array, callable or instance of \Sokil\Mongo\Expression');
        }

        $this->addStage('$match', $expression);
        return $this;
    }
962809
private String getFirstSegment(String expression)
    {
        int indexDot = expression.indexOf('.');
        int indexBracket = expression.indexOf('[');

        if (indexBracket < 0)
        {

            return indexDot < 0 ? expression : expression.substring(0, indexDot);

        }

        if (indexDot < 0)
        {
            return expression.substring(0, indexBracket);
        }

        return expression.substring(0, Math.min(indexDot, indexBracket));

    }
248056
private function ensureArray($expression)
    {
        if (is_array($expression)) {
            $array = array();
            foreach ($expression as $index => $value) {
                $array[$index] = $this->ensureArray($value);
            }

            return $array;
        } elseif ($expression instanceof self) {
            return $expression->getExpression();
        }

        return $expression;
    }
247299
public function passExpression(array $expression)
    {
        switch ($expression['type']) {
            case 'bool':
            case 'double':
            case 'int':
            case 'uint':
            case 'long':
            case 'ulong':
            case 'string':
            case 'istring':
            case 'null':
            case 'char':
            case 'uchar':
            case 'empty-array':
            case 'variable':
            case 'constant':
            case 'static-constant-access':
            case 'closure':
            case 'closure-arrow':
            case 'reference':
                break;

            case 'sub':
            case 'add':
            case 'div':
            case 'mul':
            case 'mod':
            case 'and':
            case 'or':
            case 'concat':
            case 'equals':
            case 'identical':
            case 'not-identical':
            case 'not-equals':
            case 'less':
            case 'greater':
            case 'greater-equal':
            case 'less-equal':
            case 'bitwise_and':
            case 'bitwise_or':
            case 'bitwise_xor':
            case 'bitwise_shiftleft':
            case 'bitwise_shiftright':
            case 'irange':
            case 'erange':
                $this->passExpression($expression['left']);
                $this->passExpression($expression['right']);
                break;

            case 'typeof':
            case 'not':
                $this->passExpression($expression['left']);
                break;

            case 'mcall':
            case 'fcall':
            case 'scall':
                $this->passCall($expression);
                break;

            case 'array':
                $this->passArray($expression);
                break;

            case 'new':
                $this->passNew($expression);
                break;

            case 'property-access':
            case 'property-dynamic-access':
            case 'property-string-access':
            case 'static-property-access':
            case 'array-access':
                $this->passExpression($expression['left']);
                break;

            case 'isset':
            case 'empty':
            case 'instanceof':
            case 'require':
            case 'clone':
            case 'likely':
            case 'unlikely':
            case 'ternary':
                /* do special pass later */
                $this->passExpression($expression['left']);
                break;

            case 'fetch':
                $this->increaseMutations($expression['left']['value']);
                $this->passExpression($expression['right']);
                break;

            case 'minus':
                $this->passExpression($expression['left']);
                break;

            case 'list':
                $this->passExpression($expression['left']);
                break;

            case 'cast':
            case 'type-hint':
                $this->passExpression($expression['right']);
                break;

            default:
                echo 'MutateGathererPassType=', $expression['type'], PHP_EOL;
                break;
        }
    }
494717
private function ensureArray($expression)
    {
        if (is_string($expression) && substr($expression, 0, 1) === '$') {
            return '$' . $this->getDocumentPersister()->prepareFieldName(substr($expression, 1));
        } elseif (is_array($expression)) {
            return array_map([$this, 'ensureArray'], $expression);
        } elseif ($expression instanceof self) {
            return $expression->getExpression();
        }

        // Convert PHP types to MongoDB types for everything else
        return Type::convertPHPToDatabaseValue($expression);
    }
463430
private function Arrayx()
    {
        $array = $values = [];

        $this->match(DocLexer::T_OPEN_CURLY_BRACES);

        // If the array is empty, stop parsing and return.
        if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
            $this->match(DocLexer::T_CLOSE_CURLY_BRACES);

            return $array;
        }

        $values[] = $this->ArrayEntry();

        while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
            $this->match(DocLexer::T_COMMA);

            // optional trailing comma
            if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
                break;
            }

            $values[] = $this->ArrayEntry();
        }

        $this->match(DocLexer::T_CLOSE_CURLY_BRACES);

        foreach ($values as $value) {
            list ($key, $val) = $value;

            if ($key !== null) {
                $array[$key] = $val;
            } else {
                $array[] = $val;
            }
        }

        return $array;
    }
836590
public function evaluate($expression, $values=[]) {
        if(is_array($values)) {
            return parent::evaluate($expression, $values);
        } else {
            return $this->parse($expression, $values->getKeys())->getNodes()->evaluate($this->functions, $values);
        }
    }
285933
public function removeExpression($expression)
    {
        if ($this->expressions->contains($expression)) {
            $this->expressions->removeElement($expression);
        }

        return $this;
    }
127253
public static function parse($expression)
    {
        if (trim($expression) === '') {
            throw new InvalidArgumentException('Console command definition is empty.');
        }

        preg_match('/[^\s]+/', $expression, $matches);

        if (isset($matches[0])) {
            $name = $matches[0];
        } else {
            throw new InvalidArgumentException('Unable to determine command name from signature.');
        }

        preg_match_all('/\{\s*(.*?)\s*\}/', $expression, $matches);

        $tokens = isset($matches[1]) ? $matches[1] : [];

        if (count($tokens)) {
            return array_merge([$name], static::parameters($tokens));
        }

        return [$name, [], []];
    }
765084
public function parse(string $expression): Interval
    {
        $parse = \explode(',', $expression);
        if (self::BOUNDARIES !== count($parse)) {
            throw new \ErrorException('Parse interval expression');
        }

        $startTerm = \reset($parse);
        $endTerm   = \end($parse);

        $startTerm = $this->parseStartTerm($startTerm);
        $endTerm   = $this->parseEndTerm($endTerm);

        return new Interval(
            $startTerm,
            $endTerm
        );
    }
353779
public function find_last_index( $condition ) {
		$index = -1;

		for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) {
			if ( call_user_func( $condition, $this->elements[ $i ] ) ) {
				$index = $i;
				break;
			}
		}

		return $index;
	}
323135
public static function lastIndexOf(array $array, callable $predicate) : int
    {
        for ($index = count($array) - 1; $index >= 0; --$index) {
            if ($predicate($array[$index])) {
                return $index;
            }
        }

        return -1;
    }
644517
protected static function convertNthExpression($expression)
    {
        if ($expression === '') {
            throw new InvalidSelectorException('nth-child (or nth-last-child) expression must not be empty');
        }

        if ($expression === 'odd') {
            return 'position() mod 2 = 1 and position() >= 1';
        }

        if ($expression === 'even') {
            return 'position() mod 2 = 0 and position() >= 0';
        }

        if (is_numeric($expression)) {
            return sprintf('position() = %d', $expression);
        }

        if (preg_match("/^(?P<mul>[0-9]?n)(?:(?P<sign>\+|\-)(?P<pos>[0-9]+))?$/is", $expression, $segments)) {
            if (isset($segments['mul'])) {
                $multiplier = $segments['mul'] === 'n' ? 1 : trim($segments['mul'], 'n');
                $sign = (isset($segments['sign']) && $segments['sign'] === '+') ? '-' : '+';
                $position = isset($segments['pos']) ? $segments['pos'] : 0;

                return sprintf('(position() %s %d) mod %d = 0 and position() >= %d', $sign, $position, $multiplier, $position);
            }
        }

        throw new InvalidSelectorException(sprintf('Invalid nth-child expression "%s"', $expression));
    }
828631
protected function compileIfStack($expression)
    {
        foreach (['UnLess', 'If', 'ElseIf', 'ElseIfAlias', 'Else', 'EndIf'] as $token) {
            $out = $this->{'compile'.$token}($expression);

            if (strlen($out) !== 0) {
                $expression = $out;
            }
        }

        return $expression;
    }
173893
public function getValueForOperand($expression)
    {
        if (is_array($expression)) {
            $result = [];
            foreach ($expression as $expressionEntry) {
                $result[] = $this->getValueForOperand($expressionEntry);
            }
            return $result;
        } elseif (is_numeric($expression)) {
            return $expression;
        } elseif ($expression === true) {
            return true;
        } elseif ($expression === false) {
            return false;
        } elseif ($expression === null) {
            return null;
        } elseif (strpos($expression, 'context.') === 0) {
            $objectAccess = explode('.', $expression, 3);
            $globalObjectsRegisteredClassName = $this->globalObjects[$objectAccess[1]];
            $globalObject = $this->objectManager->get($globalObjectsRegisteredClassName);
            $this->securityContext->withoutAuthorizationChecks(function () use ($globalObject, $objectAccess, &$globalObjectValue) {
                $globalObjectValue = $this->getObjectValueByPath($globalObject, $objectAccess[2]);
            });

            return $globalObjectValue;
        } else {
            return trim($expression, '"\'');
        }
    }
861981
public function orX(array $expressions)
    {
        $expr = $this->createExpr();
        foreach ($expressions as $expression) {
            $expr->addOr($expression);
        }

        return $expr;
    }