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
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\JsonLintCode
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;
}
}
}