Информация об изменениях

Сообщение Re[8]: JSON vs BSON: очередное торжество больного воображени от 01.12.2022 21:47

Изменено 01.12.2022 21:52 vsb

Re[8]: JSON vs BSON: очередное торжество больного воображени
Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк.
  код
package test;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public sealed interface Json {
  record Obj(Map<String, Json> members) implements Json {
  }

  record Arr(List<Json> elements) implements Json {
  }

  record Str(String value) implements Json {
  }

  record Num(BigDecimal value) implements Json {
  }

  record Bool(boolean value) implements Json {
  }

  record Null() implements Json {
  }

  static Obj parseObj(Reader reader) throws IOException {
    skipWs(reader);
    expectCh(reader, '{');
    var members = new HashMap<String, Json>();
    while (true) {
      skipWs(reader);
      if (peekCh(reader) == '}') {
        expectCh(reader, '}');
        return new Obj(Map.copyOf(members));
      }
      if (!members.isEmpty()) {
        expectCh(reader, ',');
      }
      var str = parseStr(reader);
      skipWs(reader);
      expectCh(reader, ':');
      var elem = parseValue(reader);
      members.put(str.value(), elem);
    }
  }

  static Arr parseArr(Reader reader) throws IOException {
    skipWs(reader);
    expectCh(reader, '[');
    var elements = new ArrayList<Json>();
    while (true) {
      skipWs(reader);
      if (peekCh(reader) == ']') {
        expectCh(reader, ']');
        return new Arr(List.copyOf(elements));
      }
      if (!elements.isEmpty()) {
        expectCh(reader, ',');
      }
      var elem = parseValue(reader);
      elements.add(elem);
    }
  }

  static Json parseValue(Reader reader) throws IOException {
    skipWs(reader);
    char ch = peekCh(reader);
    return switch (ch) {
      case '"' -> parseStr(reader);
      case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> parseNum(reader);
      case '{' -> parseObj(reader);
      case '[' -> parseArr(reader);
      case 't' -> {
        expectCh(reader, 't');
        expectCh(reader, 'r');
        expectCh(reader, 'u');
        expectCh(reader, 'e');
        yield new Bool(true);
      }
      case 'f' -> {
        expectCh(reader, 'f');
        expectCh(reader, 'a');
        expectCh(reader, 'l');
        expectCh(reader, 's');
        expectCh(reader, 'e');
        yield new Bool(false);
      }
      case 'n' -> {
        expectCh(reader, 'n');
        expectCh(reader, 'u');
        expectCh(reader, 'l');
        expectCh(reader, 'l');
        yield new Null();
      }
      default -> throw new RuntimeException("Unexpected character: '" + ch + '\'');
    };
  }

  static Str parseStr(Reader reader) throws IOException {
    skipWs(reader);
    expectCh(reader, '"');
    var sb = new StringBuilder();
    while (true) {
      char ch = readCh(reader);
      if (ch == '"') {
        return new Str(sb.toString());
      }
      if (ch != '\\') {
        sb.append(ch);
        continue;
      }
      ch = readCh(reader);
      sb.append(switch (ch) {
        case '"' -> '"';
        case '\\' -> '\\';
        case '/' -> '/';
        case 'b' -> '\b';
        case 'f' -> '\f';
        case 'n' -> '\n';
        case 'r' -> '\r';
        case 't' -> '\t';
        case 'u' -> {
          char ch1 = readCh(reader);
          char ch2 = readCh(reader);
          char ch3 = readCh(reader);
          char ch4 = readCh(reader);
          String hex = new String(new char[] {ch1, ch2, ch3, ch4});
          yield (char) Integer.parseInt(hex, 16);
        }
        default -> throw new RuntimeException("Unexpected character: " + ch);
      });
    }
  }

  static Num parseNum(Reader reader) throws IOException {
    skipWs(reader);
    var sb = new StringBuilder();
    parseNumInteger(reader, sb);
    char ch = peekCh(reader);
    if (ch == '.') {
      expectCh(reader, '.');
      sb.append(ch);
      parseNumDigits(reader, sb);
      ch = peekCh(reader);
    }
    if (ch == 'e' || ch == 'E') {
      expectCh(reader, ch);
      sb.append(ch);
      ch = peekCh(reader);
      if (ch == '+' || ch == '-') {
        expectCh(reader, ch);
        sb.append(ch);
      }
      parseNumDigits(reader, sb);
    }
    return new Json.Num(new BigDecimal(sb.toString()));
  }

  private static void parseNumInteger(Reader reader, StringBuilder sb) throws IOException {
    char ch = peekCh(reader);
    if (ch == '-') {
      expectCh(reader, ch);
      sb.append(ch);
      ch = peekCh(reader);
    }
    if (ch == '0') {
      expectCh(reader, ch);
      sb.append(ch);
      return;
    }
    parseNumDigits(reader, sb);
  }

  private static void parseNumDigits(Reader reader, StringBuilder sb) throws IOException {
    char ch = expectCh(reader, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
    sb.append(ch);
    ch = peekCh(reader);
    while (ch >= '0' && ch <= '9') {
      expectCh(reader, ch);
      sb.append(ch);
      ch = peekCh(reader);
    }
  }

  private static char readCh(Reader reader) throws IOException {
    int c = reader.read();
    if (c == -1) {
      throw new RuntimeException("Unexpected EOF");
    }
    return (char) c;
  }

  private static void skipWs(Reader reader) throws IOException {
    char ch;
    do {
      reader.mark(1);
      ch = readCh(reader);
    } while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t');
    reader.reset();
  }

  private static char peekCh(Reader reader) throws IOException {
    reader.mark(1);
    char ch = readCh(reader);
    reader.reset();
    return ch;
  }

  private static char expectCh(Reader reader, char... echs) throws IOException {
    char ch = readCh(reader);
    for (char ech : echs) {
      if (ch == ech) {
        return ch;
      }
    }
    if (echs.length == 0) {
      throw new UnsupportedOperationException();
    }
    var sb = new StringBuilder("'").append(echs[0]).append("'");
    for (int i = 1; i < echs.length; i++) {
      sb.append('|').append("'").append(echs[i]).append("'");
    }
    throw new RuntimeException("Expected " + sb + " but got " + ch);
  }
}
Re[8]: JSON vs BSON: очередное торжество больного воображени
Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк.
  код
package test;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public sealed interface Json {
  record Obj(Map<String, Json> members) implements Json {
  }

  record Arr(List<Json> elements) implements Json {
  }

  record Str(String value) implements Json {
  }

  record Num(BigDecimal value) implements Json {
  }

  record Bool(boolean value) implements Json {
  }

  record Null() implements Json {
  }

  static Obj parseObj(Reader reader) throws IOException {
    skipWs(reader);
    expect(reader, '{');
    var members = new HashMap<String, Json>();
    while (true) {
      skipWs(reader);
      if (peek(reader) == '}') {
        expect(reader, '}');
        return new Obj(Map.copyOf(members));
      }
      if (!members.isEmpty()) {
        expect(reader, ',');
      }
      var str = parseStr(reader);
      skipWs(reader);
      expect(reader, ':');
      var elem = parseValue(reader);
      members.put(str.value(), elem);
    }
  }

  static Arr parseArr(Reader reader) throws IOException {
    skipWs(reader);
    expect(reader, '[');
    var elements = new ArrayList<Json>();
    while (true) {
      skipWs(reader);
      if (peek(reader) == ']') {
        expect(reader, ']');
        return new Arr(List.copyOf(elements));
      }
      if (!elements.isEmpty()) {
        expect(reader, ',');
      }
      var elem = parseValue(reader);
      elements.add(elem);
    }
  }

  static Json parseValue(Reader reader) throws IOException {
    skipWs(reader);
    char c = peek(reader);
    return switch (c) {
      case '"' -> parseStr(reader);
      case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> parseNum(reader);
      case '{' -> parseObj(reader);
      case '[' -> parseArr(reader);
      case 't' -> {
        expect(reader, 't');
        expect(reader, 'r');
        expect(reader, 'u');
        expect(reader, 'e');
        yield new Bool(true);
      }
      case 'f' -> {
        expect(reader, 'f');
        expect(reader, 'a');
        expect(reader, 'l');
        expect(reader, 's');
        expect(reader, 'e');
        yield new Bool(false);
      }
      case 'n' -> {
        expect(reader, 'n');
        expect(reader, 'u');
        expect(reader, 'l');
        expect(reader, 'l');
        yield new Null();
      }
      default -> throw new RuntimeException("Unexpected character: '" + c + '\'');
    };
  }

  static Str parseStr(Reader reader) throws IOException {
    skipWs(reader);
    expect(reader, '"');
    var sb = new StringBuilder();
    while (true) {
      char c = read(reader);
      if (c == '"') {
        return new Str(sb.toString());
      }
      if (c != '\\') {
        sb.append(c);
        continue;
      }
      c = read(reader);
      sb.append(switch (c) {
        case '"' -> '"';
        case '\\' -> '\\';
        case '/' -> '/';
        case 'b' -> '\b';
        case 'f' -> '\f';
        case 'n' -> '\n';
        case 'r' -> '\r';
        case 't' -> '\t';
        case 'u' -> {
          char c1 = read(reader);
          char c2 = read(reader);
          char c3 = read(reader);
          char c4 = read(reader);
          String hex = new String(new char[] {c1, c2, c3, c4});
          yield (char) Integer.parseInt(hex, 16);
        }
        default -> throw new RuntimeException("Unexpected character: " + c);
      });
    }
  }

  static Num parseNum(Reader reader) throws IOException {
    skipWs(reader);
    var sb = new StringBuilder();
    parseNumInteger(reader, sb);
    char c = peek(reader);
    if (c == '.') {
      expect(reader, '.');
      sb.append(c);
      parseNumDigits(reader, sb);
      c = peek(reader);
    }
    if (c == 'e' || c == 'E') {
      expect(reader, c);
      sb.append(c);
      c = peek(reader);
      if (c == '+' || c == '-') {
        expect(reader, c);
        sb.append(c);
      }
      parseNumDigits(reader, sb);
    }
    return new Json.Num(new BigDecimal(sb.toString()));
  }

  private static void parseNumInteger(Reader reader, StringBuilder sb) throws IOException {
    char c = peek(reader);
    if (c == '-') {
      expect(reader, c);
      sb.append(c);
      c = peek(reader);
    }
    if (c == '0') {
      expect(reader, c);
      sb.append(c);
      return;
    }
    parseNumDigits(reader, sb);
  }

  private static void parseNumDigits(Reader reader, StringBuilder sb) throws IOException {
    char c = expect(reader, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
    sb.append(c);
    c = peek(reader);
    while (c >= '0' && c <= '9') {
      expect(reader, c);
      sb.append(c);
      c = peek(reader);
    }
  }

  private static char read(Reader reader) throws IOException {
    int c = reader.read();
    if (c == -1) {
      throw new RuntimeException("Unexpected EOF");
    }
    return (char) c;
  }

  private static void skipWs(Reader reader) throws IOException {
    char c;
    do {
      reader.mark(1);
      c = read(reader);
    } while (c == ' ' || c == '\n' || c == '\r' || c == '\t');
    reader.reset();
  }

  private static char peek(Reader reader) throws IOException {
    reader.mark(1);
    char c = read(reader);
    reader.reset();
    return c;
  }

  private static char expect(Reader reader, char... cs) throws IOException {
    char c = read(reader);
    for (char ec : cs) {
      if (c == ec) {
        return c;
      }
    }
    if (cs.length == 0) {
      throw new UnsupportedOperationException();
    }
    var sb = new StringBuilder("'").append(cs[0]).append("'");
    for (int i = 1; i < cs.length; i++) {
      sb.append('|').append("'").append(cs[i]).append("'");
    }
    throw new RuntimeException("Expected " + sb + " but got " + c);
  }
}