summaryrefslogtreecommitdiff
path: root/encoding/json/value.ha
diff options
context:
space:
mode:
Diffstat (limited to 'encoding/json/value.ha')
-rw-r--r--encoding/json/value.ha217
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;
+ };
+};