Package se.llbit.json

Source Code of se.llbit.json.JsonParser$SyntaxError

/* Copyright (c) 2013 Jesper Öqvist <jesper@llbit.se>
*
* This file is part of Chunky.
*
* Chunky is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chunky is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Chunky.  If not, see <http://www.gnu.org/licenses/>.
*/
package se.llbit.json;

import static se.llbit.json.JsonConstants.BEGIN_ARRAY;
import static se.llbit.json.JsonConstants.BEGIN_OBJECT;
import static se.llbit.json.JsonConstants.END_ARRAY;
import static se.llbit.json.JsonConstants.END_OBJECT;
import static se.llbit.json.JsonConstants.ESCAPE;
import static se.llbit.json.JsonConstants.FALSE;
import static se.llbit.json.JsonConstants.NAME_SEPARATOR;
import static se.llbit.json.JsonConstants.NULL;
import static se.llbit.json.JsonConstants.QUOTE_MARK;
import static se.llbit.json.JsonConstants.TRUE;
import static se.llbit.json.JsonConstants.VALUE_SEPARATOR;

import java.io.IOException;
import java.io.InputStream;

import se.llbit.io.LookaheadReader;

/**
* Parses JSON
* @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
*/
public class JsonParser {

  /**
   * @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
   */
  @SuppressWarnings("serial")
  public static class SyntaxError extends Exception {
    /**
     * @param message
     */
    public SyntaxError(String message) {
      super("Syntax Error: " + message);
    }
  }

  private final LookaheadReader in;

  /**
   * @param input
   */
  public JsonParser(InputStream input) {
    in = new LookaheadReader(input, 8);
  }

  /**
   * Parses a JSON object or array.
   * @return JsonObject or JsonArray, not null
   * @throws IOException
   * @throws SyntaxError
   */
  public JsonValue parse() throws IOException, SyntaxError {
    JsonValue value;
    skipWhitespace();
    switch(in.peek()) {
      case BEGIN_OBJECT:
        value = parseObject();
        break;
      case BEGIN_ARRAY:
        value = parseArray();
        break;
      default:
        throw new SyntaxError("expected object or array");
    }
    skipWhitespace();
    if (in.peek() != -1) {
      throw new SyntaxError("garbage at end of input");
    }
    // attach value to tree
    JsonRoot root = new JsonRoot(value);
    return root.getValue();
  }

  private JsonArray parseArray() throws IOException, SyntaxError {
    accept(BEGIN_ARRAY);
    JsonArray array = new JsonArray();
    do {
      skipWhitespace();
      JsonValue value = parseValue();
      if (value == null) {
        if (array.hasElement() || in.peek() == VALUE_SEPARATOR)
          throw new SyntaxError("missing element in array");
        break;
      }
      array.addElement(value);
      skipWhitespace();
    } while (skip(VALUE_SEPARATOR));
    accept(END_ARRAY);
    return array;
  }

  private JsonValue parseValue() throws IOException, SyntaxError {
    switch (in.peek()) {
      case BEGIN_OBJECT:
        return parseObject();
      case BEGIN_ARRAY:
        return parseArray();
      case '0': case '1': case '2': case '3': case '4': case '5':
      case '6': case '7': case '8': case '9': case '-': case '+':
        return parseNumber();
      case QUOTE_MARK:
        return parseString();
      case 't':
        acceptLiteral(TRUE);
        return new JsonTrue();
      case 'f':
        acceptLiteral(FALSE);
        return new JsonFalse();
      case 'n':
        acceptLiteral(NULL);
        return new JsonNull();
      default:
        // TODO use Null Object pattern
        return null; // not a JSON value
    }
  }

  private JsonString parseString() throws IOException, SyntaxError {
    accept(QUOTE_MARK);
    StringBuilder sb = new StringBuilder();
    while (true) {
      int next = in.pop();
      if (next == -1) {
        throw new SyntaxError("EOF while parsing JSON string");
      } else if (next == ESCAPE) {
        sb.append(unescapeStringChar());
      } else if (next == QUOTE_MARK) {
        break;
      } else {
        sb.append((char) next);
      }
    }
    return new JsonString(sb.toString());
  }

  private char unescapeStringChar() throws IOException, SyntaxError {
    int next = in.pop();
    switch (next) {
    case QUOTE_MARK:
    case ESCAPE:
    case '/':
      return (char) next;
    case 'b':
      return '\b';
    case 'f':
      return '\f';
    case 'n':
      return '\n';
    case 'r':
      return '\r';
    case 't':
      return '\t';
    case 'u':
      int[] u = { hexdigit(), hexdigit(), hexdigit(), hexdigit() };
      return (char) ((u[0] << 12) | (u[1] << 8) | (u[2] << 4) | u[3]);
    case -1:
      throw new SyntaxError("end of input while parsing JSON string");
    default:
      throw new SyntaxError("illegal escape sequence in JSON string");
    }
  }

  private int hexdigit() throws IOException, SyntaxError {
    int next = in.pop();
    int v1 = next - '0';
    int v2 = next - 'A' + 0xA;
    int v3 = next - 'a' + 0xA;
    if (v1 >= 0 && v1 <= 9) return v1;
    if (v2 >= 0xA && v2 <= 0xF) return v2;
    if (v3 >= 0xA && v3 <= 0xF) return v3;
    throw new SyntaxError("non-hexadecimal digit in unicode escape sequence");
  }

  private JsonValue parseNumber() throws IOException, SyntaxError {
    StringBuilder sb = new StringBuilder();
    while (true) {
      switch (in.peek()) {
        case -1:
          throw new SyntaxError("EOF while parsing JSON number");
        case '0': case '1': case '2': case '3': case '4': case '5':
        case '6': case '7': case '8': case '9': case '-': case '+':
        case '.': case 'e': case 'E':
          sb.append((char) in.pop());
          break;
        default:
          return new JsonNumber(sb.toString());
      }
    }
  }

  private void skipWhitespace() throws IOException {
    while (isWhitespace(in.peek())) in.pop();
  }

  private boolean isWhitespace(int chr) {
    return chr == 0x20 || chr == 0x09 || chr == 0x0A || chr == 0x0D;
  }

  private void acceptLiteral(char[] literal) throws IOException, SyntaxError {
    for (char c: literal) {
      if (in.pop() != c)
        throw new SyntaxError("encountered invalid JSON literal");
    }
  }

  private JsonObject parseObject() throws IOException, SyntaxError {
    accept(BEGIN_OBJECT);
    JsonObject object = new JsonObject();
    do {
      skipWhitespace();
      JsonMember member = parseMember();
      if (member == null) {
        if (object.hasMember() || in.peek() == VALUE_SEPARATOR)
          throw new SyntaxError("missing member in object");
        break;
      }
      object.addMember(member);
      skipWhitespace();
    } while (skip(VALUE_SEPARATOR));
    accept(END_OBJECT);
    return object;
  }

  private boolean skip(char c) throws IOException, SyntaxError {
    boolean skip = in.peek() == c;
    if (skip) in.pop();
    return skip;
  }

  private void accept(char c) throws IOException, SyntaxError {
    int next = in.pop();
    if (next == -1)
      throw new SyntaxError("unexpected end of input (expected '" + c + "')");
    if (next != c)
      throw new SyntaxError("unexpected character (was '" +
        ((char) next) + "', expected '" + c + "')");
  }

  private JsonMember parseMember() throws IOException, SyntaxError {
    if (in.peek() == QUOTE_MARK) {
      JsonString name = parseString();
      skipWhitespace();
      accept(NAME_SEPARATOR);
      skipWhitespace();
      JsonValue value = parseValue();
      if (value == null)
        throw new SyntaxError("missing value for object member");
      return new JsonMember(name.getValue(), value);
    }
    return null;
  }
}
TOP

Related Classes of se.llbit.json.JsonParser$SyntaxError

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.