Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. JsonParser.php

function JsonParser::parse

@phpstan-param int-mask-of<self::*> $flags

Parameters

string $input JSON string:

int $flags Bitmask of parse/lint options (see constants of this class):

Return value

mixed

Throws

ParsingException

1 call to JsonParser::parse()
JsonParser::lint in vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php
@phpstan-param int-mask-of<self::*> $flags

File

vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php, line 203

Class

JsonParser
Parser class

Namespace

Seld\JsonLint

Code

public function parse($input, $flags = 0) {
    if ($flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY && $flags & self::ALLOW_DUPLICATE_KEYS) {
        throw new \InvalidArgumentException('Only one of ALLOW_DUPLICATE_KEYS and ALLOW_DUPLICATE_KEYS_TO_ARRAY can be used, you passed in both.');
    }
    $this->failOnBOM($input);
    $this->flags = $flags;
    $this->stack = array(
        0,
    );
    $this->vstack = array(
        null,
    );
    $this->lstack = array();
    $yytext = '';
    $yylineno = 0;
    $yyleng = 0;
    
    /** @var int<0,3> */
    $recovering = 0;
    $this->lexer = new Lexer($flags);
    $this->lexer
        ->setInput($input);
    $yyloc = $this->lexer->yylloc;
    $this->lstack[] = $yyloc;
    $symbol = null;
    $preErrorSymbol = null;
    $action = null;
    $a = null;
    $r = null;
    $p = null;
    $len = null;
    $newState = null;
    $expected = null;
    
    /** @var string|null */
    $errStr = null;
    while (true) {
        // retrieve state number from top of stack
        $state = $this->stack[\count($this->stack) - 1];
        // use default actions if available
        if (isset($this->defaultActions[$state])) {
            $action = $this->defaultActions[$state];
        }
        else {
            if ($symbol === null) {
                $symbol = $this->lexer
                    ->lex();
            }
            // read action for current state and first input
            
            /** @var array<int, int>|false */
            $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
        }
        // handle parse error
        if (!$action || !$action[0]) {
            assert(isset($symbol));
            if (!$recovering) {
                // Report error
                $expected = array();
                foreach ($this->table[$state] as $p => $ignore) {
                    if (isset($this->terminals_[$p]) && $p > 2) {
                        $expected[] = "'" . $this->terminals_[$p] . "'";
                    }
                }
                $message = null;
                if (\in_array("'STRING'", $expected) && \in_array(substr($this->lexer->match, 0, 1), array(
                    '"',
                    "'",
                ))) {
                    $message = "Invalid string";
                    if ("'" === substr($this->lexer->match, 0, 1)) {
                        $message .= ", it appears you used single quotes instead of double quotes";
                    }
                    elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer
                        ->getFullUpcomingInput(), $match)) {
                        $message .= ", it appears you have an unescaped backslash at: " . $match[1];
                    }
                    elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer
                        ->getFullUpcomingInput())) {
                        $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid";
                    }
                }
                $errStr = 'Parse error on line ' . ($yylineno + 1) . ":\n";
                $errStr .= $this->lexer
                    ->showPosition() . "\n";
                if ($message) {
                    $errStr .= $message;
                }
                else {
                    $errStr .= \count($expected) > 1 ? "Expected one of: " : "Expected: ";
                    $errStr .= implode(', ', $expected);
                }
                if (',' === substr(trim($this->lexer
                    ->getPastInput()), -1)) {
                    $errStr .= " - It appears you have an extra trailing comma";
                }
                $this->parseError($errStr, array(
                    'text' => $this->lexer->match,
                    'token' => isset($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol,
                    'line' => $this->lexer->yylineno,
                    'loc' => $yyloc,
                    'expected' => $expected,
                ));
            }
            // just recovered from another error
            if ($recovering == 3) {
                if ($symbol === Lexer::EOF) {
                    throw new ParsingException($errStr ?: 'Parsing halted.');
                }
                // discard current lookahead and grab another
                $yyleng = $this->lexer->yyleng;
                $yytext = $this->lexer->yytext;
                $yylineno = $this->lexer->yylineno;
                $yyloc = $this->lexer->yylloc;
                $symbol = $this->lexer
                    ->lex();
            }
            // try to recover from error
            while (true) {
                // check for error recovery rule in this state
                if (\array_key_exists(Lexer::T_ERROR, $this->table[$state])) {
                    break;
                }
                if ($state == 0) {
                    throw new ParsingException($errStr ?: 'Parsing halted.');
                }
                $this->popStack(1);
                $state = $this->stack[\count($this->stack) - 1];
            }
            $preErrorSymbol = $symbol;
            // save the lookahead token
            $symbol = Lexer::T_ERROR;
            // insert generic error symbol as new lookahead
            $state = $this->stack[\count($this->stack) - 1];
            
            /** @var array<int, int>|false */
            $action = isset($this->table[$state][Lexer::T_ERROR]) ? $this->table[$state][Lexer::T_ERROR] : false;
            if ($action === false) {
                throw new \LogicException('No table value found for ' . $state . ' => ' . Lexer::T_ERROR);
            }
            $recovering = 3;
            // allow 3 real symbols to be shifted before reporting a new error
        }
        // this shouldn't happen, unless resolve defaults are off
        if (\is_array($action[0]) && \count($action) > 1) {
            throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol);
        }
        switch ($action[0]) {
            case 1:
                // shift
                assert(isset($symbol));
                $this->stack[] = $symbol;
                $this->vstack[] = $this->lexer->yytext;
                $this->lstack[] = $this->lexer->yylloc;
                $this->stack[] = $action[1];
                // push state
                $symbol = null;
                if (!$preErrorSymbol) {
                    // normal execution/no error
                    $yyleng = $this->lexer->yyleng;
                    $yytext = $this->lexer->yytext;
                    $yylineno = $this->lexer->yylineno;
                    $yyloc = $this->lexer->yylloc;
                    if ($recovering > 0) {
                        $recovering--;
                    }
                }
                else {
                    // error just occurred, resume old lookahead from before error
                    $symbol = $preErrorSymbol;
                    $preErrorSymbol = null;
                }
                break;
            case 2:
                // reduce
                $len = $this->productions_[$action[1]][1];
                // perform semantic action
                $currentToken = $this->vstack[\count($this->vstack) - $len];
                // default to $$ = $1
                // default location, uses first token for firsts, last for lasts
                $position = array(
                    // _$ = store
'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'],
                    'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'],
                    'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'],
                    'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'],
                );
                list($newToken, $actionResult) = $this->performAction($currentToken, $yytext, $yyleng, $yylineno, $action[1]);
                if (!$actionResult instanceof Undefined) {
                    return $actionResult;
                }
                if ($len) {
                    $this->popStack($len);
                }
                $this->stack[] = $this->productions_[$action[1]][0];
                // push nonterminal (reduce)
                $this->vstack[] = $newToken;
                $this->lstack[] = $position;
                
                /** @var int */
                $newState = $this->table[$this->stack[\count($this->stack) - 2]][$this->stack[\count($this->stack) - 1]];
                $this->stack[] = $newState;
                break;
            case 3:
                // accept
                return true;
        }
    }
}

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal