423 lines
13 KiB
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;
|
|
}
|