Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14105238
D9622.id23080.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
31 KB
Referenced Files
None
Subscribers
None
D9622.id23080.diff
View Options
diff --git a/externals/jsonlint/LICENSE b/externals/jsonlint/LICENSE
new file mode 100644
--- /dev/null
+++ b/externals/jsonlint/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/externals/jsonlint/src/Seld/JsonLint/JsonParser.php b/externals/jsonlint/src/Seld/JsonLint/JsonParser.php
new file mode 100644
--- /dev/null
+++ b/externals/jsonlint/src/Seld/JsonLint/JsonParser.php
@@ -0,0 +1,460 @@
+<?php
+
+/*
+ * This file is part of the JSON Lint package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Parser class
+ *
+ * Example:
+ *
+ * $parser = new JsonParser();
+ * // returns null if it's valid json, or an error object
+ * $parser->lint($json);
+ * // returns parsed json, like json_decode does, but slower, throws exceptions on failure.
+ * $parser->parse($json);
+ *
+ * Ported from https://github.com/zaach/jsonlint
+ */
+class JsonLintJsonParser
+{
+ const DETECT_KEY_CONFLICTS = 1;
+ const ALLOW_DUPLICATE_KEYS = 2;
+
+ private $flags;
+ private $stack;
+ private $vstack; // semantic value stack
+ private $lstack; // location stack
+
+ private $yy;
+ private $symbols = array(
+ 'error' => 2,
+ 'JSONString' => 3,
+ 'STRING' => 4,
+ 'JSONNumber' => 5,
+ 'NUMBER' => 6,
+ 'JSONNullLiteral' => 7,
+ 'NULL' => 8,
+ 'JSONBooleanLiteral' => 9,
+ 'TRUE' => 10,
+ 'FALSE' => 11,
+ 'JSONText' => 12,
+ 'JSONValue' => 13,
+ 'EOF' => 14,
+ 'JSONObject' => 15,
+ 'JSONArray' => 16,
+ '{' => 17,
+ '}' => 18,
+ 'JSONMemberList' => 19,
+ 'JSONMember' => 20,
+ ':' => 21,
+ ',' => 22,
+ '[' => 23,
+ ']' => 24,
+ 'JSONElementList' => 25,
+ '$accept' => 0,
+ '$end' => 1,
+ );
+
+ private $terminals_ = array(
+ 2 => "error",
+ 4 => "STRING",
+ 6 => "NUMBER",
+ 8 => "NULL",
+ 10 => "TRUE",
+ 11 => "FALSE",
+ 14 => "EOF",
+ 17 => "{",
+ 18 => "}",
+ 21 => ":",
+ 22 => ",",
+ 23 => "[",
+ 24 => "]",
+ );
+
+ private $productions_ = array(
+ 0,
+ array(3, 1),
+ array(5, 1),
+ array(7, 1),
+ array(9, 1),
+ array(9, 1),
+ array(12, 2),
+ array(13, 1),
+ array(13, 1),
+ array(13, 1),
+ array(13, 1),
+ array(13, 1),
+ array(13, 1),
+ array(15, 2),
+ array(15, 3),
+ array(20, 3),
+ array(19, 1),
+ array(19, 3),
+ array(16, 2),
+ array(16, 3),
+ array(25, 1),
+ array(25, 3)
+ );
+
+ private $table = array(array(3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 1 => array(3)), array( 14 => array(1,16)), array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)), array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)), array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)), array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)), array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)), array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)), array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)), array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)), array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)), array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)), array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)), array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), array( 1 => array(2,6)), array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)), array( 18 => array(1,24), 22 => array(1,25)), array( 18 => array(2,16), 22 => array(2,16)), array( 21 => array(1,26)), array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)), array( 22 => array(1,28), 24 => array(1,27)), array( 22 => array(2,20), 24 => array(2,20)), array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)), array( 3 => 20, 4 => array(1,12), 20 => 29 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 18 => array(2,17), 22 => array(2,17)), array( 18 => array(2,15), 22 => array(2,15)), array( 22 => array(2,21), 24 => array(2,21)),
+ );
+
+ private $defaultActions = array(
+ 16 => array(2, 6)
+ );
+
+ /**
+ * @param string $input JSON string
+ * @return null|JsonLintParsingException null if no error is found, a JsonLintParsingException containing all details otherwise
+ */
+ public function lint($input)
+ {
+ try {
+ $this->parse($input);
+ } catch (JsonLintParsingException $e) {
+ return $e;
+ }
+ }
+
+ /**
+ * @param string $input JSON string
+ * @return mixed
+ * @throws JsonLintParsingException
+ */
+ public function parse($input, $flags = 0)
+ {
+ $this->failOnBOM($input);
+
+ $this->flags = $flags;
+
+ $this->stack = array(0);
+ $this->vstack = array(null);
+ $this->lstack = array();
+
+ $yytext = '';
+ $yylineno = 0;
+ $yyleng = 0;
+ $recovering = 0;
+ $TERROR = 2;
+ $EOF = 1;
+
+ $this->lexer = new JsonLintLexer();
+ $this->lexer->setInput($input);
+
+ $yyloc = $this->lexer->yylloc;
+ $this->lstack[] = $yyloc;
+
+ $symbol = null;
+ $preErrorSymbol = null;
+ $state = null;
+ $action = null;
+ $a = null;
+ $r = null;
+ $yyval = new stdClass;
+ $p = null;
+ $len = null;
+ $newState = null;
+ $expected = null;
+ $errStr = null;
+
+ while (true) {
+ // retreive 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->lex();
+ }
+ // read action for current state and first input
+ $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
+ }
+
+ // handle parse error
+ if (!$action || !$action[0]) {
+ 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->getUpcomingInput(), $match)) {
+ $message .= ", it appears you have an unescaped backslash at: ".$match[1];
+ } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getUpcomingInput())) {
+ $message .= ", it appears you forgot to terminated the 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' => !empty($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 == $EOF) {
+ throw new JsonLintParsingException($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->lex();
+ }
+
+ // try to recover from error
+ while (true) {
+ // check for error recovery rule in this state
+ if (array_key_exists($TERROR, $this->table[$state])) {
+ break;
+ }
+ if ($state == 0) {
+ throw new JsonLintParsingException($errStr ?: 'Parsing halted.');
+ }
+ $this->popStack(1);
+ $state = $this->stack[count($this->stack)-1];
+ }
+
+ $preErrorSymbol = $symbol; // save the lookahead token
+ $symbol = $TERROR; // insert generic error symbol as new lookahead
+ $state = $this->stack[count($this->stack)-1];
+ $action = isset($this->table[$state][$TERROR]) ? $this->table[$state][$TERROR] : false;
+ $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 JsonLintParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol);
+ }
+
+ switch ($action[0]) {
+ case 1: // shift
+ $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 f/ before error
+ $symbol = $preErrorSymbol;
+ $preErrorSymbol = null;
+ }
+ break;
+
+ case 2: // reduce
+ $len = $this->productions_[$action[1]][1];
+
+ // perform semantic action
+ $yyval->token = $this->vstack[count($this->vstack) - $len]; // default to $$ = $1
+ // default location, uses first token for firsts, last for lasts
+ $yyval->store = 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'],
+ );
+ $r = $this->performAction($yyval, $yytext, $yyleng, $yylineno, $action[1], $this->vstack, $this->lstack);
+
+ if (!$r instanceof JsonLintUndefined) {
+ return $r;
+ }
+
+ if ($len) {
+ $this->popStack($len);
+ }
+
+ $this->stack[] = $this->productions_[$action[1]][0]; // push nonterminal (reduce)
+ $this->vstack[] = $yyval->token;
+ $this->lstack[] = $yyval->store;
+ $newState = $this->table[$this->stack[count($this->stack)-2]][$this->stack[count($this->stack)-1]];
+ $this->stack[] = $newState;
+ break;
+
+ case 3: // accept
+
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ protected function parseError($str, $hash)
+ {
+ throw new JsonLintParsingException($str, $hash);
+ }
+
+ // $$ = $tokens // needs to be passed by ref?
+ // $ = $token
+ // _$ removed, useless?
+ private function performAction(stdClass $yyval, $yytext, $yyleng, $yylineno, $yystate, &$tokens)
+ {
+ // $0 = $len
+ $len = count($tokens) - 1;
+ switch ($yystate) {
+ case 1:
+ $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext);
+ $yyval->token = $yytext;
+ break;
+ case 2:
+ if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) {
+ $yyval->token = floatval($yytext);
+ } else {
+ $yyval->token = strpos($yytext, '.') === false ? intval($yytext) : floatval($yytext);
+ }
+ break;
+ case 3:
+ $yyval->token = null;
+ break;
+ case 4:
+ $yyval->token = true;
+ break;
+ case 5:
+ $yyval->token = false;
+ break;
+ case 6:
+ return $yyval->token = $tokens[$len-1];
+ case 13:
+ $yyval->token = new stdClass;
+ break;
+ case 14:
+ $yyval->token = $tokens[$len-1];
+ break;
+ case 15:
+ $yyval->token = array($tokens[$len-2], $tokens[$len]);
+ break;
+ case 16:
+ $yyval->token = new stdClass;
+ $property = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0];
+ $yyval->token->$property = $tokens[$len][1];
+ break;
+ case 17:
+ $yyval->token = $tokens[$len-2];
+ $key = $tokens[$len][0] === '' ? '_empty_' : $tokens[$len][0];
+ if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2]->{$key})) {
+ $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
+ $errStr .= $this->lexer->showPosition() . "\n";
+ $errStr .= "Duplicate key: ".$tokens[$len][0];
+ throw new JsonLintParsingException($errStr);
+ } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2]->{$key})) {
+ $duplicateCount = 1;
+ do {
+ $duplicateKey = $key . '.' . $duplicateCount++;
+ } while (isset($tokens[$len-2]->$duplicateKey));
+ $key = $duplicateKey;
+ }
+ $tokens[$len-2]->$key = $tokens[$len][1];
+ break;
+ case 18:
+ $yyval->token = array();
+ break;
+ case 19:
+ $yyval->token = $tokens[$len-1];
+ break;
+ case 20:
+ $yyval->token = array($tokens[$len]);
+ break;
+ case 21:
+ $tokens[$len-2][] = $tokens[$len];
+ $yyval->token = $tokens[$len-2];
+ break;
+ }
+
+ return new JsonLintUndefined();
+ }
+
+ private function stringInterpolation($match)
+ {
+ switch ($match[0]) {
+ case '\\\\':
+ return '\\';
+ case '\"':
+ return '"';
+ case '\b':
+ return chr(8);
+ case '\f':
+ return chr(12);
+ case '\n':
+ return "\n";
+ case '\r':
+ return "\r";
+ case '\t':
+ return "\t";
+ case '\/':
+ return "/";
+ default:
+ return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', 0, 'UTF-8');
+ }
+ }
+
+ private function popStack($n)
+ {
+ $this->stack = array_slice($this->stack, 0, - (2 * $n));
+ $this->vstack = array_slice($this->vstack, 0, - $n);
+ $this->lstack = array_slice($this->lstack, 0, - $n);
+ }
+
+ private function lex()
+ {
+ $token = $this->lexer->lex() ?: 1; // $end = 1
+ // if token isn't its numeric value, convert
+ if (!is_numeric($token)) {
+ $token = isset($this->symbols[$token]) ? $this->symbols[$token] : $token;
+ }
+
+ return $token;
+ }
+
+ private function failOnBOM($input)
+ {
+ // UTF-8 ByteOrderMark sequence
+ $bom = "\xEF\xBB\xBF";
+
+ if (substr($input, 0, 3) === $bom) {
+ $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark", array());
+ }
+ }
+}
diff --git a/externals/jsonlint/src/Seld/JsonLint/Lexer.php b/externals/jsonlint/src/Seld/JsonLint/Lexer.php
new file mode 100644
--- /dev/null
+++ b/externals/jsonlint/src/Seld/JsonLint/Lexer.php
@@ -0,0 +1,227 @@
+<?php
+
+/*
+ * This file is part of the JSON Lint package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Lexer class
+ *
+ * Ported from https://github.com/zaach/jsonlint
+ */
+class JsonLintLexer
+{
+ private $EOF = 1;
+ private $rules = array(
+ 0 => '/^\s+/',
+ 1 => '/^-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
+ 2 => '{^"(\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x09\x0a-\x1f\\\\"])*"}',
+ 3 => '/^\{/',
+ 4 => '/^\}/',
+ 5 => '/^\[/',
+ 6 => '/^\]/',
+ 7 => '/^,/',
+ 8 => '/^:/',
+ 9 => '/^true\b/',
+ 10 => '/^false\b/',
+ 11 => '/^null\b/',
+ 12 => '/^$/',
+ 13 => '/^./',
+ );
+
+ private $conditions = array(
+ "INITIAL" => array(
+ "rules" => array(0,1,2,3,4,5,6,7,8,9,10,11,12,13),
+ "inclusive" => true,
+ ),
+ );
+
+ private $conditionStack;
+ private $input;
+ private $more;
+ private $done;
+ private $matched;
+
+ public $match;
+ public $yylineno;
+ public $yyleng;
+ public $yytext;
+ public $yylloc;
+
+ public function lex()
+ {
+ $r = $this->next();
+ if (!$r instanceof JsonLintUndefined) {
+ return $r;
+ }
+
+ return $this->lex();
+ }
+
+ public function setInput($input)
+ {
+ $this->input = $input;
+ $this->more = false;
+ $this->done = false;
+ $this->yylineno = $this->yyleng = 0;
+ $this->yytext = $this->matched = $this->match = '';
+ $this->conditionStack = array('INITIAL');
+ $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0);
+
+ return $this;
+ }
+
+ public function showPosition()
+ {
+ $pre = str_replace("\n", '', $this->getPastInput());
+ $c = str_repeat('-', strlen($pre) - 1); // new Array(pre.length + 1).join("-");
+
+ return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^";
+ }
+
+ public function getPastInput()
+ {
+ $past = substr($this->matched, 0, strlen($this->matched) - strlen($this->match));
+
+ return (strlen($past) > 20 ? '...' : '') . substr($past, -20);
+ }
+
+ public function getUpcomingInput()
+ {
+ $next = $this->match;
+ if (strlen($next) < 20) {
+ $next .= substr($this->input, 0, 20 - strlen($next));
+ }
+
+ return substr($next, 0, 20) . (strlen($next) > 20 ? '...' : '');
+ }
+
+ protected function parseError($str, $hash)
+ {
+ throw new \Exception($str);
+ }
+
+ private function next()
+ {
+ if ($this->done) {
+ return $this->EOF;
+ }
+ if (!$this->input) {
+ $this->done = true;
+ }
+
+ $token = null;
+ $match = null;
+ $col = null;
+ $lines = null;
+
+ if (!$this->more) {
+ $this->yytext = '';
+ $this->match = '';
+ }
+
+ $rules = $this->getCurrentRules();
+ $rulesLen = count($rules);
+
+ for ($i=0; $i < $rulesLen; $i++) {
+ if (preg_match($this->rules[$rules[$i]], $this->input, $match)) {
+ preg_match_all('/\n.*/', $match[0], $lines);
+ $lines = $lines[0];
+ if ($lines) {
+ $this->yylineno += count($lines);
+ }
+
+ $this->yylloc = array(
+ 'first_line' => $this->yylloc['last_line'],
+ 'last_line' => $this->yylineno+1,
+ 'first_column' => $this->yylloc['last_column'],
+ 'last_column' => $lines ? strlen($lines[count($lines) - 1]) - 1 : $this->yylloc['last_column'] + strlen($match[0]),
+ );
+ $this->yytext .= $match[0];
+ $this->match .= $match[0];
+ $this->matches = $match;
+ $this->yyleng = strlen($this->yytext);
+ $this->more = false;
+ $this->input = substr($this->input, strlen($match[0]));
+ $this->matched .= $match[0];
+ $token = $this->performAction($rules[$i], $this->conditionStack[count($this->conditionStack)-1]);
+ if ($token) {
+ return $token;
+ }
+
+ return new JsonLintUndefined();
+ }
+ }
+
+ if ($this->input === "") {
+ return $this->EOF;
+ }
+
+ $this->parseError(
+ 'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition(),
+ array(
+ 'text' => "",
+ 'token' => null,
+ 'line' => $this->yylineno,
+ )
+ );
+ }
+
+ private function begin($condition)
+ {
+ $this->conditionStack[] = $condition;
+ }
+
+ private function popState()
+ {
+ return array_pop($this->conditionStack);
+ }
+
+ private function getCurrentRules()
+ {
+ return $this->conditions[$this->conditionStack[count($this->conditionStack)-1]]['rules'];
+ }
+
+ private function performAction($avoiding_name_collisions, $YY_START)
+ {
+ $YYSTATE = $YY_START;
+ switch ($avoiding_name_collisions) {
+ case 0:/* skip whitespace */
+ break;
+ case 1:
+ return 6;
+ break;
+ case 2:
+ $this->yytext = substr($this->yytext, 1, $this->yyleng-2);
+
+ return 4;
+ case 3:
+ return 17;
+ case 4:
+ return 18;
+ case 5:
+ return 23;
+ case 6:
+ return 24;
+ case 7:
+ return 22;
+ case 8:
+ return 21;
+ case 9:
+ return 10;
+ case 10:
+ return 11;
+ case 11:
+ return 8;
+ case 12:
+ return 14;
+ case 13:
+ return 'INVALID';
+ }
+ }
+}
diff --git a/externals/jsonlint/src/Seld/JsonLint/ParsingException.php b/externals/jsonlint/src/Seld/JsonLint/ParsingException.php
new file mode 100644
--- /dev/null
+++ b/externals/jsonlint/src/Seld/JsonLint/ParsingException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the JSON Lint package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class JsonLintParsingException extends Exception
+{
+ protected $details;
+
+ public function __construct($message, $details = array())
+ {
+ $this->details = $details;
+ parent::__construct($message);
+ }
+
+ public function getDetails()
+ {
+ return $this->details;
+ }
+}
diff --git a/externals/jsonlint/src/Seld/JsonLint/Undefined.php b/externals/jsonlint/src/Seld/JsonLint/Undefined.php
new file mode 100644
--- /dev/null
+++ b/externals/jsonlint/src/Seld/JsonLint/Undefined.php
@@ -0,0 +1,14 @@
+<?php
+
+/*
+ * This file is part of the JSON Lint package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class JsonLintUndefined
+{
+}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -179,6 +179,9 @@
'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php',
'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php',
'PhutilJSON' => 'parser/PhutilJSON.php',
+ 'PhutilJSONParser' => 'parser/PhutilJSONParser.php',
+ 'PhutilJSONParserException' => 'parser/exception/PhutilJSONParserException.php',
+ 'PhutilJSONParserTestCase' => 'parser/__tests__/PhutilJSONParserTestCase.php',
'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php',
'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php',
'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php',
@@ -575,6 +578,8 @@
'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon',
'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow',
'PhutilInfrastructureTestCase' => 'PhutilTestCase',
+ 'PhutilJSONParserException' => 'Exception',
+ 'PhutilJSONParserTestCase' => 'PhutilTestCase',
'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel',
'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase',
'PhutilJSONTestCase' => 'PhutilTestCase',
diff --git a/src/parser/PhutilJSONParser.php b/src/parser/PhutilJSONParser.php
new file mode 100644
--- /dev/null
+++ b/src/parser/PhutilJSONParser.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * WARNING: This is not-quite-usable yet. See T5297.
+ *
+ * @phutil-external-symbol class JsonLintJsonParser
+ * @phutil-external-symbol class JsonLintParsingException
+ */
+final class PhutilJSONParser {
+
+ public function parse($json) {
+ $jsonlint_root = phutil_get_library_root('phutil').'/../externals/jsonlint';
+ require_once($jsonlint_root.'/src/Seld/JsonLint/JsonParser.php');
+ require_once($jsonlint_root.'/src/Seld/JsonLint/Lexer.php');
+ require_once($jsonlint_root.'/src/Seld/JsonLint/ParsingException.php');
+ require_once($jsonlint_root.'/src/Seld/JsonLint/Undefined.php');
+
+ if (!$json) {
+ return null;
+ }
+
+ $parser = new JsonLintJsonParser();
+ try {
+ $output = $parser->parse($json);
+ } catch (JsonLintParsingException $ex) {
+ throw new PhutilJSONParserException($ex->getMessage());
+ }
+
+ if ($output instanceof stdClass) {
+ return (array)$output;
+ }
+
+ return $output;
+ }
+
+}
diff --git a/src/parser/__tests__/PhutilJSONParserTestCase.php b/src/parser/__tests__/PhutilJSONParserTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/parser/__tests__/PhutilJSONParserTestCase.php
@@ -0,0 +1,49 @@
+<?php
+
+final class PhutilJSONParserTestCase extends PhutilTestCase {
+
+ public function testValidJSON() {
+ $parser = new PhutilJSONParser();
+
+ $tests = array(
+ '' => null,
+ 'null' => null,
+ 'false' => false,
+ 'true' => true,
+ '"foo"' => 'foo',
+ '{}' => array(),
+ '{"foo": "bar"}' => array('foo' => 'bar'),
+ '[1, "foo", true, null]' => array(1, 'foo', true, null),
+ );
+
+ foreach ($tests as $input => $expect) {
+ $this->assertEqual(
+ $expect,
+ $parser->parse($input),
+ 'Parsing JSON: '.$input);
+ }
+ }
+
+ public function testInvalidJSON() {
+ $parser = new PhutilJSONParser();
+
+ $tests = array(
+ '{',
+ '[',
+ '{"foo":',
+ '{"foo":"bar",}',
+ '{{}',
+ );
+
+ foreach ($tests as $input) {
+ $caught = null;
+ try {
+ $parser->parse($input);
+ } catch (PhutilJSONParserException $ex) {
+ $caught = $ex;
+ }
+ $this->assertTrue($caught instanceof Exception);
+ }
+ }
+
+}
diff --git a/src/parser/exception/PhutilJSONParserException.php b/src/parser/exception/PhutilJSONParserException.php
new file mode 100644
--- /dev/null
+++ b/src/parser/exception/PhutilJSONParserException.php
@@ -0,0 +1,3 @@
+<?php
+
+final class PhutilJSONParserException extends Exception {}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Nov 28, 2:02 AM (3 h, 35 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6797833
Default Alt Text
D9622.id23080.diff (31 KB)
Attached To
Mode
D9622: Add a native JSON parser.
Attached
Detach File
Event Timeline
Log In to Comment