core packages now split out
This commit is contained in:
431
internal/nota.c
Normal file
431
internal/nota.c
Normal file
@@ -0,0 +1,431 @@
|
||||
#define NOTA_IMPLEMENTATION
|
||||
#include "quickjs-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 = JS_DupValue (enc->ctx, 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_FreeValue (enc->ctx, node->ref.val);
|
||||
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 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_ref->val, 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;
|
||||
}
|
||||
|
||||
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] = { 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;
|
||||
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);
|
||||
JS_FreeValue (ctx, adata);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue (ctx, adata);
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user