// License: MPL-2.0 // (c) 2022 Drew DeVault use hash::fnv; use htab; use strings; export type object = struct { table: htab::table, }; // A JSON value. export type value = (f64 | str | bool | _null | []value | object); type entry = (str, value); fn htab_eq(ctx: *opaque, ent: *opaque) bool = *(ctx: *str) == (ent: *entry).0; fn _get(obj: *object, hash: u64, key: str) nullable *entry = htab::get(&obj.table, hash, &htab_eq, &key, size(entry)): nullable *entry; // Initializes a new (empty) JSON object. Call [[finish]] to free associated // resources when you're done using it. export fn newobject() object = { return object { table = htab::new(0, size(entry)), }; }; // Gets a value from a JSON object. The return value is borrowed from the // object. export fn get(obj: *object, key: str) (*value | void) = { const hash = fnv::string(key); match (_get(obj, hash, key)) { case let ent: *entry => return &ent.1; case null => return void; }; }; // Sets a value in a JSON object. The key and value will be duplicated. export fn set(obj: *object, key: const str, val: const value) void = { put(obj, key, dup(val)); }; // Sets a value in a JSON object. The key will be duplicated. The object will // assume ownership over the value, without duplicating it. export fn put(obj: *object, key: const str, val: const value) void = { const hash = fnv::string(key); match (_get(obj, hash, key)) { case let ent: *entry => finish(ent.1); ent.1 = val; case null => const ent = htab::add(&obj.table, hash, size(entry)): *entry; *ent = (strings::dup(key), val); }; }; // Deletes values from a JSON object, if they are present. export fn del(obj: *object, keys: const str...) void = { for (let i = 0z; i < len(keys); i += 1) { match (take(obj, keys[i])) { case let val: value => finish(val); case void => void; }; }; }; // Deletes a key from a JSON object, returning its previous value, if any. // The caller is responsible for freeing the value. export fn take(obj: *object, key: const str) (value | void) = { const hash = fnv::string(key); match (_get(obj, hash, key)) { case let ent: *entry => free(ent.0); const val = ent.1; htab::del(&obj.table, ent, size(entry)); return val; case null => void; }; }; // Clears all values from a JSON object, leaving it empty. export fn reset(obj: *object) void = { let it = iter(obj); for (true) match (next(&it)) { case void => break; case let v: (const str, const *value) => del(obj, v.0); }; }; // Returns the number of key/value pairs in a JSON object. export fn count(obj: *object) size = { return htab::count(&obj.table); }; export type iterator = struct { iter: htab::iterator, }; // Creates an iterator that enumerates over the key/value pairs in an // [[object]]. export fn iter(obj: *object) iterator = { return iterator { iter = htab::iter(&obj.table) }; }; // Returns the next key/value pair from this iterator, or void if none remain. export fn next(iter: *iterator) ((const str, const *value) | void) = { match (htab::next(&iter.iter, size(entry))) { case let ent: *opaque => const ent = ent: *entry; return (ent.0, &ent.1); case null => void; }; }; // Duplicates a JSON value. The caller must pass the return value to [[finish]] // to free associated resources when they're done using it. export fn dup(val: value) value = { match (val) { case let s: str => return strings::dup(s); case let v: []value => let new: []value = alloc([], len(v)); for (let i = 0z; i < len(v); i += 1) { append(new, dup(v[i])); }; return new; case let o: object => let new = newobject(); const i = iter(&o); for (true) { const pair = match (next(&i)) { case void => break; case let pair: (const str, const *value) => yield pair; }; set(&new, pair.0, *pair.1); }; return new; case => return val; }; }; // Checks two JSON values for equality. export fn equal(a: value, b: value) bool = { match (a) { case _null => return b is _null; case let a: bool => return b is bool && a == b as bool; case let a: f64 => return b is f64 && a == b as f64; case let a: str => return b is str && a == b as str; case let a: []value => if (!(b is []value)) return false; const b = b as []value; if (len(a) != len(b)) return false; for (let i = 0z; i < len(a); i += 1) { if (!equal(a[i], b[i])) { return false; }; }; return true; case let a: object => if (!(b is object)) return false; let b = b as object; if (count(&a) != count(&b)) { return false; }; let a = iter(&a); for (true) match (next(&a)) { case let a: (const str, const *value) => match (get(&b, a.0)) { case let b: *value => if (!equal(*a.1, *b)) { return false; }; case void => return false; }; case void => break; }; return true; }; }; // Frees state associated with a JSON value. export fn finish(val: value) void = { match (val) { case let s: str => free(s); case let v: []value => for (let i = 0z; i < len(v); i += 1) { finish(v[i]); }; free(v); case let o: object => let i = iter(&o); for (true) match (next(&i)) { case let ent: (const str, const *value) => free(ent.0); finish(*ent.1); case void => break; }; htab::finish(&o.table); case => void; }; };