Skip to content

Commit

Permalink
[lib] JSON helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
linxuanm committed Jan 23, 2025
1 parent 9695aed commit b3a0c0a
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 32 deletions.
79 changes: 48 additions & 31 deletions lib/util/Json.v3
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type JsonValue {
case String(v: string);
case Number(v: int); // TODO: float
case Number(v: double);
case Bool(v: bool);
case Null;
case JArray(v: Array<JsonValue>);
Expand Down Expand Up @@ -78,42 +78,30 @@ def render_raw_string(buf: StringBuilder, s: string) -> StringBuilder {
return buf;
}

// enum JsonError(desc: string) {
// None("None"),
// EOF("End of file"),
// ParseError("Parse error"),
// EmptySource("Empty source string"),
// KeyNotFound("Key not found"),
// MismatchedValueType("Mismatched value type")
// }

// type ParseError(kind: JsonError, msg: string, pos: int) {}
// def NO_ERR = ParseError(JsonError.None, "", -1);

def ERR_RET = JsonValue.Null;
class JsonParser extends TextReader {

new(text: Array<byte>) super("", text) {}

def parse_value() -> JsonValue {
def parseValue() -> JsonValue {
skipWs(this);
if (test_eof()) return ERR_RET;
if (testEOF()) return ERR_RET;

if (char == '\"') return parse_string();
if (char == '-' || char >= '0' && char <= '9') return parse_number();
if (char == '\"') return parseString();
if (char == '-' || char >= '0' && char <= '9') return parseNumber();

if (optN("null") != -1) return JsonValue.Null;
if (optN("true") != -1) return JsonValue.Bool(true);
if (optN("false") != -1) return JsonValue.Bool(false);

if (char == '[') return parse_array();
if (char == '{') return parse_object();
if (char == '[') return parseArray();
if (char == '{') return parseObject();

fail(Strings.format1("expected JSON value, got character '%c'", char));
return ERR_RET;
}

def parse_string() -> JsonValue {
def parseString() -> JsonValue {
var res = Strings.parseLiteral(data, pos);
var len = res.0;
if (len <= 0) {
Expand All @@ -123,7 +111,7 @@ class JsonParser extends TextReader {
return JsonValue.String(readToken(len).image);
}

def parse_number() -> JsonValue {
def parseNumber() -> JsonValue {
var res = Ints.parseDecimal(data, pos);
var len = res.0, val = res.1;
if (len <= 0) {
Expand All @@ -133,46 +121,46 @@ class JsonParser extends TextReader {
return JsonValue.Number(val);
}

private def parse_object_entry() -> (string, JsonValue) {
private def parseObject_entry() -> (string, JsonValue) {
var ERR_VAL = ("", ERR_RET);

var key = parse_string();
var key = parseString();
if (!ok || req1(':') == -1) return ERR_VAL;
var val = parse_value();
var val = parseValue();
if (!ok) return ERR_VAL;
return (JsonValue.String.!(key).v, val);
}

def parse_object() -> JsonValue {
def parseObject() -> JsonValue {
var dict = HashMap<string, JsonValue>.new(Strings.hash, Strings.equal);
var entry: (string, JsonValue);
if (req1('{') == -1) return ERR_RET;
entry = parse_object_entry();
entry = parseObject_entry();
dict[entry.0] = entry.1;
if (!ok) return ERR_RET;
while (opt1(',') != -1) {
entry = parse_object_entry();
entry = parseObject_entry();
if (!ok) return ERR_RET;
dict[entry.0] = entry.1;
}
if (req1('}') == -1) return ERR_RET;
return JsonValue.JObject(dict);
}

def parse_array() -> JsonValue {
def parseArray() -> JsonValue {
var vals = Vector<JsonValue>.new();
if (req1('[') == -1) return ERR_RET;
vals.put(parse_value());
vals.put(parseValue());
if (!ok) return ERR_RET;
while (opt1(',') != -1) {
vals.put(parse_value());
vals.put(parseValue());
if (!ok) return ERR_RET;
}
if (req1(']') == -1) return ERR_RET;
return JsonValue.JArray(vals.extract());
}

def test_eof() -> bool {
def testEOF() -> bool {
var eof = pos >= data.length;
if (eof) fail("unexpected end of input");
return eof;
Expand All @@ -193,3 +181,32 @@ class MapCollector<K, V> {
return cmp(a.0, b.0);
}
}

component Jsons {
def emptyArray() -> JsonValue.JArray { return JsonValue.JArray([]); }
def makeArray<T>(vs: Range<T>, f: T -> JsonValue) -> JsonValue.JArray {
return JsonValue.JArray(Ranges.map(vs, f));
}
def intArray(vs: Range<int>) -> JsonValue.JArray {
return makeArray(vs, i2num);
}
def doubleArray(vs: Range<double>) -> JsonValue.JArray {
return makeArray(vs, d2num);
}
def strArray(vs: Range<string>) -> JsonValue.JArray {
return JsonValue.JArray(Ranges.map(vs, JsonValue.String));
}
def d2num(v: double) -> JsonValue.Number { return JsonValue.Number(v); }
def i2num(v: int) -> JsonValue.Number { return JsonValue.Number(v); }
}

class JsonBuilder {
def entries = Strings.newMap<JsonValue>();
def add(key: string, v: JsonValue) -> this { entries[key] = v; }
def str(key: string, v: string) -> this { entries[key] = JsonValue.String(v); }
def numI(key: string, v: int) -> this { entries[key] = JsonValue.Number(v); }
def numD(key: string, v: double) -> this { entries[key] = JsonValue.Number(v); }
def boo(key: string, v: bool) -> this { entries[key] = JsonValue.Bool(v); }
def arr(key: string, v: Array<JsonValue>) -> this { entries[key] = JsonValue.JArray(v); }
def build() -> JsonValue.JObject { return JsonValue.JObject(entries); }
}
2 changes: 1 addition & 1 deletion test/lib/JsonTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def X = [

def assert_parse_result(t: LibTest, src: string, expected: JsonValue) {
var p = JsonParser.new(src);
var res = p.parse_value();
var res = p.parseValue();
if (!p.ok) {
t.fail(Strings.format2("expected \"%q\", got error \"%s\"", expected.render, p.error_msg));
}
Expand Down

0 comments on commit b3a0c0a

Please sign in to comment.