538 lines
16 KiB
C
538 lines
16 KiB
C
#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 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);
|
||
}
|
||
|
||
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_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);
|
||
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;
|
||
}
|
||
|
||
int js_is_blob(JSContext *js, JSValue v)
|
||
{
|
||
blob *b = js2blob(js,v);
|
||
if (b) return 1;
|
||
return 0;
|
||
}
|