Files
cell/source/qjs_wota.c
2025-03-03 18:35:28 -06:00

394 lines
12 KiB
C

//
// 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;
}