Files
cell/internal/nota.c

423 lines
13 KiB
C

#define NOTA_IMPLEMENTATION
#include "pit_internal.h"
#include "cell.h"
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
int64_t len;
JS_GetLength (ctx, arr, &len);
return (int)len;
}
typedef struct NotaVisitedNode {
JSGCRef ref;
struct NotaVisitedNode *next;
} NotaVisitedNode;
typedef struct NotaEncodeContext {
JSContext *ctx;
NotaVisitedNode *visited_list;
NotaBuffer nb;
int cycle;
JSGCRef *replacer_ref; /* pointer to GC-rooted ref */
} NotaEncodeContext;
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
JS_PushGCRef (enc->ctx, &node->ref);
node->ref.val = val;
node->next = enc->visited_list;
enc->visited_list = node;
}
static void nota_stack_pop (NotaEncodeContext *enc) {
NotaVisitedNode *node = enc->visited_list;
enc->visited_list = node->next;
JS_PopGCRef (enc->ctx, &node->ref);
sys_free (node);
}
static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = enc->visited_list;
while (node) {
if (JS_StrictEq (enc->ctx, node->ref.val, val))
return 1;
node = node->next;
}
return 0;
}
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return val;
JSValue args[2] = { key, val };
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
if (JS_IsException (result)) return val;
return result;
}
static 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);
sys_free (blob);
break;
case NOTA_TEXT:
nota = nota_read_text (&str, nota);
*tmp = JS_NewString (js, str);
sys_free (str);
break;
case NOTA_ARR:
nota = nota_read_array (&n, nota);
*tmp = JS_NewArrayLen (js, n);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver);
JS_SetPropertyNumber (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++) {
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (js, &prop_key_ref);
JS_PushGCRef (js, &sub_val_ref);
nota = nota_read_text (&str, nota);
prop_key_ref.val = JS_NewString (js, str);
sub_val_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &sub_val_ref.val, nota, *tmp, prop_key_ref.val, reviver);
JS_SetPropertyStr (js, *tmp, str, sub_val_ref.val);
JS_PopGCRef (js, &sub_val_ref);
JS_PopGCRef (js, &prop_key_ref);
sys_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) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (js, &inner_ref);
JS_PushGCRef (js, &obj_ref2);
inner_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &inner_ref.val, nota, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (js);
if (!JS_IsNull (js->actor_sym))
JS_SetPropertyKey (js, obj_ref2.val, js->actor_sym, inner_ref.val);
JS_CellStone (js, obj_ref2.val);
*tmp = obj_ref2.val;
JS_PopGCRef (js, &obj_ref2);
JS_PopGCRef (js, &inner_ref);
} 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] = { key, *tmp };
JSValue revived = JS_Call (js, reviver, holder, 2, args);
if (!JS_IsException (revived)) {
*tmp = revived;
} else {
}
}
return nota;
}
static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) {
JSContext *ctx = enc->ctx;
JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref;
JS_PushGCRef (ctx, &replaced_ref);
replaced_ref.val = nota_apply_replacer (enc, holder, key, val);
int tag = JS_VALUE_GET_TAG (replaced_ref.val);
switch (tag) {
case JS_TAG_INT:
case JS_TAG_FLOAT64: {
double d;
JS_ToFloat64 (ctx, &d, replaced_ref.val);
nota_write_number (&enc->nb, d);
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
if (JS_VALUE_GET_BOOL (replaced_ref.val)) 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_PTR: {
if (JS_IsText (replaced_ref.val)) {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced_ref.val)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val);
if (buf_data == (void *)-1) {
JS_PopGCRef (ctx, &replaced_ref);
return;
}
nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data);
break;
}
if (JS_IsArray (replaced_ref.val)) {
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
int arr_len = nota_get_arr_len (ctx, replaced_ref.val);
nota_write_array (&enc->nb, arr_len);
JS_PushGCRef (ctx, &elem_ref);
for (int i = 0; i < arr_len; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, replaced_ref.val, i);
JSValue elem_key = JS_NewInt32 (ctx, i);
nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key);
}
JS_PopGCRef (ctx, &elem_ref);
nota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
nota_write_sym (&enc->nb, NOTA_PRIVATE);
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
break;
}
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL);
if (!JS_IsException (result)) {
nota_encode_value (enc, result, holder, key);
} else {
nota_write_sym (&enc->nb, NOTA_NULL);
}
nota_stack_pop (enc);
break;
}
JS_PushGCRef (ctx, &keys_ref);
keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val);
if (JS_IsException (keys_ref.val)) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
uint32_t plen = (uint32_t)plen64;
JS_PushGCRef (ctx, &prop_ref);
JS_PushGCRef (ctx, &elem_ref);
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) non_function_count++;
}
nota_write_record (&enc->nb, non_function_count);
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) {
const char *prop_name = JS_ToCString (ctx, elem_ref.val);
nota_write_text (&enc->nb, prop_name ? prop_name : "");
nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val);
JS_FreeCString (ctx, prop_name);
}
}
JS_PopGCRef (ctx, &elem_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &keys_ref);
nota_stack_pop (enc);
break;
}
default:
nota_write_sym (&enc->nb, NOTA_NULL);
break;
}
JS_PopGCRef (ctx, &replaced_ref);
}
void *value2nota (JSContext *ctx, JSValue v) {
JSGCRef val_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = v;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = NULL;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
if (enc->cycle) {
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
nota_buffer_free (&enc->nb);
return NULL;
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
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;
JSGCRef holder_ref, key_ref, ret_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "nota.encode requires at least 1 argument");
JSGCRef val_ref, replacer_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &replacer_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = argv[0];
replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = &replacer_ref;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
JSValue ret;
if (enc->cycle) {
nota_buffer_free (&enc->nb);
ret = JS_RaiseDisrupt (ctx, "Tried to encode something to nota with a cycle.");
} else {
size_t total_len = enc->nb.size;
void *data_ptr = enc->nb.data;
ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len);
nota_buffer_free (&enc->nb);
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &replacer_ref);
JS_PopGCRef (ctx, &val_ref);
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 == (unsigned char *)-1) return JS_EXCEPTION;
if (!nota) return JS_NULL;
JSGCRef holder_ref, key_ref, ret_ref, reviver_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
JS_PushGCRef (js, &reviver_ref);
reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &reviver_ref);
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF ("encode", 1, js_nota_encode),
JS_CFUNC_DEF ("decode", 1, js_nota_decode),
};
JSValue js_core_internal_nota_use (JSContext *js) {
JSGCRef export_ref;
JS_PushGCRef (js, &export_ref);
export_ref.val = JS_NewObject (js);
JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry));
JSValue result = export_ref.val;
JS_PopGCRef (js, &export_ref);
return result;
}