Files
cell/source/qjs_blob.c

455 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 */
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);
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;
}
int32_t fitval;
JS_ToInt32(ctx, &fitval, randval);
JS_FreeValue(ctx, randval);
/* Compute which byte and which bit within that byte to set/clear */
size_t byte_idx = i / 8;
size_t bit_idx = i % 8;
if (fitval & 1)
bd->data[byte_idx] |= (uint8_t)(1 << bit_idx);
else
bd->data[byte_idx] &= (uint8_t)~(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->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_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_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_UNDEFINED;
}
// 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)
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.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_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_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->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_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_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),
// 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;
}