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

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

Изменено 01.12.2022 22:06 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);
    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);
  }
}
Re[8]: JSON vs BSON: очередное торжество больного воображени
Вроде написал, на простых тестах работает. Как раз где-то в два часа уложился. Старался всё в точности по спеке делать. Баги если и есть, то исправляемые и размер не увеличат. 229 строк. И форматтер еще строк на 70.

  код
package test;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
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);
  }

  static void writeObj(Writer writer, Obj obj) throws IOException {
    writer.write('{');
    var it = obj.members().entrySet().iterator();
    if (it.hasNext()) {
      var e = it.next();
      writeStr(writer, e.getKey());
      writer.write(':');
      writeValue(writer, e.getValue());
      while (it.hasNext()) {
        e = it.next();
        writer.write(',');
        writeStr(writer, e.getKey());
        writer.write(':');
        writeValue(writer, e.getValue());
      }
    }
    writer.write('}');
  }

  static void writeArr(Writer writer, Arr arr) throws IOException {
    writer.write('[');
    var it = arr.elements().iterator();
    if (it.hasNext()) {
      writeValue(writer, it.next());
      while (it.hasNext()) {
        writer.write(',');
        writeValue(writer, it.next());
      }
    }
    writer.write(']');
  }

  static void writeValue(Writer writer, Json value) throws IOException {
    switch (value) {
      case Str str -> writeStr(writer, str);
      case Num num -> writeNum(writer, num);
      case Obj obj -> writeObj(writer, obj);
      case Arr arr -> writeArr(writer, arr);
      case Bool bool -> writeBool(writer, bool);
      case Null nullValue -> writeNull(writer, nullValue);
    }
  }

  static void writeStr(Writer writer, Str str) throws IOException {
    writeStr(writer, str.value());
  }

  static void writeStr(Writer writer, String str) throws IOException {
    writer.write('"');
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      switch (c) {
        case '"' -> writer.write("\\\"");
        case '\\' -> writer.write("\\\\");
        default -> writer.write(c);
      }
    }
    writer.write('"');
  }

  static void writeNum(Writer writer, Num num) throws IOException {
    writer.write(num.value().toString());
  }

  static void writeBool(Writer writer, Bool bool) throws IOException {
    writer.write(bool.value() ? "true" : "false");
  }

  static void writeNull(Writer writer, Null nullValue) throws IOException {
    writer.write("null");
  }
}