summaryrefslogtreecommitdiffstats
path: root/jv.c
diff options
context:
space:
mode:
authorStephen Dolan <mu@netsoc.tcd.ie>2012-09-18 17:44:43 +0100
committerStephen Dolan <mu@netsoc.tcd.ie>2012-09-18 17:44:43 +0100
commita4eea165bbab6d13f89b59707e835d58b7014a66 (patch)
treeb99ee5dde8540f8dbe5de3d87b99e04ac4dd2673 /jv.c
parent25cbab056b1f73e96b636c88779a92400d92dc15 (diff)
Move everything around - delete old Haskell code, clean up build.
Diffstat (limited to 'jv.c')
-rw-r--r--jv.c979
1 files changed, 979 insertions, 0 deletions
diff --git a/jv.c b/jv.c
new file mode 100644
index 00000000..2d91366b
--- /dev/null
+++ b/jv.c
@@ -0,0 +1,979 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "jv.h"
+
+/*
+ * Internal refcounting helpers
+ */
+
+static void jvp_refcnt_init(jv_complex* c) {
+ c->ptr->count = 1;
+}
+
+static void jvp_refcnt_inc(jv_complex* c) {
+ c->ptr->count++;
+}
+
+static int jvp_refcnt_dec(jv_complex* c) {
+ c->ptr->count--;
+ return c->ptr->count == 0;
+}
+
+static int jvp_refcnt_unshared(jv_complex* c) {
+ assert(c->ptr->count > 0);
+ return c->ptr->count == 1;
+}
+
+/*
+ * Simple values (true, false, null)
+ */
+
+jv_kind jv_get_kind(jv x) {
+ return x.kind;
+}
+
+const char* jv_kind_name(jv_kind k) {
+ switch (k) {
+ case JV_KIND_INVALID: return "<invalid>";
+ case JV_KIND_NULL: return "null";
+ case JV_KIND_FALSE: return "boolean";
+ case JV_KIND_TRUE: return "boolean";
+ case JV_KIND_NUMBER: return "number";
+ case JV_KIND_STRING: return "string";
+ case JV_KIND_ARRAY: return "array";
+ case JV_KIND_OBJECT: return "object";
+ }
+ assert(0 && "invalid kind");
+ return "<unknown>";
+}
+
+static const jv JV_NULL = {JV_KIND_NULL, {0}};
+static const jv JV_FALSE = {JV_KIND_FALSE, {0}};
+static const jv JV_TRUE = {JV_KIND_TRUE, {0}};
+
+jv jv_true() {
+ return JV_TRUE;
+}
+
+jv jv_false() {
+ return JV_FALSE;
+}
+
+jv jv_null() {
+ return JV_NULL;
+}
+
+jv jv_bool(int x) {
+ return x ? JV_TRUE : JV_FALSE;
+}
+
+/*
+ * Invalid objects, with optional error messages
+ */
+
+typedef struct {
+ jv_refcnt refcnt;
+ jv errmsg;
+} jvp_invalid;
+
+jv jv_invalid_with_msg(jv err) {
+ jv x;
+ x.kind = JV_KIND_INVALID;
+ x.val.complex.i[0] = x.val.complex.i[1] = 0;
+ jvp_invalid* i = malloc(sizeof(jvp_invalid));
+ x.val.complex.ptr = &i->refcnt;
+ i->refcnt.count = 1;
+ i->errmsg = err;
+ return x;
+}
+
+jv jv_invalid() {
+ return jv_invalid_with_msg(jv_null());
+}
+
+jv jv_invalid_get_msg(jv inv) {
+ jv x = jv_copy(((jvp_invalid*)inv.val.complex.ptr)->errmsg);
+ jv_free(inv);
+ return x;
+}
+
+int jv_invalid_has_msg(jv inv) {
+ jv msg = jv_invalid_get_msg(inv);
+ int r = jv_get_kind(msg) != JV_KIND_NULL;
+ jv_free(msg);
+ return r;
+}
+
+static void jvp_invalid_free(jv_complex* x) {
+ if (jvp_refcnt_dec(x)) {
+ jv_free(((jvp_invalid*)x->ptr)->errmsg);
+ free(x->ptr);
+ }
+}
+
+/*
+ * Numbers
+ */
+
+jv jv_number(double x) {
+ jv j;
+ j.kind = JV_KIND_NUMBER;
+ j.val.number = x;
+ return j;
+}
+
+double jv_number_value(jv j) {
+ assert(jv_get_kind(j) == JV_KIND_NUMBER);
+ return j.val.number;
+}
+
+
+/*
+ * Arrays (internal helpers)
+ */
+
+#define ARRAY_SIZE_ROUND_UP(n) (((n)*3)/2)
+
+static int imax(int a, int b) {
+ if (a>b) return a;
+ else return b;
+}
+
+//FIXME signed vs unsigned
+typedef struct {
+ jv_refcnt refcnt;
+ int length, alloc_length;
+ jv elements[];
+} jvp_array;
+
+static jvp_array* jvp_array_ptr(jv_complex* a) {
+ return (jvp_array*)a->ptr;
+}
+
+static jvp_array* jvp_array_alloc(unsigned size) {
+ jvp_array* a = malloc(sizeof(jvp_array) + sizeof(jv) * size);
+ a->refcnt.count = 1;
+ a->length = 0;
+ a->alloc_length = size;
+ return a;
+}
+
+static jv_complex jvp_array_new(unsigned size) {
+ jv_complex r = {&jvp_array_alloc(size)->refcnt, {0, 0}};
+ return r;
+}
+
+static void jvp_array_free(jv_complex* a) {
+ if (jvp_refcnt_dec(a)) {
+ jvp_array* array = jvp_array_ptr(a);
+ for (int i=0; i<array->length; i++) {
+ jv_free(array->elements[i]);
+ }
+ free(array);
+ }
+}
+
+static int jvp_array_length(jv_complex* a) {
+ return a->i[1] - a->i[0];
+}
+
+static jv* jvp_array_read(jv_complex* a, int i) {
+ if (i >= 0 && i < jvp_array_length(a)) {
+ jvp_array* array = jvp_array_ptr(a);
+ assert(i + a->i[0] < array->length);
+ return &array->elements[i + a->i[0]];
+ } else {
+ return 0;
+ }
+}
+
+static jv* jvp_array_write(jv_complex* a, int i) {
+ assert(i >= 0);
+ jvp_array* array = jvp_array_ptr(a);
+
+ int pos = i + a->i[0];
+ if (pos < array->alloc_length) {
+ // maybe we can update it in-place
+ // FIXME: this "optimisation" can cause circular references
+ #if 0
+ int can_write_past_end =
+ array->length <= pos && /* the end of this array has never been used */
+ a->i[1] == array->length; /* the current slice sees the end of the array */
+ #endif
+ int can_write_past_end = 0;
+ if (can_write_past_end || jvp_refcnt_unshared(a)) {
+ // extend the array
+ for (int j = array->length; j <= pos; j++) {
+ array->elements[j] = JV_NULL;
+ }
+ array->length = imax(pos + 1, array->length);
+ a->i[1] = imax(pos + 1, array->length);
+ return &array->elements[pos];
+ }
+ }
+
+
+ int new_length = imax(i + 1, jvp_array_length(a));
+ jvp_array* new_array = jvp_array_alloc(ARRAY_SIZE_ROUND_UP(new_length));
+ int j;
+ for (j = 0; j < jvp_array_length(a); j++) {
+ new_array->elements[j] = jv_copy(array->elements[j + a->i[0]]);
+ }
+ for (; j < new_length; j++) {
+ new_array->elements[j] = JV_NULL;
+ }
+ new_array->length = new_length;
+ jvp_array_free(a);
+ a->ptr = &new_array->refcnt;
+ a->i[0] = 0;
+ a->i[1] = new_length;
+ return &new_array->elements[i];
+}
+
+static int jvp_array_equal(jv_complex* a, jv_complex* b) {
+ if (jvp_array_length(a) != jvp_array_length(b))
+ return 0;
+ if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
+ a->i[0] == b->i[0])
+ return 1;
+ for (int i=0; i<jvp_array_length(a); i++) {
+ if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
+ jv_copy(*jvp_array_read(b,i))))
+ return 0;
+ }
+ return 1;
+}
+
+static jv_complex jvp_array_slice(jv_complex* a, int start, int end) {
+ // FIXME: maybe slice should reallocate if the slice is small enough
+ assert(start <= end);
+ jvp_array* array = jvp_array_ptr(a);
+ assert(a->i[1] + end < array->length);
+ jv_complex slice = *a;
+ slice.i[0] += start;
+ slice.i[1] = slice.i[0] + (end - start);
+ return slice;
+}
+
+/*
+ * Arrays (public interface)
+ */
+
+jv jv_array_sized(int n) {
+ jv j;
+ j.kind = JV_KIND_ARRAY;
+ j.val.complex = jvp_array_new(n);
+ return j;
+}
+
+jv jv_array() {
+ return jv_array_sized(16);
+}
+
+int jv_array_length(jv j) {
+ assert(jv_get_kind(j) == JV_KIND_ARRAY);
+ int len = jvp_array_length(&j.val.complex);
+ jv_free(j);
+ return len;
+}
+
+jv jv_array_get(jv j, int idx) {
+ assert(jv_get_kind(j) == JV_KIND_ARRAY);
+ jv* slot = jvp_array_read(&j.val.complex, idx);
+ jv val;
+ if (slot) {
+ val = jv_copy(*slot);
+ } else {
+ val = jv_invalid();
+ }
+ jv_free(j);
+ return val;
+}
+
+jv jv_array_set(jv j, int idx, jv val) {
+ assert(jv_get_kind(j) == JV_KIND_ARRAY);
+ // copy/free of val,j coalesced
+ jv* slot = jvp_array_write(&j.val.complex, idx);
+ jv_free(*slot);
+ *slot = val;
+ return j;
+}
+
+jv jv_array_append(jv j, jv val) {
+ // copy/free of val,j coalesced
+ return jv_array_set(j, jv_array_length(jv_copy(j)), val);
+}
+
+jv jv_array_concat(jv a, jv b) {
+ assert(jv_get_kind(a) == JV_KIND_ARRAY);
+ assert(jv_get_kind(b) == JV_KIND_ARRAY);
+
+ // FIXME: could be much faster
+ for (int i=0; i<jv_array_length(jv_copy(b)); i++) {
+ a = jv_array_append(a, jv_array_get(jv_copy(b), i));
+ }
+ jv_free(b);
+ return a;
+}
+
+jv jv_array_slice(jv a, int start, int end) {
+ assert(jv_get_kind(a) == JV_KIND_ARRAY);
+ // copy/free of a coalesced
+ a.val.complex = jvp_array_slice(&a.val.complex, start, end);
+ return a;
+}
+
+
+/*
+ * Strings (internal helpers)
+ */
+
+typedef struct {
+ jv_refcnt refcnt;
+ uint32_t hash;
+ // high 31 bits are length, low bit is a flag
+ // indicating whether hash has been computed.
+ uint32_t length_hashed;
+ uint32_t alloc_length;
+ char data[];
+} jvp_string;
+
+static jvp_string* jvp_string_ptr(jv_complex* a) {
+ return (jvp_string*)a->ptr;
+}
+
+static jvp_string* jvp_string_alloc(uint32_t size) {
+ jvp_string* s = malloc(sizeof(jvp_string) + size + 1);
+ s->refcnt.count = 1;
+ s->alloc_length = size;
+ return s;
+}
+
+static jv_complex jvp_string_new(const char* data, uint32_t length) {
+ jvp_string* s = jvp_string_alloc(length);
+ s->length_hashed = length << 1;
+ memcpy(s->data, data, length);
+ s->data[length] = 0;
+ jv_complex r = {&s->refcnt, {0,0}};
+ return r;
+}
+
+static void jvp_string_free(jv_complex* s) {
+ if (jvp_refcnt_dec(s)) {
+ jvp_string* str = jvp_string_ptr(s);
+ free(str);
+ }
+}
+
+static void jvp_string_free_p(jvp_string* s) {
+ jv_complex p = {&s->refcnt,{0,0}};
+ jvp_string_free(&p);
+}
+
+static jvp_string* jvp_string_copy_p(jvp_string* s) {
+ jv_complex p = {&s->refcnt,{0,0}};
+ jvp_refcnt_inc(&p);
+ return s;
+}
+
+static uint32_t jvp_string_length(jvp_string* s) {
+ return s->length_hashed >> 1;
+}
+
+static uint32_t jvp_string_remaining_space(jvp_string* s) {
+ assert(s->alloc_length >= jvp_string_length(s));
+ uint32_t r = s->alloc_length - jvp_string_length(s);
+ return r;
+}
+
+static void jvp_string_append(jv_complex* string, const char* data, uint32_t len) {
+ jvp_string* s = jvp_string_ptr(string);
+ uint32_t currlen = jvp_string_length(s);
+
+ if (jvp_refcnt_unshared(string) &&
+ jvp_string_remaining_space(s) >= len) {
+ // the next string fits at the end of a
+ memcpy(s->data + currlen, data, len);
+ s->data[currlen + len] = 0;
+ s->length_hashed = (currlen + len) << 1;
+ } else {
+ // allocate a bigger buffer and copy
+ uint32_t allocsz = (currlen + len) * 2;
+ if (allocsz < 32) allocsz = 32;
+ jvp_string* news = jvp_string_alloc(allocsz);
+ news->length_hashed = (currlen + len) << 1;
+ memcpy(news->data, s->data, currlen);
+ memcpy(news->data + currlen, data, len);
+ news->data[currlen + len] = 0;
+ jvp_string_free(string);
+ jv_complex r = {&news->refcnt, {0,0}};
+ *string = r;
+ }
+}
+
+static const uint32_t HASH_SEED = 0x432A9843;
+
+static uint32_t rotl32 (uint32_t x, int8_t r){
+ return (x << r) | (x >> (32 - r));
+}
+
+static uint32_t jvp_string_hash(jvp_string* str) {
+ if (str->length_hashed & 1)
+ return str->hash;
+
+ /* The following is based on MurmurHash3.
+ MurmurHash3 was written by Austin Appleby, and is placed
+ in the public domain. */
+
+ const uint8_t* data = (const uint8_t*)str->data;
+ int len = (int)jvp_string_length(str);
+ const int nblocks = len / 4;
+
+ uint32_t h1 = HASH_SEED;
+
+ const uint32_t c1 = 0xcc9e2d51;
+ const uint32_t c2 = 0x1b873593;
+ const uint32_t* blocks = (const uint32_t *)(data + nblocks*4);
+
+ for(int i = -nblocks; i; i++) {
+ uint32_t k1 = blocks[i]; //FIXME: endianness/alignment
+
+ k1 *= c1;
+ k1 = rotl32(k1,15);
+ k1 *= c2;
+
+ h1 ^= k1;
+ h1 = rotl32(h1,13);
+ h1 = h1*5+0xe6546b64;
+ }
+
+ const uint8_t* tail = (const uint8_t*)(data + nblocks*4);
+
+ uint32_t k1 = 0;
+
+ switch(len & 3) {
+ case 3: k1 ^= tail[2] << 16;
+ case 2: k1 ^= tail[1] << 8;
+ case 1: k1 ^= tail[0];
+ k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1;
+ }
+
+ h1 ^= len;
+
+ h1 ^= h1 >> 16;
+ h1 *= 0x85ebca6b;
+ h1 ^= h1 >> 13;
+ h1 *= 0xc2b2ae35;
+ h1 ^= h1 >> 16;
+
+ str->length_hashed |= 1;
+ str->hash = h1;
+
+ return h1;
+}
+
+static int jvp_string_equal_hashed(jvp_string* a, jvp_string* b) {
+ assert(a->length_hashed & 1);
+ assert(b->length_hashed & 1);
+ if (a == b) return 1;
+ if (a->hash != b->hash) return 0;
+ if (a->length_hashed != b->length_hashed) return 0;
+ return memcmp(a->data, b->data, jvp_string_length(a)) == 0;
+}
+
+static int jvp_string_equal(jv_complex* a, jv_complex* b) {
+ jvp_string* stra = jvp_string_ptr(a);
+ jvp_string* strb = jvp_string_ptr(b);
+ if (jvp_string_length(stra) != jvp_string_length(strb)) return 0;
+ return memcmp(stra->data, strb->data, jvp_string_length(stra)) == 0;
+}
+
+/*
+ * Strings (public API)
+ */
+
+jv jv_string_sized(const char* str, int len) {
+ jv j;
+ j.kind = JV_KIND_STRING;
+ j.val.complex = jvp_string_new(str, len);
+ return j;
+}
+
+jv jv_string(const char* str) {
+ return jv_string_sized(str, strlen(str));
+}
+
+int jv_string_length(jv j) {
+ assert(jv_get_kind(j) == JV_KIND_STRING);
+ int r = jvp_string_length(jvp_string_ptr(&j.val.complex));
+ jv_free(j);
+ return r;
+}
+
+uint32_t jv_string_hash(jv j) {
+ assert(jv_get_kind(j) == JV_KIND_STRING);
+ uint32_t hash = jvp_string_hash(jvp_string_ptr(&j.val.complex));
+ jv_free(j);
+ return hash;
+}
+
+const char* jv_string_value(jv j) {
+ assert(jv_get_kind(j) == JV_KIND_STRING);
+ return jvp_string_ptr(&j.val.complex)->data;
+}
+
+jv jv_string_concat(jv a, jv b) {
+ jvp_string* sb = jvp_string_ptr(&b.val.complex);
+ jvp_string_append(&a.val.complex, sb->data, jvp_string_length(sb));
+ jv_free(b);
+ return a;
+}
+
+jv jv_string_append_buf(jv a, const char* buf, int len) {
+ jvp_string_append(&a.val.complex, buf, len);
+ return a;
+}
+
+jv jv_string_append_str(jv a, const char* str) {
+ return jv_string_append_buf(a, str, strlen(str));
+}
+
+jv jv_string_fmt(const char* fmt, ...) {
+ int size = 1024;
+ while (1) {
+ char* buf = malloc(size);
+ va_list args;
+ va_start(args, fmt);
+ int n = vsnprintf(buf, size, fmt, args);
+ va_end(args);
+ if (n < size) {
+ jv ret = jv_string_sized(buf, n);
+ free(buf);
+ return ret;
+ } else {
+ free(buf);
+ size = n * 2;
+ }
+ }
+}
+
+/*
+ * Objects (internal helpers)
+ */
+
+struct object_slot {
+ int next;
+ jvp_string* string;
+ uint32_t hash;
+ jv value;
+};
+
+typedef struct {
+ jv_refcnt refcnt;
+ int first_free;
+ struct object_slot elements[];
+} jvp_object;
+
+
+/* warning: complex justification of alignment */
+static jv_complex jvp_object_new(int size) {
+ // Allocates an object of (size) slots and (size*2) hash buckets.
+
+ // size must be a power of two
+ assert(size > 0 && (size & (size - 1)) == 0);
+ jvp_object* obj = malloc(sizeof(jvp_object) +
+ sizeof(struct object_slot) * size +
+ sizeof(int) * (size * 2));
+ obj->refcnt.count = 1;
+ for (int i=0; i<size; i++) {
+ obj->elements[i].next = i - 1;
+ obj->elements[i].string = 0;
+ obj->elements[i].hash = 0;
+ obj->elements[i].value = JV_NULL;
+ }
+ obj->first_free = size - 1;
+ int* hashbuckets = (int*)(&obj->elements[size]);
+ jv_complex r = {&obj->refcnt,
+ {size*2 - 1, (char*)hashbuckets - (char*)obj}};
+ for (int i=0; i<size*2; i++) {
+ hashbuckets[i] = -1;
+ }
+ return r;
+}
+
+static jvp_object* jvp_object_ptr(jv_complex* o) {
+ return (jvp_object*)o->ptr;
+}
+
+static uint32_t jvp_object_mask(jv_complex* o) {
+ return o->i[0];
+}
+
+static int jvp_object_size(jv_complex* o) {
+ return (o->i[0] + 1) >> 1;
+}
+
+static int* jvp_object_buckets(jv_complex* o) {
+ int* buckets = (int*)((char*)o->ptr + o->i[1]);
+ assert(buckets == (int*)&jvp_object_ptr(o)->elements[jvp_object_size(o)]);
+ return buckets;
+}
+
+static int* jvp_object_find_bucket(jv_complex* object, jvp_string* key) {
+ return jvp_object_buckets(object) + (jvp_object_mask(object) & jvp_string_hash(key));
+}
+
+static struct object_slot* jvp_object_get_slot(jv_complex* object, int slot) {
+ assert(slot == -1 || (slot >= 0 && slot < jvp_object_size(object)));
+ if (slot == -1) return 0;
+ else return &jvp_object_ptr(object)->elements[slot];
+}
+
+static struct object_slot* jvp_object_next_slot(jv_complex* object, struct object_slot* slot) {
+ return jvp_object_get_slot(object, slot->next);
+}
+
+static struct object_slot* jvp_object_find_slot(jv_complex* object, jvp_string* keystr, int* bucket) {
+ for (struct object_slot* curr = jvp_object_get_slot(object, *bucket);
+ curr;
+ curr = jvp_object_next_slot(object, curr)) {
+ if (jvp_string_equal_hashed(keystr, curr->string)) {
+ return curr;
+ }
+ }
+ return 0;
+}
+
+static struct object_slot* jvp_object_add_slot(jv_complex* object, jvp_string* key, int* bucket) {
+ jvp_object* o = jvp_object_ptr(object);
+ int newslot_idx = o->first_free;
+ struct object_slot* newslot = jvp_object_get_slot(object, newslot_idx);
+ if (newslot == 0) return 0;
+ o->first_free = newslot->next;
+ newslot->next = *bucket;
+ *bucket = newslot_idx;
+ newslot->hash = jvp_string_hash(key);
+ newslot->string = key;
+ return newslot;
+}
+
+static void jvp_object_free_slot(jv_complex* object, struct object_slot* slot) {
+ jvp_object* o = jvp_object_ptr(object);
+ slot->next = o->first_free;
+ assert(slot->string);
+ jvp_string_free_p(slot->string);
+ slot->string = 0;
+ jv_free(slot->value);
+ o->first_free = slot - jvp_object_get_slot(object, 0);
+}
+
+static jv* jvp_object_read(jv_complex* object, jvp_string* key) {
+ int* bucket = jvp_object_find_bucket(object, key);
+ struct object_slot* slot = jvp_object_find_slot(object, key, bucket);
+ if (slot == 0) return 0;
+ else return &slot->value;
+}
+
+static void jvp_object_free(jv_complex* o) {
+ if (jvp_refcnt_dec(o)) {
+ for (int i=0; i<jvp_object_size(o); i++) {
+ struct object_slot* slot = jvp_object_get_slot(o, i);
+ if (slot->string) {
+ jvp_string_free_p(slot->string);
+ jv_free(slot->value);
+ }
+ }
+ free(jvp_object_ptr(o));
+ }
+}
+
+static void jvp_object_rehash(jv_complex* object) {
+ assert(jvp_refcnt_unshared(object));
+ int size = jvp_object_size(object);
+ jv_complex new_object = jvp_object_new(size * 2);
+ for (int i=0; i<size; i++) {
+ struct object_slot* slot = jvp_object_get_slot(object, i);
+ if (!slot->string) continue;
+
+ int* new_bucket = jvp_object_find_bucket(&new_object, slot->string);
+ assert(!jvp_object_find_slot(&new_object, slot->string, new_bucket));
+ struct object_slot* new_slot = jvp_object_add_slot(&new_object, slot->string, new_bucket);
+ assert(new_slot);
+ new_slot->value = slot->value;
+ }
+ // references are transported, just drop the old table
+ free(jvp_object_ptr(object));
+ *object = new_object;
+}
+
+static void jvp_object_unshare(jv_complex* object) {
+ if (jvp_refcnt_unshared(object))
+ return;
+
+ jv_complex new_object = jvp_object_new(jvp_object_size(object));
+ jvp_object_ptr(&new_object)->first_free = jvp_object_ptr(object)->first_free;
+ for (int i=0; i<jvp_object_size(&new_object); i++) {
+ struct object_slot* old_slot = jvp_object_get_slot(object, i);
+ struct object_slot* new_slot = jvp_object_get_slot(&new_object, i);
+ *new_slot = *old_slot;
+ if (old_slot->string) {
+ new_slot->string = jvp_string_copy_p(old_slot->string);
+ new_slot->value = jv_copy(old_slot->value);
+ }
+ }
+
+ int* old_buckets = jvp_object_buckets(object);
+ int* new_buckets = jvp_object_buckets(&new_object);
+ memcpy(new_buckets, old_buckets, sizeof(int) * jvp_object_size(&new_object)*2);
+
+ jvp_object_free(object);
+ *object = new_object;
+ assert(jvp_refcnt_unshared(object));
+}
+
+static jv* jvp_object_write(jv_complex* object, jvp_string* key) {
+ jvp_object_unshare(object);
+ int* bucket = jvp_object_find_bucket(object, key);
+ struct object_slot* slot = jvp_object_find_slot(object, key, bucket);
+ if (slot) {
+ // already has the key
+ jvp_string_free_p(key);
+ return &slot->value;
+ }
+ slot = jvp_object_add_slot(object, key, bucket);
+ if (slot) {
+ slot->value = jv_invalid();
+ } else {
+ jvp_object_rehash(object);
+ bucket = jvp_object_find_bucket(object, key);
+ assert(!jvp_object_find_slot(object, key, bucket));
+ slot = jvp_object_add_slot(object, key, bucket);
+ assert(slot);
+ slot->value = jv_invalid();
+ }
+ return &slot->value;
+}
+
+static int jvp_object_delete(jv_complex* object, jvp_string* key) {
+ jvp_object_unshare(object);
+ int* bucket = jvp_object_find_bucket(object, key);
+ int* prev_ptr = bucket;
+ for (struct object_slot* curr = jvp_object_get_slot(object, *bucket);
+ curr;
+ curr = jvp_object_next_slot(object, curr)) {
+ if (jvp_string_equal_hashed(key, curr->string)) {
+ *prev_ptr = curr->next;
+ jvp_object_free_slot(object, curr);
+ return 1;
+ }
+ prev_ptr = &curr->next;
+ }
+ return 0;
+}
+
+static int jvp_object_length(jv_complex* object) {
+ int n = 0;
+ for (int i=0; i<jvp_object_size(object); i++) {
+ struct object_slot* slot = jvp_object_get_slot(object, i);
+ if (slot->string) n++;
+ }
+ return n;
+}
+
+static int jvp_object_equal(jv_complex* o1, jv_complex* o2) {
+ int len2 = jvp_object_length(o2);
+ int len1 = 0;
+ for (int i=0; i<jvp_object_size(o1); i++) {
+ struct object_slot* slot = jvp_object_get_slot(o1, i);
+ if (!slot->string) continue;
+ jv* slot2 = jvp_object_read(o2, slot->string);
+ if (!slot2) return 0;
+ // FIXME: do less refcounting here
+ if (!jv_equal(jv_copy(slot->value), jv_copy(*slot2))) return 0;
+ len1++;
+ }
+ return len1 == len2;
+}
+
+/*
+ * Objects (public interface)
+ */
+#define DEFAULT_OBJECT_SIZE 8
+jv jv_object() {
+ jv j;
+ j.kind = JV_KIND_OBJECT;
+ j.val.complex = jvp_object_new(8);
+ return j;
+}
+
+jv jv_object_get(jv object, jv key) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ assert(jv_get_kind(key) == JV_KIND_STRING);
+ jv* slot = jvp_object_read(&object.val.complex, jvp_string_ptr(&key.val.complex));
+ jv val;
+ if (slot) {
+ val = jv_copy(*slot);
+ } else {
+ val = jv_invalid();
+ }
+ jv_free(object);
+ jv_free(key);
+ return val;
+}
+
+jv jv_object_set(jv object, jv key, jv value) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ assert(jv_get_kind(key) == JV_KIND_STRING);
+ // copy/free of object, key, value coalesced
+ jv* slot = jvp_object_write(&object.val.complex, jvp_string_ptr(&key.val.complex));
+ jv_free(*slot);
+ *slot = value;
+ return object;
+}
+
+jv jv_object_delete(jv object, jv key) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ assert(jv_get_kind(key) == JV_KIND_STRING);
+ jvp_object_delete(&object.val.complex, jvp_string_ptr(&key.val.complex));
+ jv_free(key);
+ return object;
+}
+
+int jv_object_length(jv object) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ int n = jvp_object_length(&object.val.complex);
+ jv_free(object);
+ return n;
+}
+
+jv jv_object_merge(jv a, jv b) {
+ assert(jv_get_kind(a) == JV_KIND_OBJECT);
+ jv_object_foreach(i, b) {
+ a = jv_object_set(a,
+ jv_object_iter_key(b, i),
+ jv_object_iter_value(b, i));
+ }
+ jv_free(b);
+ return a;
+}
+
+/*
+ * Object iteration (internal helpers)
+ */
+
+enum { ITER_FINISHED = -2 };
+
+int jv_object_iter_valid(jv object, int i) {
+ return i != ITER_FINISHED;
+}
+
+int jv_object_iter(jv object) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ return jv_object_iter_next(object, -1);
+}
+
+int jv_object_iter_next(jv object, int iter) {
+ assert(jv_get_kind(object) == JV_KIND_OBJECT);
+ assert(iter != ITER_FINISHED);
+ jv_complex* o = &object.val.complex;
+ struct object_slot* slot;
+ do {
+ iter++;
+ if (iter >= jvp_object_size(o))
+ return ITER_FINISHED;
+ slot = jvp_object_get_slot(o, iter);
+ } while (!slot->string);
+ return iter;
+}
+
+jv jv_object_iter_key(jv object, int iter) {
+ jvp_string* s = jvp_object_get_slot(&object.val.complex, iter)->string;
+ assert(s);
+ jv j;
+ j.kind = JV_KIND_STRING;
+ j.val.complex.ptr = &s->refcnt;
+ j.val.complex.i[0] = 0;
+ j.val.complex.i[1] = 0;
+ return jv_copy(j);
+}
+
+jv jv_object_iter_value(jv object, int iter) {
+ return jv_copy(jvp_object_get_slot(&object.val.complex, iter)->value);
+}
+
+/*
+ * Memory management
+ */
+jv jv_copy(jv j) {
+ if (jv_get_kind(j) == JV_KIND_ARRAY ||
+ jv_get_kind(j) == JV_KIND_STRING ||
+ jv_get_kind(j) == JV_KIND_OBJECT ||
+ jv_get_kind(j) == JV_KIND_INVALID) {
+ jvp_refcnt_inc(&j.val.complex);
+ }
+ return j;
+}
+
+void jv_free(jv j) {
+ if (jv_get_kind(j) == JV_KIND_ARRAY) {
+ jvp_array_free(&j.val.complex);
+ } else if (jv_get_kind(j) == JV_KIND_STRING) {
+ jvp_string_free(&j.val.complex);
+ } else if (jv_get_kind(j) == JV_KIND_OBJECT) {
+ jvp_object_free(&j.val.complex);
+ } else if (jv_get_kind(j) == JV_KIND_INVALID) {
+ jvp_invalid_free(&j.val.complex);
+ }
+}
+
+int jv_get_refcnt(jv j) {
+ switch (jv_get_kind(j)) {
+ case JV_KIND_ARRAY:
+ case JV_KIND_STRING:
+ case JV_KIND_OBJECT:
+ return j.val.complex.ptr->count;
+ default:
+ return 1;
+ }
+}
+
+/*
+ * Higher-level operations
+ */
+
+int jv_equal(jv a, jv b) {
+ int r;
+ if (jv_get_kind(a) != jv_get_kind(b)) {
+ r = 0;
+ } else if (jv_get_kind(a) == JV_KIND_NUMBER) {
+ r = jv_number_value(a) == jv_number_value(b);
+ } else if (a.val.complex.ptr == b.val.complex.ptr &&
+ a.val.complex.i[0] == b.val.complex.i[0] &&
+ a.val.complex.i[1] == b.val.complex.i[1]) {
+ r = 1;
+ } else {
+ switch (jv_get_kind(a)) {
+ case JV_KIND_ARRAY:
+ r = jvp_array_equal(&a.val.complex, &b.val.complex);
+ break;
+ case JV_KIND_STRING:
+ r = jvp_string_equal(&a.val.complex, &b.val.complex);
+ break;
+ case JV_KIND_OBJECT:
+ r = jvp_object_equal(&a.val.complex, &b.val.complex);
+ break;
+ default:
+ r = 1;
+ break;
+ }
+ }
+ jv_free(a);
+ jv_free(b);
+ return r;
+}