1
Fork 0
hare-playground/vendor/hare-json/encoding/json/value.ha
2024-06-01 16:46:01 +02:00

220 lines
5.3 KiB
Hare

// License: MPL-2.0
// (c) 2022 Drew DeVault <sir@cmpwn.com>
use hash::fnv;
use strings;
// TODO: Resize table as appropriate
export def OBJECT_BUCKETS: size = 32;
export type object = struct {
buckets: [OBJECT_BUCKETS][](str, value),
count: size,
};
// A JSON value.
export type value = (f64 | str | bool | _null | []value | object);
// Initializes a new (empty) JSON object. Call [[finish]] to free associated
// resources when you're done using it.
export fn newobject() object = {
return object { ... };
};
// 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);
const bucket = &obj.buckets[hash % len(obj.buckets)];
for (let i = 0z; i < len(bucket); i += 1) {
if (bucket[i].0 == key) {
return &bucket[i].1;
};
};
};
// 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);
const bucket = &obj.buckets[hash % len(obj.buckets)];
for (let i = 0z; i < len(bucket); i += 1) {
if (bucket[i].0 == key) {
finish(bucket[i].1);
bucket[i].1 = val;
return;
};
};
obj.count += 1;
append(bucket, (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);
const bucket = &obj.buckets[hash % len(obj.buckets)];
for (let i = 0z; i < len(bucket); i += 1) {
if (bucket[i].0 == key) {
obj.count -= 1;
free(bucket[i].0);
const val = bucket[i].1;
delete(bucket[i]);
return val;
};
};
};
// 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 obj.count;
};
export type iterator = struct {
obj: *object,
i: size,
j: size,
};
// Creates an iterator that enumerates over the key/value pairs in an
// [[object]].
export fn iter(obj: *object) iterator = {
return iterator { obj = obj, ... };
};
// Returns the next key/value pair from this iterator, or void if none remain.
export fn next(iter: *iterator) ((const str, const *value) | void) = {
for (iter.i < len(iter.obj.buckets); iter.i += 1) {
const bucket = &iter.obj.buckets[iter.i];
for (iter.j < len(bucket)) {
const key = bucket[iter.j].0;
const val = &bucket[iter.j].1;
iter.j += 1;
return (key, val);
};
iter.j = 0;
};
};
// 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 =>
for (let i = 0z; i < len(o.buckets); i += 1) {
const bucket = &o.buckets[i];
for (let j = 0z; j < len(bucket); j += 1) {
free(bucket[j].0);
finish(bucket[j].1);
};
free(*bucket);
};
case => void;
};
};