Example #1
0
 private function consumeChar($c)
 {
     // valid whitespace characters in JSON (from RFC4627 for JSON) include:
     // space, horizontal tab, line feed or new line, and carriage return.
     // thanks: http://stackoverflow.com/questions/16042274/definition-of-whitespace-in-json
     if (($c === " " || $c === "\t" || $c === "\n" || $c === "\r") && !($this->state === self::STATE_IN_STRING || $this->state === self::STATE_UNICODE || $this->state === self::STATE_START_ESCAPE || $this->state === self::STATE_IN_NUMBER || $this->state === self::STATE_START_DOCUMENT)) {
         return;
     }
     switch ($this->state) {
         case self::STATE_START_DOCUMENT:
             $this->listener->onDocumentStart();
             if ($c === '[') {
                 $this->startArray();
             } elseif ($c === '{') {
                 $this->startObject();
             } else {
                 throw new ParsingError($this->lineNumber, $this->charNumber, "Document must start with object or array.");
             }
             break;
         case self::STATE_IN_ARRAY:
             if ($c === ']') {
                 $this->endArray();
             } else {
                 $this->startValue($c);
             }
             break;
         case self::STATE_IN_OBJECT:
             if ($c === '}') {
                 $this->endObject();
             } elseif ($c === '"') {
                 $this->startKey();
             } else {
                 throw new ParsingError($this->lineNumber, $this->charNumber, "Start of string expected for object key. Instead got: " . $c);
             }
             break;
         case self::STATE_END_KEY:
             if ($c !== ':') {
                 throw new ParsingError($this->lineNumber, $this->charNumber, "Expected ':' after key.");
             }
             $this->state = self::STATE_AFTER_KEY;
             break;
         case self::STATE_AFTER_KEY:
             $this->startValue($c);
             break;
         case self::STATE_IN_STRING:
             if ($c === '"') {
                 $this->endString();
             } elseif ($c === '\\') {
                 $this->state = self::STATE_START_ESCAPE;
             } elseif ($c < "" || $c === "") {
                 throw new ParsingError($this->lineNumber, $this->charNumber, "Unescaped control character encountered: " . $c);
             } else {
                 $this->buffer .= $c;
             }
             break;
         case self::STATE_START_ESCAPE:
             $this->processEscapeCharacter($c);
             break;
         case self::STATE_UNICODE:
             $this->processUnicodeCharacter($c);
             break;
         case self::STATE_AFTER_VALUE:
             $within = end($this->stack);
             if ($within === self::STACK_OBJECT) {
                 if ($c === '}') {
                     $this->endObject();
                 } elseif ($c === ',') {
                     $this->state = self::STATE_IN_OBJECT;
                 } else {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Expected ',' or '}' while parsing object. Got: " . $c);
                 }
             } elseif ($within === self::STACK_ARRAY) {
                 if ($c === ']') {
                     $this->endArray();
                 } elseif ($c === ',') {
                     $this->state = self::STATE_IN_ARRAY;
                 } else {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Expected ',' or ']' while parsing array. Got: " . $c);
                 }
             } else {
                 throw new ParsingError($this->lineNumber, $this->charNumber, "Finished a literal, but unclear what state to move to. Last state: " . $within);
             }
             break;
         case self::STATE_IN_NUMBER:
             if (preg_match('/\\d/', $c)) {
                 $this->buffer .= $c;
             } elseif ($c === '.') {
                 if (strpos($this->buffer, '.') !== false) {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Cannot have multiple decimal points in a number.");
                 } elseif (stripos($this->buffer, 'e') !== false) {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Cannot have a decimal point in an exponent.");
                 }
                 $this->buffer .= $c;
             } elseif ($c === 'e' || $c === 'E') {
                 if (stripos($this->buffer, 'e') !== false) {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Cannot have multiple exponents in a number.");
                 }
                 $this->buffer .= $c;
             } elseif ($c === '+' || $c === '-') {
                 $last = mb_substr($this->buffer, -1);
                 if (!($last === 'e' || $last === 'E')) {
                     throw new ParsingError($this->lineNumber, $this->charNumber, "Can only have '+' or '-' after the 'e' or 'E' in a number.");
                 }
                 $this->buffer .= $c;
             } else {
                 $this->endNumber();
                 // we have consumed one beyond the end of the number
                 $this->consumeChar($c);
             }
             break;
         case self::STATE_IN_TRUE:
             $this->buffer .= $c;
             if (mb_strlen($this->buffer) === 4) {
                 $this->endTrue();
             }
             break;
         case self::STATE_IN_FALSE:
             $this->buffer .= $c;
             if (mb_strlen($this->buffer) === 5) {
                 $this->endFalse();
             }
             break;
         case self::STATE_IN_NULL:
             $this->buffer .= $c;
             if (mb_strlen($this->buffer) === 4) {
                 $this->endNull();
             }
             break;
         case self::STATE_DONE:
             throw new ParsingError($this->lineNumber, $this->charNumber, "Expected end of document.");
         default:
             throw new ParsingError($this->lineNumber, $this->charNumber, "Internal error. Reached an unknown state: " . $this->state);
     }
 }