#define BLOB_IMPLEMENTATION #include "blob.h" #include "qjs_blob.h" // Get countof from macros if not defined #ifndef countof #define countof(x) (sizeof(x)/sizeof((x)[0])) #endif // Free function for blob void blob_free(JSRuntime *rt, blob *b) { if (b) { blob_destroy(b); } } // Use QJSCLASS macro to generate class boilerplate QJSCLASS(blob,) // Constructor function for blob static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { blob *bd = NULL; // new Blob() if (argc == 0) { // empty antestone blob bd = blob_new(0); } // new Blob(capacity) else if (argc == 1 && JS_IsNumber(argv[0])) { int64_t capacity_bits; if (JS_ToInt64(ctx, &capacity_bits, argv[0]) < 0) { return JS_EXCEPTION; } if (capacity_bits < 0) capacity_bits = 0; bd = blob_new((size_t)capacity_bits); } // new Blob(length, logical/random) else if (argc == 2 && JS_IsNumber(argv[0])) { int64_t length_bits; if (JS_ToInt64(ctx, &length_bits, argv[0]) < 0) { return JS_EXCEPTION; } if (length_bits < 0) length_bits = 0; if (JS_IsBool(argv[1])) { // Fill with all 0s or all 1s int is_one = JS_ToBool(ctx, argv[1]); bd = blob_new_with_fill((size_t)length_bits, is_one); } else if (JS_IsFunction(ctx, argv[1])) { // Random function provided - call it for each bit bd = blob_new((size_t)length_bits); if (bd) { bd->bit_length = length_bits; for (size_t i = 0; i < length_bits; i++) { JSValue randval = JS_Call(ctx, argv[1], JS_UNDEFINED, 0, NULL); if (JS_IsException(randval)) { blob_destroy(bd); return JS_EXCEPTION; } int64_t fitval; JS_ToInt64(ctx, &fitval, randval); JS_FreeValue(ctx, randval); // Set bit based on random value size_t byte_idx = i / 8; size_t bit_idx = i % 8; if (fitval & 1) { bd->data[byte_idx] |= (1 << bit_idx); } else { bd->data[byte_idx] &= ~(1 << bit_idx); } } } } else { return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function"); } } // new Blob(blob, from, to) else if (argc >= 1 && JS_IsObject(argv[0])) { // we try copying from another blob if it's of the same class blob *src = js2blob(ctx, argv[0]); if (!src) { return JS_ThrowTypeError(ctx, "Blob constructor: argument 1 not a blob"); } int64_t from = 0, to = (int64_t)src->bit_length; if (argc >= 2 && JS_IsNumber(argv[1])) { JS_ToInt64(ctx, &from, argv[1]); if (from < 0) from = 0; } if (argc >= 3 && JS_IsNumber(argv[2])) { JS_ToInt64(ctx, &to, argv[2]); if (to < from) to = from; if (to > (int64_t)src->bit_length) to = (int64_t)src->bit_length; } bd = blob_new_from_blob(src, (size_t)from, (size_t)to); } // else fail else { return JS_ThrowTypeError(ctx, "Blob constructor: invalid arguments"); } if (!bd) { return JS_ThrowOutOfMemory(ctx); } return blob2js(ctx, bd); } // blob.write_bit(logical) static JSValue js_blob_write_bit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_bit(logical) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_bit: not called on a blob"); } // Handle numeric 0/1 or boolean int bit_val; if (JS_IsNumber(argv[0])) { int32_t num; JS_ToInt32(ctx, &num, argv[0]); if (num != 0 && num != 1) { return JS_ThrowTypeError(ctx, "write_bit: value must be true, false, 0, or 1"); } bit_val = num; } else { bit_val = JS_ToBool(ctx, argv[0]); } if (blob_write_bit(bd, bit_val) < 0) { return JS_ThrowTypeError(ctx, "write_bit: cannot write (maybe stone or OOM)"); } return JS_UNDEFINED; } // blob.write_blob(second_blob) static JSValue js_blob_write_blob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_blob(second_blob) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_blob: not called on a blob"); } blob *second = js2blob(ctx, argv[0]); if (!second) { return JS_ThrowTypeError(ctx, "write_blob: argument must be a blob"); } if (blob_write_blob(bd, second) < 0) { return JS_ThrowTypeError(ctx, "write_blob: cannot write to stone blob or OOM"); } return JS_UNDEFINED; } // blob.write_dec64(number) static JSValue js_blob_write_dec64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_dec64(number) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_dec64: not called on a blob"); } // Get the number as a double and convert to DEC64 double d; if (JS_ToFloat64(ctx, &d, argv[0]) < 0) { return JS_EXCEPTION; } if (blob_write_dec64(bd, d) < 0) { return JS_ThrowTypeError(ctx, "write_dec64: cannot write to stone blob or OOM"); } return JS_UNDEFINED; } // blob.write_fit(fit, length) static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "write_fit(fit, length) requires 2 arguments"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_fit: not called on a blob"); } int64_t fit; int32_t length; if (JS_ToInt64(ctx, &fit, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &length, argv[1]) < 0) return JS_EXCEPTION; if (blob_write_fit(bd, fit, length) < 0) { return JS_ThrowTypeError(ctx, "write_fit: cannot write (stone blob, OOM, or value out of range)"); } return JS_UNDEFINED; } // blob.write_kim(fit) static JSValue js_blob_write_kim(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_kim(fit) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_kim: not called on a blob"); } // Handle number or single character string int64_t value; if (JS_IsString(argv[0])) { const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; // Get first UTF-32 character // For simplicity, assuming ASCII for now if (strlen(str) == 0) { JS_FreeCString(ctx, str); return JS_ThrowTypeError(ctx, "write_kim: empty string"); } value = (unsigned char)str[0]; JS_FreeCString(ctx, str); } else { if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; } if (blob_write_kim(bd, value) < 0) { return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM"); } return JS_UNDEFINED; } // blob.write_pad(block_size) static JSValue js_blob_write_pad(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_pad(block_size) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_pad: not called on a blob"); } int32_t block_size; if (JS_ToInt32(ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_pad(bd, block_size) < 0) { return JS_ThrowTypeError(ctx, "write_pad: cannot write (stone blob, OOM, or invalid block size)"); } return JS_UNDEFINED; } // blob.write_text(text) static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_text(text) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_text: not called on a blob"); } const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; int result = blob_write_text(bd, str); JS_FreeCString(ctx, str); if (result < 0) { return JS_ThrowTypeError(ctx, "write_text: cannot write to stone blob or OOM"); } return JS_UNDEFINED; } // blob.write_text_raw(text) static JSValue js_blob_write_text_raw(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "write_text_raw(text) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "write_text_raw: not called on a blob"); } const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; int result = blob_write_text_raw(bd, str); JS_FreeCString(ctx, str); if (result < 0) { return JS_ThrowTypeError(ctx, "write_text_raw: cannot write to stone blob or OOM"); } return JS_UNDEFINED; } // blob.read_logical(from) static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "read_logical(from) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_logical: not called on a blob"); } int64_t pos; if (JS_ToInt64(ctx, &pos, argv[0]) < 0) { return JS_EXCEPTION; } if (pos < 0) { return JS_NULL; // out of range } int bit_val; if (blob_read_bit(bd, (size_t)pos, &bit_val) < 0) { return JS_NULL; // not stone or out of range } return JS_NewBool(ctx, bit_val); } // blob.read_blob(from, to) static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_blob: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_blob: blob must be stone"); } int64_t from = 0; int64_t to = bd->bit_length; if (argc >= 1) { if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) from = 0; } if (argc >= 2) { if (JS_ToInt64(ctx, &to, argv[1]) < 0) return JS_EXCEPTION; if (to > (int64_t)bd->bit_length) to = bd->bit_length; } blob *new_bd = blob_read_blob(bd, from, to); if (!new_bd) { return JS_ThrowOutOfMemory(ctx); } return blob2js(ctx, new_bd); } // blob.read_dec64(from) static JSValue js_blob_read_dec64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "read_dec64(from) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_dec64: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_dec64: blob must be stone"); } int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; double d; if (blob_read_dec64(bd, from, &d) < 0) { return JS_ThrowRangeError(ctx, "read_dec64: out of range"); } return JS_NewFloat64(ctx, d); } // blob.read_fit(from, length) static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "read_fit(from, length) requires 2 arguments"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_fit: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_fit: blob must be stone"); } int64_t from; int32_t length; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &length, argv[1]) < 0) return JS_EXCEPTION; int64_t value; if (blob_read_fit(bd, from, length, &value) < 0) { return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length"); } return JS_NewInt64(ctx, value); } // blob.read_kim(from) static JSValue js_blob_read_kim(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "read_kim(from) requires 1 argument"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_kim: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_kim: blob must be stone"); } int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; int64_t value; size_t bits_read; if (blob_read_kim(bd, from, &value, &bits_read) < 0) { return JS_ThrowRangeError(ctx, "read_kim: out of range or invalid kim encoding"); } // Return an object with the value and the number of bits read JSValue obj = JS_NewObject(ctx); JS_SetPropertyStr(ctx, obj, "value", JS_NewInt64(ctx, value)); JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read)); return obj; } // blob.read_text(from) static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_text: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_text: blob must be stone"); } int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; char *text; size_t bits_read; if (blob_read_text(bd, from, &text, &bits_read) < 0) { return JS_ThrowRangeError(ctx, "read_text: out of range or invalid encoding"); } JSValue result = JS_NewString(ctx, text); free(text); // Return object with text and total bits read JSValue obj = JS_NewObject(ctx); JS_SetPropertyStr(ctx, obj, "text", result); JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read)); return obj; } // blob.read_text_raw(from) static JSValue js_blob_read_text_raw(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "read_text_raw: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_text_raw: blob must be stone"); } int64_t from = 0; if (argc >= 1) { if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; } char *text; size_t bits_read; if (blob_read_text_raw(bd, from, &text, &bits_read) < 0) { return JS_ThrowRangeError(ctx, "read_text_raw: out of range or not byte-aligned"); } JSValue result = JS_NewString(ctx, text); free(text); // Return object with text and total bits read JSValue obj = JS_NewObject(ctx); JS_SetPropertyStr(ctx, obj, "text", result); JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read)); return obj; } // blob.pad?(from, block_size) static JSValue js_blob_pad_q(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) { return JS_ThrowTypeError(ctx, "pad?(from, block_size) requires 2 arguments"); } blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "pad?: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "pad?: blob must be stone"); } int64_t from; int32_t block_size; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; return JS_NewBool(ctx, blob_pad_check(bd, from, block_size)); } // blob.stone() static JSValue js_blob_stone(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "stone: not called on a blob"); } if (!bd->is_stone) { blob_make_stone(bd); } return JS_UNDEFINED; } // blob.length getter // Return number of bits in the blob static JSValue js_blob_get_length(JSContext *ctx, JSValueConst this_val, int magic) { blob *bd = js2blob(ctx, this_val); if (!bd) { return JS_ThrowTypeError(ctx, "length: not called on a blob"); } return JS_NewInt64(ctx, bd->bit_length); } // Static kim_length function static JSValue js_blob_kim_length(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "kim_length(value) requires 1 argument"); } if (JS_IsBool(argv[0])) { return JS_NewInt32(ctx, 1); } if (JS_IsNumber(argv[0])) { int64_t value; if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; return JS_NewInt32(ctx, kim_length_for_fit(value)); } if (JS_IsString(argv[0])) { const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; size_t len = strlen(str); // Length encoding + each character encoding int total_bits = kim_length_for_fit(len); for (size_t i = 0; i < len; i++) { total_bits += kim_length_for_fit((unsigned char)str[i]); } JS_FreeCString(ctx, str); return JS_NewInt32(ctx, total_bits); } return JS_NULL; } // ----------------------------------------------------------------------------- // Exports list // ----------------------------------------------------------------------------- static const JSCFunctionListEntry js_blob_funcs[] = { // Write methods JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit), JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob), JS_CFUNC_DEF("write_dec64", 1, js_blob_write_dec64), JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF("write_kim", 1, js_blob_write_kim), JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), JS_CFUNC_DEF("write_text", 1, js_blob_write_text_raw), // Read methods JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical), JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob), JS_CFUNC_DEF("read_dec64", 1, js_blob_read_dec64), JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit), JS_CFUNC_DEF("read_kim", 1, js_blob_read_kim), JS_CFUNC_DEF("read_text", 1, js_blob_read_text_raw), JS_CFUNC_DEF("pad?", 2, js_blob_pad_q), // Other methods JS_CFUNC_DEF("stone", 0, js_blob_stone), // Length property getter JS_CGETSET_DEF("length", js_blob_get_length, NULL), }; // ----------------------------------------------------------------------------- // js_blob_use(ctx) for easy embedding: returns an object with the blob functions // ----------------------------------------------------------------------------- JSValue js_blob_use(JSContext *js) { // Register the blob class QJSCLASSPREP_FUNCS(blob); // Create the constructor function JSValue ctor = JS_NewCFunction2(js, js_blob_constructor, "blob", 3, JS_CFUNC_constructor, 0); // Set the prototype on the constructor JSValue proto = JS_GetClassProto(js, js_blob_id); JS_SetConstructor(js, ctor, proto); JS_FreeValue(js, proto); // Add static kim_length method to constructor JS_SetPropertyStr(js, ctor, "kim_length", JS_NewCFunction(js, js_blob_kim_length, "kim_length", 1)); return ctor; } JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes) { blob *b = blob_new(bytes*8); memcpy(b->data, data, bytes); b->bit_length = bytes * 8; // Set the actual length in bits blob_make_stone(b); return blob2js(js, b); } void *js_get_blob_data(JSContext *js, size_t *size, JSValue v) { blob *b = js2blob(js, v); if (!b || !b->is_stone) return NULL; *size = (b->bit_length + 7) / 8; // Return actual byte size based on bit length return b->data; } int js_is_blob(JSContext *js, JSValue v) { blob *b = js2blob(js,v); if (b) return 1; return 0; }