/* * Copyright (c) 2018 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include "gc.h" #include "util.h" #define janet_table_maphash(cap, hash) ((uint32_t)(hash) & (cap - 1)) /* Initialize a table */ JanetTable *janet_table_init(JanetTable *table, int32_t capacity) { JanetKV *data; capacity = janet_tablen(capacity); if (capacity) { data = (JanetKV *) janet_memalloc_empty(capacity); if (NULL == data) { JANET_OUT_OF_MEMORY; } table->data = data; table->capacity = capacity; } else { table->data = NULL; table->capacity = 0; } table->count = 0; table->deleted = 0; table->proto = NULL; return table; } /* Deinitialize a table */ void janet_table_deinit(JanetTable *table) { free(table->data); } /* Create a new table */ JanetTable *janet_table(int32_t capacity) { JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable)); return janet_table_init(table, capacity); } /* Find the bucket that contains the given key. Will also return * bucket where key should go if not in the table. */ JanetKV *janet_table_find(JanetTable *t, Janet key) { int32_t index = janet_table_maphash(t->capacity, janet_hash(key)); int32_t i; JanetKV *first_bucket = NULL; /* Higher half */ for (i = index; i < t->capacity; i++) { JanetKV *kv = t->data + i; if (janet_checktype(kv->key, JANET_NIL)) { if (janet_checktype(kv->value, JANET_NIL)) { return kv; } else if (NULL == first_bucket) { first_bucket = kv; } } else if (janet_equals(kv->key, key)) { return t->data + i; } } /* Lower half */ for (i = 0; i < index; i++) { JanetKV *kv = t->data + i; if (janet_checktype(kv->key, JANET_NIL)) { if (janet_checktype(kv->value, JANET_NIL)) { return kv; } else if (NULL == first_bucket) { first_bucket = kv; } } else if (janet_equals(kv->key, key)) { return t->data + i; } } return first_bucket; } /* Resize the dictionary table. */ static void janet_table_rehash(JanetTable *t, int32_t size) { JanetKV *olddata = t->data; JanetKV *newdata = (JanetKV *) janet_memalloc_empty(size); if (NULL == newdata) { JANET_OUT_OF_MEMORY; } int32_t i, oldcapacity; oldcapacity = t->capacity; t->data = newdata; t->capacity = size; t->deleted = 0; for (i = 0; i < oldcapacity; i++) { JanetKV *kv = olddata + i; if (!janet_checktype(kv->key, JANET_NIL)) { JanetKV *newkv = janet_table_find(t, kv->key); *newkv = *kv; } } free(olddata); } /* Get a value out of the table */ Janet janet_table_get(JanetTable *t, Janet key) { JanetKV *bucket = janet_table_find(t, key); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) return bucket->value; /* Check prototypes */ { int i; for (i = JANET_MAX_PROTO_DEPTH, t = t->proto; t && i; t = t->proto, --i) { bucket = janet_table_find(t, key); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) return bucket->value; } } return janet_wrap_nil(); } /* Get a value out of the table. Don't check prototype tables. */ Janet janet_table_rawget(JanetTable *t, Janet key) { JanetKV *bucket = janet_table_find(t, key); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) return bucket->value; else return janet_wrap_nil(); } /* Remove an entry from the dictionary. Return the value that * was removed. */ Janet janet_table_remove(JanetTable *t, Janet key) { JanetKV *bucket = janet_table_find(t, key); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) { Janet ret = bucket->key; t->count--; t->deleted++; bucket->key = janet_wrap_nil(); bucket->value = janet_wrap_false(); return ret; } else { return janet_wrap_nil(); } } /* Put a value into the object */ void janet_table_put(JanetTable *t, Janet key, Janet value) { if (janet_checktype(key, JANET_NIL)) return; if (janet_checktype(value, JANET_NIL)) { janet_table_remove(t, key); } else { JanetKV *bucket = janet_table_find(t, key); if (NULL != bucket && !janet_checktype(bucket->key, JANET_NIL)) { bucket->value = value; } else { if (NULL == bucket || 2 * (t->count + t->deleted + 1) > t->capacity) { janet_table_rehash(t, janet_tablen(2 * t->count + 2)); } bucket = janet_table_find(t, key); if (janet_checktype(bucket->value, JANET_FALSE)) --t->deleted; bucket->key = key; bucket->value = value; ++t->count; } } } /* Clear a table */ void janet_table_clear(JanetTable *t) { int32_t capacity = t->capacity; JanetKV *data = t->data; janet_memempty(data, capacity); t->count = 0; t->deleted = 0; } /* Find next key in an object. Returns NULL if no next key. */ const JanetKV *janet_table_next(JanetTable *t, const JanetKV *kv) { JanetKV *end = t->data + t->capacity; kv = (kv == NULL) ? t->data : kv + 1; while (kv < end) { if (!janet_checktype(kv->key, JANET_NIL)) return kv; kv++; } return NULL; } /* Convert table to struct */ const JanetKV *janet_table_to_struct(JanetTable *t) { JanetKV *st = janet_struct_begin(t->count); JanetKV *kv = t->data; JanetKV *end = t->data + t->capacity; while (kv < end) { if (!janet_checktype(kv->key, JANET_NIL)) janet_struct_put(st, kv->key, kv->value); kv++; } return janet_struct_end(st); } /* Merge a table or struct into a table */ static void janet_table_mergekv(JanetTable *table, const JanetKV *kvs, int32_t cap) { int32_t i; for (i = 0; i < cap; i++) { const JanetKV *kv = kvs + i; if (!janet_checktype(kv->key, JANET_NIL)) { janet_table_put(table, kv->key, kv->value); } } } /* Merge a table other into another table */ void janet_table_merge_table(JanetTable *table, JanetTable *other) { janet_table_mergekv(table, other->data, other->capacity); } /* Merge a struct into a table */ void janet_table_merge_struct(JanetTable *table, const JanetKV *other) { janet_table_mergekv(table, other, janet_struct_capacity(other)); } /* C Functions */ static int cfun_new(JanetArgs args) { JanetTable *t; int32_t cap; JANET_FIXARITY(args, 1); JANET_ARG_INTEGER(cap, args, 0); t = janet_table(cap); JANET_RETURN_TABLE(args, t); } static int cfun_getproto(JanetArgs args) { JanetTable *t; JANET_FIXARITY(args, 1); JANET_ARG_TABLE(t, args, 0); JANET_RETURN(args, t->proto ? janet_wrap_table(t->proto) : janet_wrap_nil()); } static int cfun_setproto(JanetArgs args) { JanetTable *table, *proto; JANET_FIXARITY(args, 2); JANET_ARG_TABLE(table, args, 0); if (janet_checktype(args.v[1], JANET_NIL)) { proto = NULL; } else { JANET_ARG_TABLE(proto, args, 1); } table->proto = proto; JANET_RETURN_TABLE(args, table); } static int cfun_tostruct(JanetArgs args) { JanetTable *t; JANET_FIXARITY(args, 1); JANET_ARG_TABLE(t, args, 0); JANET_RETURN_STRUCT(args, janet_table_to_struct(t)); } static int cfun_rawget(JanetArgs args) { JanetTable *table; JANET_FIXARITY(args, 2); JANET_ARG_TABLE(table, args, 0); JANET_RETURN(args, janet_table_rawget(table, args.v[1])); } static const JanetReg cfuns[] = { {"table.new", cfun_new}, {"table.to-struct", cfun_tostruct}, {"table.getproto", cfun_getproto}, {"table.setproto", cfun_setproto}, {"table.rawget", cfun_rawget}, {NULL, NULL} }; /* Load the table module */ int janet_lib_table(JanetArgs args) { JanetTable *env = janet_env(args); janet_cfuns(env, NULL, cfuns); return 0; } #undef janet_table_maphash