private function parsePunctuation(TokenStream $tokens, AstNode &$currNode, bool $parsingPath): void
    {
        if (($token = $tokens->getCurrent()) === null) {
            return;
        }

        switch ($token->value) {
            case '/':
                if (!$parsingPath) {
                    throw new InvalidArgumentException("Unexpected {$token->type} \"{$token->value}\" in host");
                }

                $currNode->addChild(new AstNode(AstNodeTypes::SEGMENT_DELIMITER, $token->value));
                $tokens->next();
                break;
            case '.':
                // Periods in paths are to be treated as just text
                $nodeType = $parsingPath ? AstNodeTypes::TEXT : AstNodeTypes::SEGMENT_DELIMITER;
                $currNode->addChild(new AstNode($nodeType, $token->value));
                $tokens->next();
                break;
            case '[':
                $parentNodeType = $currNode->type;

                if (
                    $parentNodeType !== AstNodeTypes::HOST
                    && $parentNodeType !== AstNodeTypes::PATH
                    && $parentNodeType !== AstNodeTypes::OPTIONAL_ROUTE_PART
                ) {
                    throw new InvalidArgumentException("Unexpected {$token->type} \"{$token->value}\"");
                }

                $optionalRoutePartNode = new AstNode(AstNodeTypes::OPTIONAL_ROUTE_PART, $token->value);
                $currNode->addChild($optionalRoutePartNode);
                $currNode = $optionalRoutePartNode;
                $tokens->next();

                if ($parsingPath) {
                    $tokens->expect(
                        TokenTypes::T_PUNCTUATION,
                        '/',
                        'Expected optional path part to start with \'/\', got %s'
                    );
                }

                break;
            case ']':
                if ($currNode->type !== AstNodeTypes::OPTIONAL_ROUTE_PART) {
                    // Just treat this as normal text
                    $currNode->addChild(new AstNode(AstNodeTypes::TEXT, $token->value));
                    $tokens->next();
                    break;
                }

                if (!$parsingPath) {
                    /**
                     * Optional parts in hosts must end with '.', eg [foo.[bar.]]example.com
                     * So, make sure that the previous non-optional route part ends with '.'
                     */
                    $isValid = false;

                    for ($i = \count($currNode->children) - 1;$i >= 0;$i--) {
                        $childNode = $currNode->children[$i];

                        if ($childNode->type !== AstNodeTypes::OPTIONAL_ROUTE_PART) {
                            if ($childNode->type === AstNodeTypes::SEGMENT_DELIMITER) {
                                $isValid = true;
                            }

                            break;
                        }
                    }

                    if (!$isValid) {
                        throw new InvalidArgumentException('Expected optional host part to end with \'.\'');
                    }
                }

                // End this optional route part
                $currNode = $currNode->parent;
                $tokens->next();
                break;
            default:
                // Since we handle punctuation inside of variables elsewhere, we'll just treat this as text
                $currNode->addChild(new AstNode(AstNodeTypes::TEXT, $token->value));
                $tokens->next();
                break;
        }
    }
private function parsePunctuation(TokenStream $tokens, AstNode &$currNode, bool $parsingPath): void
    {
        if (($token = $tokens->getCurrent()) === null) {
            return;
        }

        switch ($token->value) {
            case '/':
                if (!$parsingPath) {
                    throw new InvalidArgumentException("Unexpected {$token->type} \"{$token->value}\" in host");
                }

                $currNode->addChild(new AstNode(AstNodeTypes::SEGMENT_DELIMITER, $token->value));
                $tokens->next();
                break;
            case '.':
                // Periods in paths are to be treated as just text
                $nodeType = $parsingPath ? AstNodeTypes::TEXT : AstNodeTypes::SEGMENT_DELIMITER;
                $currNode->addChild(new AstNode($nodeType, $token->value));
                $tokens->next();
                break;
            case '[':
                $parentNodeType = $currNode->type;

                if (
                    $parentNodeType !== AstNodeTypes::HOST
                    && $parentNodeType !== AstNodeTypes::PATH
                    && $parentNodeType !== AstNodeTypes::OPTIONAL_ROUTE_PART
                ) {
                    throw new InvalidArgumentException("Unexpected {$token->type} \"{$token->value}\"");
                }

                $optionalRoutePartNode = new AstNode(AstNodeTypes::OPTIONAL_ROUTE_PART, $token->value);
                $currNode->addChild($optionalRoutePartNode);
                $currNode = $optionalRoutePartNode;
                $tokens->next();

                if ($parsingPath) {
                    $tokens->expect(
                        TokenTypes::T_PUNCTUATION,
                        '/',
                        'Expected optional path part to start with \'/\', got %s'
                    );
                }

                break;
            case ']':
                if ($currNode->type !== AstNodeTypes::OPTIONAL_ROUTE_PART) {
                    // Just treat this as normal text
                    $currNode->addChild(new AstNode(AstNodeTypes::TEXT, $token->value));
                    $tokens->next();
                    break;
                }

                if (!$parsingPath) {
                    /**
                     * Optional parts in hosts must end with '.', eg [foo.[bar.]]example.com
                     * So, make sure that the previous non-optional route part ends with '.'
                     */
                    $isValid = false;

                    for ($i = \count($currNode->children) - 1;$i >= 0;$i--) {
                        $childNode = $currNode->children[$i];

                        if ($childNode->type !== AstNodeTypes::OPTIONAL_ROUTE_PART) {
                            if ($childNode->type === AstNodeTypes::SEGMENT_DELIMITER) {
                                $isValid = true;
                            }

                            break;
                        }
                    }

                    if (!$isValid) {
                        throw new InvalidArgumentException('Expected optional host part to end with \'.\'');
                    }
                }

                // End this optional route part
                $currNode = $currNode->parent;
                $tokens->next();
                break;
            default:
                // Since we handle punctuation inside of variables elsewhere, we'll just treat this as text
                $currNode->addChild(new AstNode(AstNodeTypes::TEXT, $token->value));
                $tokens->next();
                break;
        }
    }
private function parsePunctuation ( TokenStream $ tokens , AstNode & $ currNode , bool $ parsingPath ) : void { if ( ( $ token = $ tokens -> getCurrent ( ) ) === null ) { return ; } switch ( $ token -> value ) { case '/' : if ( ! $ parsingPath ) { throw new InvalidArgumentException ( "Unexpected {$token->type} \"{$token->value}\" in host" ) ; } $ currNode -> addChild ( new AstNode ( AstNodeTypes :: SEGMENT_DELIMITER , $ token -> value ) ) ; $ tokens -> next ( ) ; break ; case '.' : // Periods in paths are to be treated as just text $ nodeType = $ parsingPath ? AstNodeTypes :: TEXT : AstNodeTypes :: SEGMENT_DELIMITER ; $ currNode -> addChild ( new AstNode ( $ nodeType , $ token -> value ) ) ; $ tokens -> next ( ) ; break ; case '[' : $ parentNodeType = $ currNode -> type ; if ( $ parentNodeType !== AstNodeTypes :: HOST && $ parentNodeType !== AstNodeTypes :: PATH && $ parentNodeType !== AstNodeTypes :: OPTIONAL_ROUTE_PART ) { throw new InvalidArgumentException ( "Unexpected {$token->type} \"{$token->value}\"" ) ; } $ optionalRoutePartNode = new AstNode ( AstNodeTypes :: OPTIONAL_ROUTE_PART , $ token -> value ) ; $ currNode -> addChild ( $ optionalRoutePartNode ) ; $ currNode = $ optionalRoutePartNode ; $ tokens -> next ( ) ; if ( $ parsingPath ) { $ tokens -> expect ( TokenTypes :: T_PUNCTUATION , '/' , 'Expected optional path part to start with \'/\', got %s' ) ; } break ; case ']' : if ( $ currNode -> type !== AstNodeTypes :: OPTIONAL_ROUTE_PART ) { // Just treat this as normal text $ currNode -> addChild ( new AstNode ( AstNodeTypes :: TEXT , $ token -> value ) ) ; $ tokens -> next ( ) ; break ; } if ( ! $ parsingPath ) { /**
                     * Optional parts in hosts must end with '.', eg [foo.[bar.]]example.com
                     * So, make sure that the previous non-optional route part ends with '.'
                     */ $ isValid = false ; for ( $ i = \ count ( $ currNode -> children ) - 1 ; $ i >= 0 ; $ i -- ) { $ childNode = $ currNode -> children [ $ i ] ; if ( $ childNode -> type !== AstNodeTypes :: OPTIONAL_ROUTE_PART ) { if ( $ childNode -> type === AstNodeTypes :: SEGMENT_DELIMITER ) { $ isValid = true ; } break ; } } if ( ! $ isValid ) { throw new InvalidArgumentException ( 'Expected optional host part to end with \'.\'' ) ; } } // End this optional route part $ currNode = $ currNode -> parent ; $ tokens -> next ( ) ; break ; default : // Since we handle punctuation inside of variables elsewhere, we'll just treat this as text $ currNode -> addChild ( new AstNode ( AstNodeTypes :: TEXT , $ token -> value ) ) ; $ tokens -> next ( ) ; break ; } }
Parses a punctuation token

@param TokenStream $tokens The stream of tokens to parse
@param AstNode $currNode The abstract syntax tree to add nodes to
@param bool $parsingPath Whether or not we're parsing the path (otherwise we're parsing the host)
@throws InvalidArgumentException Thrown if an unexpected punctuation token was found
Parses a punctuation token