#define BLOB_IMPLEMENTATION #include "blob.h" #include "cell.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 and use up to 56 bits at a time */ size_t bytes = (length_bits + 7) / 8; bd = blob_new((size_t)length_bits); if (bd) { bd->length = length_bits; /* Ensure the backing storage starts out zeroed */ memset(bd->data, 0, bytes); size_t bits_written = 0; while (bits_written < length_bits) { JSValue randval = JS_Call(ctx, argv[1], JS_NULL, 0, NULL); if (JS_IsException(randval)) { blob_destroy(bd); return JS_EXCEPTION; } int64_t fitval; JS_ToInt64(ctx, &fitval, randval); JS_FreeValue(ctx, randval); /* Extract up to 56 bits from the random value */ size_t bits_to_use = length_bits - bits_written; if (bits_to_use > 52) bits_to_use = 52; /* Write bits from the random value */ for (size_t j = 0; j < bits_to_use; j++) { size_t bit_pos = bits_written + j; size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (fitval & (1LL << j)) bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); else bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); } bits_written += bits_to_use; } } } 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->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->length) to = (int64_t)src->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); } JSValue ret = blob2js(ctx, bd); // Ensure the returned object's prototype is set correctly for instanceof JSValue ctor_proto = JS_GetPropertyStr(ctx, new_target, "prototype"); if (!JS_IsException(ctor_proto)) { JS_SetPrototype(ctx, ret, ctor_proto); } JS_FreeValue(ctx, ctor_proto); return ret; } // 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_NULL; } // 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_NULL; } // blob.write_dec64(number) static JSValue js_blob_write_number(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_NULL; } // blob.write_fit(value, len) static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_ThrowTypeError(ctx, "write_fit(value, len) requires 2 arguments"); blob *bd = js2blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "write_fit: not called on a blob"); int64_t value; int32_t len; if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (blob_write_fit(bd, value, len) < 0) { return JS_ThrowTypeError(ctx, "write_fit: value doesn't fit in specified bits, stone blob, or OOM"); } return JS_NULL; } // blob.write_kim(fit) static JSValue js_blob_write_text(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 const char *str = JS_ToCString(ctx, argv[0]); if (blob_write_text(bd, str) < 0) { JS_FreeCString(ctx,str); return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM"); } JS_FreeCString(ctx,str); return JS_NULL; } // 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_NULL; } // 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_ThrowInternalError(ctx, "must provide a positive bit"); } if (pos < 0) { return JS_ThrowRangeError(ctx, "read_logical: position must be non-negative"); } int bit_val; if (blob_read_bit(bd, (size_t)pos, &bit_val) < 0) { return JS_ThrowTypeError(ctx, "read_logical: blob must be stone"); } 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->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->length) to = bd->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_number(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_number: not called on a blob"); } if (!bd->is_stone) { return JS_ThrowTypeError(ctx, "read_number: blob must be stone"); } double from; if (JS_ToFloat64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) return JS_ThrowRangeError(ctx, "read_number: position must be non-negative"); double d; if (blob_read_dec64(bd, from, &d) < 0) { return JS_ThrowRangeError(ctx, "read_number: out of range"); } return JS_NewFloat64(ctx, d); } // blob.read_fit(from, len) 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, len) 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 len; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (from < 0) { return JS_ThrowRangeError(ctx, "read_fit: position must be non-negative"); } int64_t value; if (blob_read_fit(bd, from, len, &value) < 0) { return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length"); } return JS_NewInt64(ctx, value); } // 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 result; } // 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_NULL; } static JSValue js_blob_stonep(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"); } return JS_NewBool(ctx, bd->is_stone); } // 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->length); } // ----------------------------------------------------------------------------- // 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_number", 1, js_blob_write_number), JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF("write_text", 1, js_blob_write_text), JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), // 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_number", 1, js_blob_read_number), JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit), JS_CFUNC_DEF("read_text", 1, js_blob_read_text), JS_CFUNC_DEF("pad?", 2, js_blob_pad_q), // Other methods JS_CFUNC_DEF("stone", 0, js_blob_stone), JS_CFUNC_DEF("stonep", 0, js_blob_stonep), // 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); // Explicitly set the prototype property to ensure instanceof works JS_SetPropertyStr(js, ctor, "__prototype__", JS_DupValue(js, proto)); JS_FreeValue(js, proto); 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->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->length + 7) / 8; // Return actual byte size based on bit length return b->data; } void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v) { blob *b = js2blob(js, v); if (!b || !b->is_stone) return NULL; *bits = b->length; return b->data; } int js_is_blob(JSContext *js, JSValue v) { blob *b = js2blob(js,v); if (b) return 1; return 0; }