// // qjs_wota.c // #include "quickjs.h" /* We define WOTA_IMPLEMENTATION so wota.h includes its implementation. */ #define WOTA_IMPLEMENTATION #include "wota.h" typedef struct WotaEncodeContext { JSContext *ctx; JSValue visitedStack; /* array for cycle detection */ WotaBuffer wb; /* dynamic buffer for building Wota data */ int cycle; } WotaEncodeContext; /* ---------------------------------------------------------------- CYCLE DETECTION (to avoid infinite recursion on circular objects) ---------------------------------------------------------------- */ static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val) { JSContext *ctx = enc->ctx; int len = JS_ArrayLength(ctx, enc->visitedStack); JS_SetPropertyInt64(ctx, enc->visitedStack, len, JS_DupValue(ctx, val)); } static void wota_stack_pop(WotaEncodeContext *enc) { JSContext *ctx = enc->ctx; int len = JS_ArrayLength(ctx, enc->visitedStack); JS_SetPropertyStr(ctx, enc->visitedStack, "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->visitedStack); for (int i = 0; i < len; i++) { JSValue elem = JS_GetPropertyUint32(ctx, enc->visitedStack, i); if (JS_IsObject(elem) && JS_IsObject(val)) { if (JS_VALUE_GET_OBJ(elem) == JS_VALUE_GET_OBJ(val)) { JS_FreeValue(ctx, elem); return 1; } } JS_FreeValue(ctx, elem); } return 0; } /* Forward declaration */ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val); /* ---------------------------------------------------------------- Encode object properties => Wota record ---------------------------------------------------------------- */ static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val) { 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; } wota_write_record(&enc->wb, plen); for (uint32_t i = 0; i < plen; i++) { /* property name => TEXT */ const char *propName = JS_AtomToCString(ctx, ptab[i].atom); wota_write_text(&enc->wb, propName); JS_FreeCString(ctx, propName); /* property value => wota_encode_value */ JSValue propVal = JS_GetProperty(ctx, val, ptab[i].atom); wota_encode_value(enc, propVal); JS_FreeValue(ctx, propVal); JS_FreeAtom(ctx, ptab[i].atom); } js_free(ctx, ptab); } /* ---------------------------------------------------------------- Main dispatcher for any JS value => Wota ---------------------------------------------------------------- */ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val) { JSContext *ctx = enc->ctx; int tag = JS_VALUE_GET_TAG(val); switch(tag) { case JS_TAG_INT: { double d; JS_ToFloat64(ctx, &d, val); wota_write_number(&enc->wb, d); return; } case JS_TAG_FLOAT64: case JS_TAG_BIG_INT: case JS_TAG_BIG_DECIMAL: case JS_TAG_BIG_FLOAT: { /* Convert to double if possible. If it's out of double range, you might need a fallback. QuickJS can handle bigint, etc. But let's just do double. */ double d; if (JS_ToFloat64(ctx, &d, val) < 0) { wota_write_sym(&enc->wb, WOTA_NULL); return; } wota_write_number(&enc->wb, d); return; } case JS_TAG_STRING: { const char *str = JS_ToCString(ctx, val); if (!str) { wota_write_text(&enc->wb, ""); return; } wota_write_text(&enc->wb, str); JS_FreeCString(ctx, str); return; } case JS_TAG_BOOL: { if (JS_VALUE_GET_BOOL(val)) { wota_write_sym(&enc->wb, WOTA_TRUE); } else { wota_write_sym(&enc->wb, WOTA_FALSE); } return; } case JS_TAG_NULL: case JS_TAG_UNDEFINED: wota_write_sym(&enc->wb, WOTA_NULL); return; case JS_TAG_OBJECT: { /* Check if it's an ArrayBuffer => blob */ if (JS_IsArrayBuffer(ctx, val)) { size_t bufLen; void *bufData = JS_GetArrayBuffer(ctx, &bufLen, val); wota_write_blob(&enc->wb, (unsigned long long)bufLen * 8, (const char *)bufData); return; } if (JS_IsArray(ctx, val)) { if (wota_stack_has(enc, val)) { enc->cycle = 1; return; } wota_stack_push(enc, val); int arrLen = JS_ArrayLength(ctx, val); wota_write_array(&enc->wb, arrLen); for (int i = 0; i < arrLen; i++) { JSValue elemVal = JS_GetPropertyUint32(ctx, val, i); wota_encode_value(enc, elemVal); JS_FreeValue(ctx, elemVal); } wota_stack_pop(enc); return; } /* Otherwise => record */ if (wota_stack_has(enc, val)) { enc->cycle = 1; return; } wota_stack_push(enc, val); encode_object_properties(enc, val); wota_stack_pop(enc); return; } default: wota_write_sym(&enc->wb, WOTA_NULL); return; } } /* ---------------------------------------------------------------- Public JS function: wota.encode(value) => ArrayBuffer ---------------------------------------------------------------- */ static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "wota.encode requires 1 argument"); } WotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; enc->visitedStack = JS_NewArray(ctx); enc->cycle = 0; wota_buffer_init(&enc->wb, 16); wota_encode_value(enc, argv[0]); if (enc->cycle) { JS_FreeValue(ctx, enc->visitedStack); wota_buffer_free(&enc->wb); return JS_ThrowTypeError(ctx, "Cannot encode cyclic object with wota"); } JS_FreeValue(ctx, enc->visitedStack); /* Prepare the ArrayBuffer result */ size_t word_count = enc->wb.size; size_t total_bytes = word_count * sizeof(uint64_t); uint8_t *raw = (uint8_t *)enc->wb.data; JSValue ret = JS_NewArrayBufferCopy(ctx, raw, total_bytes); wota_buffer_free(&enc->wb); return ret; } // A dedicated function that decodes one Wota value from data_ptr, // returns a JSValue, and advances data_ptr. // We return the updated pointer (like wota_read_* functions do). static char* decode_wota_value(JSContext *ctx, char *data_ptr, char *end_ptr, JSValue *outVal) { if ((end_ptr - data_ptr) < 8) { // Not enough data to read; just set undefined *outVal = JS_UNDEFINED; return data_ptr; } 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); *outVal = JS_NewInt64(ctx, val); break; } case WOTA_FLOAT: { double d; data_ptr = wota_read_float(&d, data_ptr); *outVal = JS_NewFloat64(ctx, d); break; } case WOTA_SYM: { int scode; data_ptr = wota_read_sym(&scode, data_ptr); if (scode == WOTA_NULL) { *outVal = JS_UNDEFINED; } else if (scode == WOTA_FALSE) { *outVal = JS_NewBool(ctx, 0); } else if (scode == WOTA_TRUE) { *outVal = JS_NewBool(ctx, 1); } else { // other symbol codes => undefined or handle them *outVal = JS_UNDEFINED; } break; } case WOTA_BLOB: { long long blen; char *bdata = NULL; data_ptr = wota_read_blob(&blen, &bdata, data_ptr); if (bdata) { *outVal = JS_NewArrayBufferCopy(ctx, (uint8_t*)bdata, (size_t)blen); free(bdata); } else { *outVal = JS_NewArrayBufferCopy(ctx, NULL, 0); } break; } case WOTA_TEXT: { char *utf8 = NULL; data_ptr = wota_read_text(&utf8, data_ptr); if (utf8) { *outVal = JS_NewString(ctx, utf8); free(utf8); } else { *outVal = JS_NewString(ctx, ""); } break; } case WOTA_ARR: { // Recursively decode the array 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 elemVal = JS_UNDEFINED; data_ptr = decode_wota_value(ctx, data_ptr, end_ptr, &elemVal); JS_SetPropertyUint32(ctx, arr, i, elemVal); } *outVal = arr; break; } case WOTA_REC: { // Recursively decode the record long long c; data_ptr = wota_read_record(&c, data_ptr); JSValue obj = JS_NewObject(ctx); for (long long i = 0; i < c; i++) { // read the key char *tkey = NULL; data_ptr = wota_read_text(&tkey, data_ptr); // read the value JSValue subVal = JS_UNDEFINED; data_ptr = decode_wota_value(ctx, data_ptr, end_ptr, &subVal); if (tkey) { JS_SetPropertyStr(ctx, obj, tkey, subVal); free(tkey); } else { JS_FreeValue(ctx, subVal); } } *outVal = obj; break; } default: // unknown => skip data_ptr += 8; *outVal = JS_UNDEFINED; break; } return data_ptr; } 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; } char *data_ptr = (char *)buf; char *end_ptr = data_ptr + len; JSValue result = JS_UNDEFINED; decode_wota_value(ctx, data_ptr, end_ptr, &result); return result; } /* ---------------------------------------------------------------- Expose wota.encode / wota.decode to QuickJS ---------------------------------------------------------------- */ static const JSCFunctionListEntry js_wota_funcs[] = { JS_CFUNC_DEF("encode", 1, js_wota_encode), JS_CFUNC_DEF("decode", 1, js_wota_decode), }; /* For module usage */ 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; } /* An optional helper function if you're using it outside the module system */ 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; }