#include "qjs_nota.h" #include "qjs_blob.h" #define NOTA_IMPLEMENTATION #include "nota.h" typedef struct NotaEncodeContext { JSContext *ctx; JSValue visitedStack; NotaBuffer nb; int cycle; JSValue replacer; } NotaEncodeContext; static void nota_stack_push(NotaEncodeContext *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 nota_stack_pop(NotaEncodeContext *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 nota_stack_has(NotaEncodeContext *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_StrictEq(ctx, elem, val)) { JS_FreeValue(ctx, elem); return 1; } } JS_FreeValue(ctx, elem); } return 0; } static JSValue apply_replacer(NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) { if (JS_IsNull(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; } char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) { int type = nota_type(nota); JSValue ret2; long long n; double d; int b; char *str; uint8_t *blob; switch(type) { case NOTA_BLOB: nota = nota_read_blob(&n, (char**)&blob, nota); *tmp = js_new_blob_stoned_copy(js, blob, n); free(blob); break; case NOTA_TEXT: nota = nota_read_text(&str, nota); *tmp = JS_NewString(js, str); free(str); break; case NOTA_ARR: nota = nota_read_array(&n, nota); *tmp = JS_NewArray(js); for (int i = 0; i < n; i++) { nota = js_do_nota_decode(js, &ret2, nota, *tmp, JS_NewInt32(js, i), reviver); JS_SetPropertyInt64(js, *tmp, i, ret2); } break; case NOTA_REC: nota = nota_read_record(&n, nota); *tmp = JS_NewObject(js); for (int i = 0; i < n; i++) { nota = nota_read_text(&str, nota); JSValue prop_key = JS_NewString(js, str); nota = js_do_nota_decode(js, &ret2, nota, *tmp, prop_key, reviver); JS_SetPropertyStr(js, *tmp, str, ret2); JS_FreeValue(js, prop_key); free(str); } break; case NOTA_INT: nota = nota_read_int(&n, nota); *tmp = JS_NewInt64(js, n); break; case NOTA_SYM: nota = nota_read_sym(&b, nota); if (b == NOTA_PRIVATE) { JSValue inner; nota = js_do_nota_decode(js, &inner, nota, holder, JS_NULL, reviver); JSValue obj = JS_NewObject(js); cell_rt *crt = JS_GetContextOpaque(js); JS_SetProperty(js, obj, crt->actor_sym, inner); *tmp = obj; } else { switch(b) { case NOTA_NULL: *tmp = JS_NULL; break; case NOTA_FALSE: *tmp = JS_NewBool(js, 0); break; case NOTA_TRUE: *tmp = JS_NewBool(js, 1); break; default: *tmp = JS_NULL; break; } } break; default: case NOTA_FLOAT: nota = nota_read_float(&d, nota); *tmp = JS_NewFloat64(js, d); break; } if (!JS_IsNull(reviver)) { JSValue args[2] = { JS_DupValue(js, key), JS_DupValue(js, *tmp) }; JSValue revived = JS_Call(js, reviver, holder, 2, args); JS_FreeValue(js, args[0]); JS_FreeValue(js, args[1]); if (!JS_IsException(revived)) { JS_FreeValue(js, *tmp); *tmp = revived; } else { JS_FreeValue(js, revived); } } return nota; } static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) { JSContext *ctx = enc->ctx; JSValue replaced = apply_replacer(enc, holder, key, val); int tag = JS_VALUE_GET_TAG(replaced); switch (tag) { case JS_TAG_INT: { double d; JS_ToFloat64(ctx, &d, replaced); nota_write_number(&enc->nb, d); break; } case JS_TAG_FLOAT64: { const char *str = JS_ToCString(ctx, replaced); nota_write_number_str(&enc->nb, str); JS_FreeCString(ctx, str); break; } case JS_TAG_STRING: { const char *str = JS_ToCString(ctx, replaced); nota_write_text(&enc->nb, str); JS_FreeCString(ctx, str); break; } case JS_TAG_BOOL: if (JS_VALUE_GET_BOOL(replaced)) nota_write_sym(&enc->nb, NOTA_TRUE); else nota_write_sym(&enc->nb, NOTA_FALSE); break; case JS_TAG_NULL: nota_write_sym(&enc->nb, NOTA_NULL); break; case JS_TAG_OBJECT: { if (js_is_blob(ctx, replaced)) { size_t buf_len; void *buf_data = js_get_blob_data(ctx, &buf_len, replaced); nota_write_blob(&enc->nb, (unsigned long long)buf_len * 8, (const char*)buf_data); break; } if (JS_IsArray(ctx, replaced)) { if (nota_stack_has(enc, replaced)) { enc->cycle = 1; break; } nota_stack_push(enc, replaced); int arr_len = JS_ArrayLength(ctx, replaced); nota_write_array(&enc->nb, 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); nota_encode_value(enc, elem_val, replaced, elem_key); JS_FreeValue(ctx, elem_val); JS_FreeValue(ctx, elem_key); } nota_stack_pop(enc); break; } cell_rt *crt = JS_GetContextOpaque(ctx); JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym); if (!JS_IsNull(adata)) { nota_write_sym(&enc->nb, NOTA_PRIVATE); nota_encode_value(enc, adata, replaced, JS_NULL); JS_FreeValue(ctx, adata); break; } JS_FreeValue(ctx, adata); if (nota_stack_has(enc, replaced)) { enc->cycle = 1; break; } nota_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)) { nota_encode_value(enc, result, holder, key); JS_FreeValue(ctx, result); } else { nota_write_sym(&enc->nb, NOTA_NULL); } nota_stack_pop(enc); break; } JS_FreeValue(ctx, to_json); JSPropertyEnum *ptab; uint32_t plen; if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, replaced, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) { nota_write_sym(&enc->nb, NOTA_NULL); nota_stack_pop(enc); break; } uint32_t non_function_count = 0; for (uint32_t i = 0; i < plen; i++) { JSValue prop_val = JS_GetProperty(ctx, replaced, ptab[i].atom); if (!JS_IsFunction(ctx, prop_val)) non_function_count++; JS_FreeValue(ctx, prop_val); } nota_write_record(&enc->nb, non_function_count); for (uint32_t i = 0; i < plen; i++) { JSValue prop_val = JS_GetProperty(ctx, replaced, 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); nota_write_text(&enc->nb, prop_name); nota_encode_value(enc, prop_val, replaced, 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); nota_stack_pop(enc); break; } default: nota_write_sym(&enc->nb, NOTA_NULL); break; } JS_FreeValue(ctx, replaced); } void *value2nota(JSContext *ctx, JSValue v) { NotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; enc->visitedStack = JS_NewArray(ctx); enc->cycle = 0; enc->replacer = JS_NULL; nota_buffer_init(&enc->nb, 128); nota_encode_value(enc, v, JS_NULL, JS_NewString(ctx, "")); if (enc->cycle) { JS_FreeValue(ctx, enc->visitedStack); nota_buffer_free(&enc->nb); return NULL; } JS_FreeValue(ctx, enc->visitedStack); void *data_ptr = enc->nb.data; enc->nb.data = NULL; nota_buffer_free(&enc->nb); return data_ptr; } JSValue nota2value(JSContext *js, void *nota) { if (!nota) return JS_NULL; JSValue ret; JSValue holder = JS_NewObject(js); js_do_nota_decode(js, &ret, nota, holder, JS_NewString(js, ""), JS_NULL); JS_FreeValue(js, holder); return ret; } static JSValue js_nota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(ctx, "nota.encode requires at least 1 argument"); NotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; enc->visitedStack = JS_NewArray(ctx); enc->cycle = 0; enc->replacer = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_NULL; nota_buffer_init(&enc->nb, 128); nota_encode_value(enc, argv[0], JS_NULL, JS_NewString(ctx, "")); if (enc->cycle) { JS_FreeValue(ctx, enc->visitedStack); nota_buffer_free(&enc->nb); return JS_ThrowReferenceError(ctx, "Tried to encode something to nota with a cycle."); } JS_FreeValue(ctx, enc->visitedStack); size_t total_len = enc->nb.size; void *data_ptr = enc->nb.data; JSValue ret = js_new_blob_stoned_copy(ctx, (uint8_t*)data_ptr, total_len); nota_buffer_free(&enc->nb); return ret; } static JSValue js_nota_decode(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; size_t len; unsigned char *nota = js_get_blob_data(js, &len, argv[0]); if (!nota) return JS_NULL; JSValue reviver = (argc > 1 && JS_IsFunction(js, argv[1])) ? argv[1] : JS_NULL; JSValue ret; JSValue holder = JS_NewObject(js); js_do_nota_decode(js, &ret, (char*)nota, holder, JS_NewString(js, ""), reviver); JS_FreeValue(js, holder); return ret; } static const JSCFunctionListEntry js_nota_funcs[] = { JS_CFUNC_DEF("encode", 1, js_nota_encode), JS_CFUNC_DEF("decode", 1, js_nota_decode), }; JSValue js_nota_use(JSContext *js) { JSValue export = JS_NewObject(js); JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry)); return export; }