Files
cell/source/qjs_wota.c
John Alanbrook ead61e648a
Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
changes to update to newer quickjs version
2025-05-01 12:50:03 -05:00

370 lines
12 KiB
C

#include "quickjs.h"
#include "wota.h"
#include <stdlib.h>
typedef struct WotaEncodeContext {
JSContext *ctx;
JSValue visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
} WotaEncodeContext;
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
JS_SetPropertyInt64(ctx, enc->visited_stack, len, JS_DupValue(ctx, val));
}
static void wota_stack_pop(WotaEncodeContext *enc)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
JS_SetPropertyStr(ctx, enc->visited_stack, "length", JS_NewUint32(ctx, len - 1));
}
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
for (int i = 0; i < len; i++) {
JSValue elem = JS_GetPropertyUint32(ctx, enc->visited_stack, i);
if (JS_IsObject(elem) && JS_IsObject(val))
if (JS_StrictEq(ctx, elem, val)) {
JS_FreeValue(ctx, elem);
return 1;
}
JS_FreeValue(ctx, elem);
}
return 0;
}
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val)
{
if (JS_IsUndefined(enc->replacer)) return JS_DupValue(enc->ctx, val);
JSValue args[2] = { JS_DupValue(enc->ctx, key), JS_DupValue(enc->ctx, val) };
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue(enc->ctx, args[0]);
JS_FreeValue(enc->ctx, args[1]);
if (JS_IsException(result)) return JS_DupValue(enc->ctx, val);
return result;
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key);
static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder)
{
JSContext *ctx = enc->ctx;
JSPropertyEnum *ptab;
uint32_t plen;
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, val, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
wota_write_sym(&enc->wb, WOTA_NULL);
return;
}
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
if (!JS_IsFunction(ctx, prop_val)) non_function_count++;
JS_FreeValue(ctx, prop_val);
}
wota_write_record(&enc->wb, non_function_count);
for (uint32_t i = 0; i < plen; i++) {
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
if (!JS_IsFunction(ctx, prop_val)) {
const char *prop_name = JS_AtomToCString(ctx, ptab[i].atom);
JSValue prop_key = JS_AtomToValue(ctx, ptab[i].atom);
wota_write_text(&enc->wb, prop_name);
wota_encode_value(enc, prop_val, val, prop_key);
JS_FreeCString(ctx, prop_name);
JS_FreeValue(ctx, prop_key);
}
JS_FreeValue(ctx, prop_val);
JS_FreeAtom(ctx, ptab[i].atom);
}
js_free(ctx, ptab);
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key)
{
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsUndefined(enc->replacer) && !JS_IsUndefined(key))
replaced = apply_replacer(enc, holder, key, val);
else
replaced = JS_DupValue(enc->ctx, val);
int tag = JS_VALUE_GET_TAG(replaced);
switch (tag) {
case JS_TAG_INT: {
double d;
JS_ToFloat64(ctx, &d, replaced);
wota_write_number(&enc->wb, d);
break;
}
case JS_TAG_FLOAT64:
case JS_TAG_BIG_INT: {
double d;
if (JS_ToFloat64(ctx, &d, replaced) < 0) {
wota_write_sym(&enc->wb, WOTA_NULL);
break;
}
wota_write_number(&enc->wb, d);
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString(ctx, replaced);
wota_write_text(&enc->wb, str ? str : "");
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:
case JS_TAG_UNDEFINED:
wota_write_sym(&enc->wb, WOTA_NULL);
break;
case JS_TAG_OBJECT: {
if (JS_IsArrayBuffer(ctx, replaced)) {
size_t buf_len;
void *buf_data = JS_GetArrayBuffer(ctx, &buf_len, replaced);
wota_write_blob(&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
break;
}
if (JS_IsArray(ctx, replaced)) {
if (wota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push(enc, replaced);
int arr_len = JS_ArrayLength(ctx, replaced);
wota_write_array(&enc->wb, arr_len);
for (int i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
JSValue elem_key = JS_NewInt32(ctx, i);
wota_encode_value(enc, elem_val, replaced, elem_key);
JS_FreeValue(ctx, elem_val);
JS_FreeValue(ctx, elem_key);
}
wota_stack_pop(enc);
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(ctx, to_json)) {
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
JS_FreeValue(ctx, to_json);
if (!JS_IsException(result)) {
wota_encode_value(enc, result, holder, key);
JS_FreeValue(ctx, result);
} else
wota_write_sym(&enc->wb, WOTA_NULL);
wota_stack_pop(enc);
break;
}
JS_FreeValue(ctx, to_json);
encode_object_properties(enc, replaced, holder);
wota_stack_pop(enc);
break;
}
default:
wota_write_sym(&enc->wb, WOTA_NULL);
break;
}
JS_FreeValue(ctx, replaced);
}
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_NULL) *out_val = JS_UNDEFINED;
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_UNDEFINED;
break;
}
case WOTA_BLOB: {
long long blen;
char *bdata = NULL;
data_ptr = wota_read_blob(&blen, &bdata, data_ptr);
*out_val = bdata ? JS_NewArrayBufferCopy(ctx, (uint8_t *)bdata, (size_t)blen) : JS_NewArrayBufferCopy(ctx, NULL, 0);
if (bdata) 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) free(utf8);
break;
}
case WOTA_ARR: {
long long c;
data_ptr = wota_read_array(&c, data_ptr);
JSValue arr = JS_NewArray(ctx);
for (long long i = 0; i < c; i++) {
JSValue elem_val = JS_UNDEFINED;
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, JS_NewInt32(ctx, i), reviver);
JS_SetPropertyUint32(ctx, arr, i, elem_val);
}
*out_val = arr;
break;
}
case WOTA_REC: {
long long c;
data_ptr = wota_read_record(&c, data_ptr);
JSValue obj = JS_NewObject(ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
data_ptr = wota_read_text(&tkey, data_ptr);
JSValue prop_key = tkey ? JS_NewString(ctx, tkey) : JS_UNDEFINED;
JSValue sub_val = JS_UNDEFINED;
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
if (tkey) JS_SetPropertyStr(ctx, obj, tkey, sub_val);
else JS_FreeValue(ctx, sub_val);
JS_FreeValue(ctx, prop_key);
if (tkey) free(tkey);
}
*out_val = obj;
break;
}
default:
data_ptr += 8;
*out_val = JS_UNDEFINED;
break;
}
if (!JS_IsUndefined(reviver)) {
JSValue args[2] = { JS_DupValue(ctx, key), JS_DupValue(ctx, *out_val) };
JSValue revived = JS_Call(ctx, reviver, holder, 2, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
if (!JS_IsException(revived)) {
JS_FreeValue(ctx, *out_val);
*out_val = revived;
} else
JS_FreeValue(ctx, revived);
}
return data_ptr;
}
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer)
{
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = JS_NewArray(ctx);
enc->cycle = 0;
enc->replacer = replacer;
wota_buffer_init(&enc->wb, 16);
wota_encode_value(enc, v, JS_UNDEFINED, JS_UNDEFINED);
if (enc->cycle) {
JS_FreeValue(ctx, enc->visited_stack);
wota_buffer_free(&enc->wb);
return NULL;
}
JS_FreeValue(ctx, enc->visited_stack);
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
void *wota = realloc(enc->wb.data, total_bytes);
return wota;
}
JSValue wota2value(JSContext *ctx, void *wota)
{
JSValue result = JS_UNDEFINED;
JSValue holder = JS_NewObject(ctx);
decode_wota_value(ctx, wota, &result, holder, JS_UNDEFINED, JS_UNDEFINED);
JS_FreeValue(ctx, holder);
return result;
}
static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = JS_NewArray(ctx);
enc->cycle = 0;
enc->replacer = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_UNDEFINED;
wota_buffer_init(&enc->wb, 16);
wota_encode_value(enc, argv[0], JS_UNDEFINED, JS_UNDEFINED);
if (enc->cycle) {
JS_FreeValue(ctx, enc->visited_stack);
wota_buffer_free(&enc->wb);
return JS_ThrowReferenceError(ctx, "Cannot encode cyclic object with wota");
}
JS_FreeValue(ctx, enc->visited_stack);
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
JSValue ret = JS_NewArrayBufferCopy(ctx, (uint8_t *)enc->wb.data, total_bytes);
wota_buffer_free(&enc->wb);
return ret;
}
static JSValue js_wota_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_UNDEFINED;
size_t len;
uint8_t *buf = JS_GetArrayBuffer(ctx, &len, argv[0]);
if (!buf) return JS_UNDEFINED;
JSValue reviver = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_UNDEFINED;
char *data_ptr = (char *)buf;
JSValue result = JS_UNDEFINED;
JSValue holder = JS_NewObject(ctx);
decode_wota_value(ctx, data_ptr, &result, holder, JS_NewString(ctx, ""), reviver);
JS_FreeValue(ctx, holder);
return result;
}
static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF("encode", 2, js_wota_encode),
JS_CFUNC_DEF("decode", 2, js_wota_decode),
};
static int js_wota_init(JSContext *ctx, JSModuleDef *m)
{
JS_SetModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return 0;
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_wota
#endif
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m = JS_NewCModule(ctx, module_name, js_wota_init);
if (!m) return NULL;
JS_AddModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return m;
}
JSValue js_wota_use(JSContext *ctx)
{
JSValue exports = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, exports, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return exports;
}