diff options
Diffstat (limited to 'encoding/json/value.ha')
-rw-r--r-- | encoding/json/value.ha | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/encoding/json/value.ha b/encoding/json/value.ha new file mode 100644 index 0000000..fe68688 --- /dev/null +++ b/encoding/json/value.ha @@ -0,0 +1,217 @@ +// License: MPL-2.0 +// (c) 2022 Drew DeVault <sir@cmpwn.com> +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; + }; +}; |