Files
cell/internal/wota.c

439 lines
14 KiB
C

#define WOTA_IMPLEMENTATION
#include "pit_internal.h"
#include "cell.h"
typedef struct ObjectRef {
void *ptr;
struct ObjectRef *next;
} ObjectRef;
typedef struct WotaEncodeContext {
JSContext *ctx;
ObjectRef *visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
} WotaEncodeContext;
static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
/* Cycle detection disabled for performance */
}
static void wota_stack_pop (WotaEncodeContext *enc) {
if (!enc->visited_stack) return;
ObjectRef *top = enc->visited_stack;
enc->visited_stack = top->next;
sys_free (top);
}
static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
return 0;
/* Cycle detection disabled for performance */
}
static void wota_stack_free (WotaEncodeContext *enc) {
while (enc->visited_stack) {
wota_stack_pop (enc);
}
}
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
if (JS_IsNull (enc->replacer)) return val;
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
JSValue args[2] = { key_val, val };
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
if (JS_IsException (result)) return val;
return result;
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key);
static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) {
JSContext *ctx = enc->ctx;
/* Root the input value to protect it during property enumeration */
JSGCRef val_ref, keys_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &keys_ref);
val_ref.val = val;
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
if (JS_IsException (keys_ref.val)) {
wota_write_sym (&enc->wb, WOTA_NULL);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
wota_write_sym (&enc->wb, WOTA_NULL);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
uint32_t plen = (uint32_t)plen64;
uint32_t non_function_count = 0;
/* Allocate GC-rooted arrays for props and keys */
JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen);
JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen);
for (uint32_t i = 0; i < plen; i++) {
JS_PushGCRef (ctx, &prop_refs[i]);
JS_PushGCRef (ctx, &key_refs[i]);
prop_refs[i].val = JS_NULL;
key_refs[i].val = JS_NULL;
}
for (uint32_t i = 0; i < plen; i++) {
/* Store key into its GCRef slot immediately so it's rooted before
JS_GetProperty can trigger GC and relocate the string. */
key_refs[i].val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key_refs[i].val);
if (!JS_IsFunction (prop_val)) {
if (i != non_function_count) {
key_refs[non_function_count].val = key_refs[i].val;
key_refs[i].val = JS_NULL;
}
prop_refs[non_function_count].val = prop_val;
non_function_count++;
} else {
key_refs[i].val = JS_NULL;
}
}
wota_write_record (&enc->wb, non_function_count);
for (uint32_t i = 0; i < non_function_count; i++) {
size_t klen;
const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val);
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
JS_FreeCString (ctx, prop_name);
}
/* Pop all GC refs in reverse order */
for (int i = plen - 1; i >= 0; i--) {
JS_PopGCRef (ctx, &key_refs[i]);
JS_PopGCRef (ctx, &prop_refs[i]);
}
sys_free (prop_refs);
sys_free (key_refs);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) {
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
replaced = wota_apply_replacer (enc, holder, key, val);
else
replaced = val;
int tag = JS_VALUE_GET_TAG (replaced);
switch (tag) {
case JS_TAG_INT: {
int32_t d;
JS_ToInt32 (ctx, &d, replaced);
wota_write_int_word (&enc->wb, d);
break;
}
case JS_TAG_FLOAT64: {
double d;
if (JS_ToFloat64 (ctx, &d, replaced) < 0) {
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
wota_write_float_word (&enc->wb, d);
break;
}
case JS_TAG_STRING: {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE);
break;
case JS_TAG_NULL:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
case JS_TAG_PTR: {
if (JS_IsText (replaced)) {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
if (buf_data == (void *)-1) {
return;
}
if (buf_len == 0) {
wota_write_blob (&enc->wb, 0, "");
} else {
wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
}
break;
}
if (JS_IsArray (replaced)) {
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
int64_t arr_len;
JS_GetLength (ctx, replaced, &arr_len);
wota_write_array (&enc->wb, arr_len);
for (int64_t i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
}
wota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
wota_write_sym (&enc->wb, WOTA_PRIVATE);
wota_encode_value (enc, adata, replaced, JS_NULL);
break;
}
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
if (!JS_IsException (result)) {
wota_encode_value (enc, result, holder, key);
} else
wota_write_sym (&enc->wb, WOTA_NULL);
wota_stack_pop (enc);
break;
}
encode_object_properties (enc, replaced, holder);
wota_stack_pop (enc);
break;
}
default:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
}
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
uint64_t first_word = *(uint64_t *)data_ptr;
int type = (int)(first_word & 0xffU);
switch (type) {
case WOTA_INT: {
long long val;
data_ptr = wota_read_int (&val, data_ptr);
*out_val = JS_NewInt64 (ctx, val);
break;
}
case WOTA_FLOAT: {
double d;
data_ptr = wota_read_float (&d, data_ptr);
*out_val = JS_NewFloat64 (ctx, d);
break;
}
case WOTA_SYM: {
int scode;
data_ptr = wota_read_sym (&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (ctx, &inner_ref);
JS_PushGCRef (ctx, &obj_ref2);
inner_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &inner_ref.val, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (ctx);
if (!JS_IsNull (ctx->actor_sym))
JS_SetPropertyKey (ctx, obj_ref2.val, ctx->actor_sym, inner_ref.val);
JS_CellStone (ctx, obj_ref2.val);
*out_val = obj_ref2.val;
JS_PopGCRef (ctx, &obj_ref2);
JS_PopGCRef (ctx, &inner_ref);
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0);
else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1);
else *out_val = JS_NULL;
break;
}
case WOTA_BLOB: {
long long blen;
char *bdata = NULL;
data_ptr = wota_read_blob (&blen, &bdata, data_ptr);
*out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0);
if (bdata) sys_free (bdata);
break;
}
case WOTA_TEXT: {
char *utf8 = NULL;
data_ptr = wota_read_text (&utf8, data_ptr);
*out_val = JS_NewString (ctx, utf8 ? utf8 : "");
if (utf8) sys_free (utf8);
break;
}
case WOTA_ARR: {
long long c;
data_ptr = wota_read_array (&c, data_ptr);
JSGCRef arr_ref;
JS_PushGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, c);
for (long long i = 0; i < c; i++) {
JSGCRef elem_ref;
JS_PushGCRef (ctx, &elem_ref);
elem_ref.val = JS_NULL;
JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i);
data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver);
JS_SetPropertyNumber (ctx, arr_ref.val, i, elem_ref.val);
JS_PopGCRef (ctx, &elem_ref);
}
*out_val = arr_ref.val;
JS_PopGCRef (ctx, &arr_ref);
break;
}
case WOTA_REC: {
long long c;
data_ptr = wota_read_record (&c, data_ptr);
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr);
if (!tkey) continue;
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (ctx, &prop_key_ref);
JS_PushGCRef (ctx, &sub_val_ref);
prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len);
sub_val_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver);
JS_SetPropertyStr (ctx, obj_ref.val, tkey, sub_val_ref.val);
JS_PopGCRef (ctx, &sub_val_ref);
JS_PopGCRef (ctx, &prop_key_ref);
sys_free (tkey);
}
*out_val = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
break;
}
default:
data_ptr += 8;
*out_val = JS_NULL;
break;
}
if (!JS_IsNull (reviver)) {
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
JSValue args[2] = { key_val, *out_val };
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
if (!JS_IsException (revived)) {
*out_val = revived;
}
}
return data_ptr;
}
void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) {
JSGCRef val_ref, rep_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &rep_ref);
val_ref.val = v;
rep_ref.val = replacer;
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = NULL;
enc->cycle = 0;
enc->replacer = rep_ref.val;
wota_buffer_init (&enc->wb, 16);
wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL);
if (enc->cycle) {
wota_stack_free (enc);
wota_buffer_free (&enc->wb);
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return NULL;
}
wota_stack_free (enc);
size_t total_bytes = enc->wb.size * sizeof (uint64_t);
void *wota = sys_realloc (enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return wota;
}
JSValue wota2value (JSContext *ctx, void *wota) {
JSGCRef holder_ref, result_ref;
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &result_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &holder_ref);
return result;
}
static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "wota.encode requires at least 1 argument");
size_t total_bytes;
void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes);
JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes);
sys_free (wota);
return ret;
}
static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]);
if (buf == (uint8_t *)-1) return JS_EXCEPTION;
if (!buf || len == 0) return JS_RaiseDisrupt (ctx, "No blob data present");
JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
char *data_ptr = (char *)buf;
JSGCRef result_ref, holder_ref, empty_key_ref;
JS_PushGCRef (ctx, &result_ref);
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &empty_key_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
empty_key_ref.val = JS_NewString (ctx, "");
decode_wota_value (ctx, data_ptr, &result_ref.val, holder_ref.val, empty_key_ref.val, reviver);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &empty_key_ref);
JS_PopGCRef (ctx, &holder_ref);
JS_PopGCRef (ctx, &result_ref);
return result;
}
static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF ("encode", 2, js_wota_encode),
JS_CFUNC_DEF ("decode", 2, js_wota_decode),
};
JSValue js_core_internal_wota_use (JSContext *ctx) {
JSGCRef exports_ref;
JS_PushGCRef (ctx, &exports_ref);
exports_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0]));
JSValue result = exports_ref.val;
JS_PopGCRef (ctx, &exports_ref);
return result;
}