diff --git a/source/blob.h b/source/blob.h new file mode 100644 index 00000000..53fedf85 --- /dev/null +++ b/source/blob.h @@ -0,0 +1,556 @@ +#ifndef BLOB_H +#define BLOB_H + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// A simple blob structure that can be in two states: +// - antestone (mutable): writing is allowed +// - stone (immutable): reading is allowed +// +// The blob is stored as an array of bits in memory, but for simplicity here, +// we store them in a dynamic byte array with a bit_length and capacity in bits. +// ----------------------------------------------------------------------------- + +typedef struct blob { + // The actual buffer holding the bits (in multiples of 8 bits). + uint8_t *data; + + // The total number of bits currently in use (the "length" of the blob). + size_t bit_length; + + // The total capacity in bits that 'data' can currently hold without realloc. + size_t bit_capacity; + + // 0 = antestone (mutable) + // 1 = stone (immutable) + int is_stone; +} blob; + +// Initialize a new blob +void blob_init(blob *b); + +// Create a new blob with specified bit capacity +blob *blob_new(size_t bit_capacity); + +// Create a blob from another blob (copy bits from 'from' to 'to') +blob *blob_new_from_blob(const blob *src, size_t from, size_t to); + +// Create a blob with length bits, filled with logical value or random function +blob *blob_new_with_fill(size_t length_bits, int logical_value); + +// Destroy blob and free memory +void blob_destroy(blob *b); + +// Turn a blob into stone (immutable) +void blob_make_stone(blob *b); + +// Write operations (only work on antestone blobs) +int blob_write_bit(blob *b, int bit_val); +int blob_write_blob(blob *b, const blob *src); +int blob_write_dec64(blob *b, double d); +int blob_write_fit(blob *b, int64_t fit, int length); +int blob_write_kim(blob *b, int64_t value); +int blob_write_pad(blob *b, int block_size); +int blob_write_text(blob *b, const char *text); + +// Read operations (only work on stone blobs) +int blob_read_bit(const blob *b, size_t pos, int *out_bit); +blob *blob_read_blob(const blob *b, size_t from, size_t to); +int blob_read_dec64(const blob *b, size_t from, double *out_value); +int blob_read_fit(const blob *b, size_t from, int length, int64_t *out_value); +int blob_read_kim(const blob *b, size_t from, int64_t *out_value, size_t *bits_read); +int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read); +int blob_pad_check(const blob *b, size_t from, int block_size); + +// Utility functions +int kim_length_for_fit(int64_t value); + +#ifdef BLOB_IMPLEMENTATION + +// Helper to ensure capacity for writing +static int blob_ensure_capacity(blob *b, size_t new_bits) { + size_t need_bits = b->bit_length + new_bits; + if (need_bits <= b->bit_capacity) return 0; + + // Increase capacity (in multiples of bytes). + // We can pick a growth strategy. For demonstration, double it: + size_t new_capacity = b->bit_capacity == 0 ? 64 : b->bit_capacity * 2; + while (new_capacity < need_bits) new_capacity *= 2; + + // Round up new_capacity to a multiple of 8 bits + if (new_capacity % 8) { + new_capacity += 8 - (new_capacity % 8); + } + + size_t new_size_bytes = new_capacity / 8; + uint8_t *new_ptr = realloc(b->data, new_size_bytes); + if (!new_ptr) { + return -1; // out of memory + } + // zero-fill the new area (only beyond the old capacity) + size_t old_size_bytes = b->bit_capacity / 8; + if (new_size_bytes > old_size_bytes) { + memset(new_ptr + old_size_bytes, 0, new_size_bytes - old_size_bytes); + } + b->data = new_ptr; + b->bit_capacity = new_capacity; + return 0; +} + +void blob_init(blob *b) { + b->data = NULL; + b->bit_length = 0; + b->bit_capacity = 0; + b->is_stone = 0; +} + +blob *blob_new(size_t bit_capacity) { + blob *b = calloc(1, sizeof(blob)); + if (!b) return NULL; + + blob_init(b); + + if (bit_capacity > 0) { + // Round up to multiple of 8 + if (bit_capacity % 8) { + bit_capacity += 8 - (bit_capacity % 8); + } + b->bit_capacity = bit_capacity; + size_t bytes = bit_capacity / 8; + b->data = calloc(bytes, 1); + if (!b->data) { + free(b); + return NULL; + } + } + + return b; +} + +blob *blob_new_from_blob(const blob *src, size_t from, size_t to) { + if (!src) return NULL; + if (to > src->bit_length) to = src->bit_length; + if (from >= to) return blob_new(0); + + size_t copy_len = to - from; + blob *b = blob_new(copy_len); + if (!b) return NULL; + + b->bit_length = copy_len; + + // Copy bits + for (size_t i = 0; i < copy_len; i++) { + size_t src_bit_index = from + i; + size_t src_byte = src_bit_index >> 3; + size_t src_off = src_bit_index & 7; + int bit_val = (src->data[src_byte] >> src_off) & 1; + size_t dst_byte = i >> 3; + size_t dst_off = i & 7; + if (bit_val) { + b->data[dst_byte] |= (1 << dst_off); + } + } + + return b; +} + +blob *blob_new_with_fill(size_t length_bits, int logical_value) { + blob *b = blob_new(length_bits); + if (!b) return NULL; + + b->bit_length = length_bits; + + if (logical_value) { + // Fill with 1s + size_t bytes = b->bit_capacity / 8; + memset(b->data, 0xff, bytes); + // Clear unused bits in last byte + size_t used_bits_in_last_byte = length_bits & 7; + if (used_bits_in_last_byte && bytes > 0) { + uint8_t mask = (1 << used_bits_in_last_byte) - 1; + b->data[bytes - 1] &= mask; + } + } + + return b; +} + +void blob_destroy(blob *b) { + if (b) { + if (b && b->data) { + free(b->data); + b->data = NULL; + b->bit_length = 0; + b->bit_capacity = 0; + } + free(b); + } +} + +void blob_make_stone(blob *b) { + if (!b) return; + b->is_stone = 1; + // Optionally shrink the buffer to exactly bit_length in size + if (b->bit_capacity > b->bit_length) { + size_t size_in_bytes = (b->bit_length + 7) >> 3; // round up to full bytes + uint8_t *new_ptr = NULL; + if (size_in_bytes) { + new_ptr = realloc(b->data, size_in_bytes); + if (new_ptr) { + b->data = new_ptr; + } + } else { + // zero length + free(b->data); + b->data = NULL; + } + b->bit_capacity = b->bit_length; // capacity in bits now matches length + } +} + +int blob_write_bit(blob *b, int bit_val) { + if (!b || b->is_stone) return -1; + + if (blob_ensure_capacity(b, 1) < 0) { + return -1; + } + + size_t bit_index = b->bit_length; + size_t byte_index = bit_index >> 3; + size_t offset_in_byte = bit_index & 7; + + // set or clear bit + if (bit_val) + b->data[byte_index] |= (1 << offset_in_byte); + else + b->data[byte_index] &= ~(1 << offset_in_byte); + + b->bit_length++; + return 0; +} + +int blob_write_blob(blob *b, const blob *src) { + if (!b || !src || b->is_stone) return -1; + + // Append all bits from src blob + for (size_t i = 0; i < src->bit_length; i++) { + size_t byte_idx = i / 8; + size_t bit_idx = i % 8; + int bit = (src->data[byte_idx] >> bit_idx) & 1; + if (blob_write_bit(b, bit) < 0) { + return -1; + } + } + + return 0; +} + +int blob_write_dec64(blob *b, double d) { + if (!b || b->is_stone) return -1; + + // Simple DEC64 encoding: store as IEEE 754 double (64 bits) + uint64_t bits; + memcpy(&bits, &d, sizeof(bits)); + + // Write 64 bits + for (int i = 0; i < 64; i++) { + if (blob_write_bit(b, (bits >> i) & 1) < 0) { + return -1; + } + } + + return 0; +} + +int blob_write_fit(blob *b, int64_t fit, int length) { + if (!b || b->is_stone) return -1; + if (length < 0 || length > 64) return -1; + + // Check if fit requires more bits than allowed + if (length < 64) { + int64_t max = (1LL << length) - 1; + if (fit < 0 || fit > max) { + return -1; + } + } + + // Write the bits + for (int i = 0; i < length; i++) { + if (blob_write_bit(b, (fit >> i) & 1) < 0) { + return -1; + } + } + + return 0; +} + +// Calculate the kim length in bits for a fit (int64) value +int kim_length_for_fit(int64_t value) { + if (value >= -1 && value <= 127) return 8; + if (value >= -127 && value <= 16383) return 16; + if (value >= -16383 && value <= 2097151) return 24; + if (value >= -2097151 && value <= 268435455) return 32; + if (value >= -268435455 && value <= 34359738367LL) return 40; + if (value >= -34359738367LL && value <= 4398046511103LL) return 48; + if (value >= -4398046511103LL && value <= 562949953421311LL) return 56; + if (value >= -562949953421311LL && value <= 36028797018963967LL) return 64; + if (value >= -36028797018963967LL) return 72; + return 80; // Maximum kim length +} + +int blob_write_kim(blob *b, int64_t value) { + if (!b || b->is_stone) return -1; + + int bits = kim_length_for_fit(value); + int bytes = bits / 8; + + uint8_t kim_bytes[10] = {0}; // Max 10 bytes for kim + + if (value < 0) { + // Negative number: first byte is 0x80, then encode -value-1 + kim_bytes[0] = 0x80; + value = -value - 1; + + // Encode remaining bytes + for (int i = bytes - 1; i > 0; i--) { + kim_bytes[i] = value & 0x7F; + value >>= 7; + if (i > 1) kim_bytes[i] |= 0x80; // Set continuation bit + } + } else { + // Positive number + for (int i = bytes - 1; i >= 0; i--) { + kim_bytes[i] = value & 0x7F; + value >>= 7; + if (i > 0) kim_bytes[i] |= 0x80; // Set continuation bit + } + } + + // Write the kim bytes as bits + for (int i = 0; i < bytes; i++) { + for (int j = 0; j < 8; b++) { + if (blob_write_bit(b, (kim_bytes[i] >> j) & 1) < 0) + return -1; + } + } + + return 0; +} + +int blob_write_pad(blob *b, int block_size) { + if (!b || b->is_stone) return -1; + if (block_size <= 0) return -1; + + // Write a 1 bit + if (blob_write_bit(b, 1) < 0) { + return -1; + } + + // Calculate how many 0 bits to add + size_t current_len = b->bit_length; + size_t remainder = current_len % block_size; + if (remainder > 0) { + size_t zeros_needed = block_size - remainder; + for (size_t i = 0; i < zeros_needed; i++) { + if (blob_write_bit(b, 0) < 0) { + return -1; + } + } + } + + return 0; +} + +int blob_write_text(blob *b, const char *text) { + if (!b || !text || b->is_stone) return -1; + + size_t len = strlen(text); + + // Write kim-encoded length + if (blob_write_kim(b, len) < 0) { + return -1; + } + + // Write each character as kim-encoded UTF-32 + // For simplicity, assuming ASCII + for (size_t i = 0; i < len; i++) { + if (blob_write_kim(b, (unsigned char)text[i]) < 0) { + return -1; + } + } + + return 0; +} + +int blob_read_bit(const blob *b, size_t pos, int *out_bit) { + if (!b || !b->is_stone || !out_bit) return -1; + if (pos >= b->bit_length) return -1; + + size_t byte_index = pos >> 3; + size_t offset_in_byte = pos & 7; + *out_bit = (b->data[byte_index] & (1 << offset_in_byte)) ? 1 : 0; + return 0; +} + +blob *blob_read_blob(const blob *b, size_t from, size_t to) { + if (!b || !b->is_stone) return NULL; + return blob_new_from_blob(b, from, to); +} + +int blob_read_dec64(const blob *b, size_t from, double *out_value) { + if (!b || !b->is_stone || !out_value) return -1; + if (from + 64 > b->bit_length) return -1; + + // Read 64 bits + uint64_t bits = 0; + for (int i = 0; i < 64; i++) { + int bit; + blob_read_bit(b, from + i, &bit); + if (bit) bits |= (1ULL << i); + } + + // Convert to double + memcpy(out_value, &bits, sizeof(*out_value)); + return 0; +} + +int blob_read_fit(const blob *b, size_t from, int length, int64_t *out_value) { + if (!b || !b->is_stone || !out_value) return -1; + if (length < 0 || length > 64) return -1; + if (from + length > b->bit_length) return -1; + + // Read bits + int64_t value = 0; + for (int i = 0; i < length; i++) { + int bit; + blob_read_bit(b, from + i, &bit); + if (bit) value |= (1LL << i); + } + + *out_value = value; + return 0; +} + +int blob_read_kim(const blob *b, size_t from, int64_t *out_value, size_t *bits_read) { + if (!b || !b->is_stone || !out_value || !bits_read) return -1; + + size_t pos = from; + uint8_t bytes[10]; + int byte_count = 0; + + // Read bytes until we find one without continuation bit + while (byte_count < 10) { + if (pos + 8 > b->bit_length) return -1; + + uint8_t byte = 0; + for (int i = 0; i < 8; i++) { + int bit; + if (blob_read_bit(b, pos + i, &bit) < 0) + return -1; + if (bit) byte |= (1 << i); + } + + bytes[byte_count++] = byte; + pos += 8; + + if (!(byte & 0x80)) break; // No continuation bit + } + + if (byte_count == 0) return -1; + + // Decode the kim value + int64_t value = 0; + int is_negative = (bytes[0] == 0x80); + + if (is_negative) { + // Skip the 0x80 byte and decode remaining + for (int i = 1; i < byte_count; i++) { + value = (value << 7) | (bytes[i] & 0x7F); + } + value = -value - 1; + } else { + for (int i = 0; i < byte_count; i++) { + value = (value << 7) | (bytes[i] & 0x7F); + } + } + + *out_value = value; + *bits_read = byte_count * 8; + return 0; +} + +int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) { + if (!b || !b->is_stone || !out_text || !bits_read) return -1; + if (from >= b->bit_length) return -1; + + // Read kim-encoded length + int64_t length; + size_t len_bits; + if (blob_read_kim(b, from, &length, &len_bits) < 0) { + return -1; + } + + if (length < 0) return -1; + + size_t pos = from + len_bits; + + // Read characters + char *str = malloc(length + 1); + if (!str) return -1; + + for (int64_t i = 0; i < length; i++) { + int64_t ch; + size_t ch_bits; + if (blob_read_kim(b, pos, &ch, &ch_bits) < 0) { + free(str); + return -1; + } + // For simplicity, assuming ASCII + str[i] = (char)(ch & 0xFF); + pos += ch_bits; + } + str[length] = '\0'; + + *out_text = str; + *bits_read = pos - from; + return 0; +} + +int blob_pad_check(const blob *b, size_t from, int block_size) { + if (!b || !b->is_stone) return 0; + if (block_size <= 0) return 0; + + // Check if blob length is multiple of block_size + if (b->bit_length % block_size != 0) { + return 0; + } + + // Check if difference between length and from is <= block_size + int64_t diff = b->bit_length - from; + if (diff <= 0 || diff > block_size) { + return 0; + } + + // Check if bit at from is 1 + int bit; + if (blob_read_bit(b, from, &bit) < 0 || bit != 1) { + return 0; + } + + // Check if remaining bits are 0 + for (size_t i = from + 1; i < b->bit_length; i++) { + if (blob_read_bit(b, i, &bit) < 0 || bit != 0) { + return 0; + } + } + + return 1; +} + +#endif /* BLOB_IMPLEMENTATION */ + +#endif /* BLOB_H */ \ No newline at end of file diff --git a/source/qjs_blob.c b/source/qjs_blob.c index fa472bde..07ebc034 100644 --- a/source/qjs_blob.c +++ b/source/qjs_blob.c @@ -1,338 +1,65 @@ -#include -#include -#include +#define BLOB_IMPLEMENTATION +#include "blob.h" #include "quickjs.h" #include "qjs_blob.h" #include "qjs_macros.h" -#include "qjs_wota.h" // Get countof from macros if not defined #ifndef countof #define countof(x) (sizeof(x)/sizeof((x)[0])) #endif -// ----------------------------------------------------------------------------- -// A simple blob structure that can be in two states: -// - antestone (mutable): writing is allowed -// - stone (immutable): reading is allowed -// -// The blob is stored as an array of bits in memory, but for simplicity here, -// we store them in a dynamic byte array with a bit_length and capacity in bits. -// -// This is a minimal demonstration. Real usage might require more sophisticated -// memory or bit manipulation for performance. -// ----------------------------------------------------------------------------- - -typedef struct blob { - // The actual buffer holding the bits (in multiples of 8 bits). - uint8_t *data; - - // The total number of bits currently in use (the "length" of the blob). - size_t bit_length; - - // The total capacity in bits that 'data' can currently hold without realloc. - size_t bit_capacity; - - // 0 = antestone (mutable) - // 1 = stone (immutable) - int is_stone; -} blob; - // Free function for blob void blob_free(JSRuntime *rt, blob *b) { if (b) { - free(b->data); - free(b); + blob_destroy(b); } } // Use QJSCLASS macro to generate class boilerplate QJSCLASS(blob,) -// Read one bit from a stone blob at position 'pos' -static int js_blob_read_bit_internal(blob *bd, size_t pos, int *out_bit) { - if (!bd->is_stone) { - return -1; // not stone - } - if (pos >= bd->bit_length) { - return -1; // out of range - } - size_t byte_index = pos >> 3; - size_t offset_in_byte = pos & 7; - *out_bit = (bd->data[byte_index] & (1 << offset_in_byte)) ? 1 : 0; - return 0; -} - -// Read kim-encoded value from blob -static int read_kim_internal(blob *bd, size_t from, int64_t *out_value, size_t *bits_read) { - if (!bd->is_stone) return -1; - - size_t pos = from; - uint8_t bytes[10]; - int byte_count = 0; - - // Read bytes until we find one without continuation bit - while (byte_count < 10) { - if (pos + 8 > bd->bit_length) return -1; - - uint8_t byte = 0; - for (int b = 0; b < 8; b++) { - int bit; - if (js_blob_read_bit_internal(bd, pos + b, &bit) < 0) - return -1; - if (bit) byte |= (1 << b); - } - - bytes[byte_count++] = byte; - pos += 8; - - if (!(byte & 0x80)) break; // No continuation bit - } - - if (byte_count == 0) return -1; - - // Decode the kim value - int64_t value = 0; - int is_negative = (bytes[0] == 0x80); - - if (is_negative) { - // Skip the 0x80 byte and decode remaining - for (int i = 1; i < byte_count; i++) { - value = (value << 7) | (bytes[i] & 0x7F); - } - value = -value - 1; - } else { - for (int i = 0; i < byte_count; i++) { - value = (value << 7) | (bytes[i] & 0x7F); - } - } - - *out_value = value; - *bits_read = byte_count * 8; - return 0; -} - -// Helper to ensure capacity for writing -// new_bits is additional bits to be appended -static int js_blob_ensure_capacity(JSContext *ctx, blob *bd, size_t new_bits) { - size_t need_bits = bd->bit_length + new_bits; - if (need_bits <= bd->bit_capacity) return 0; - - // Increase capacity (in multiples of bytes). - // We can pick a growth strategy. For demonstration, double it: - size_t new_capacity = bd->bit_capacity == 0 ? 64 : bd->bit_capacity * 2; - while (new_capacity < need_bits) new_capacity *= 2; - - // Round up new_capacity to a multiple of 8 bits - if (new_capacity % 8) { - new_capacity += 8 - (new_capacity % 8); - } - - size_t new_size_bytes = new_capacity / 8; - uint8_t *new_ptr = realloc(bd->data, new_size_bytes); - if (!new_ptr) { - return -1; // out of memory - } - // zero-fill the new area (only beyond the old capacity) - size_t old_size_bytes = bd->bit_capacity / 8; - if (new_size_bytes > old_size_bytes) { - memset(new_ptr + old_size_bytes, 0, new_size_bytes - old_size_bytes); - } - bd->data = new_ptr; - bd->bit_capacity = new_capacity; - return 0; -} - - - -// ----------------------------------------------------------------------------- -// Kim encoding helpers -// ----------------------------------------------------------------------------- - -// Calculate the kim length in bits for a fit (int64) value -static int kim_length_for_fit(int64_t value) { - if (value >= -1 && value <= 127) return 8; - if (value >= -127 && value <= 16383) return 16; - if (value >= -16383 && value <= 2097151) return 24; - if (value >= -2097151 && value <= 268435455) return 32; - if (value >= -268435455 && value <= 34359738367LL) return 40; - if (value >= -34359738367LL && value <= 4398046511103LL) return 48; - if (value >= -4398046511103LL && value <= 562949953421311LL) return 56; - if (value >= -562949953421311LL && value <= 36028797018963967LL) return 64; - if (value >= -36028797018963967LL) return 72; - return 80; // Maximum kim length -} - -// Write one bit (0 or 1) at the end of the blob -static int js_blob_write_bit_internal(JSContext *ctx, blob *bd, int bit_val) { - if (bd->is_stone) { - // Trying to write to an immutable blob -> throw - return -1; - } - if (js_blob_ensure_capacity(ctx, bd, 1) < 0) { - return -1; - } - // index in bits - size_t bit_index = bd->bit_length; - size_t byte_index = bit_index >> 3; - size_t offset_in_byte = bit_index & 7; - - // set or clear bit - if (bit_val) - bd->data[byte_index] |= (1 << offset_in_byte); - else - bd->data[byte_index] &= ~(1 << offset_in_byte); - - bd->bit_length++; - return 0; -} - -// Write kim-encoded value to blob -static int write_kim_internal(blob *bd, JSContext *ctx, int64_t value) { - int bits = kim_length_for_fit(value); - int bytes = bits / 8; - - uint8_t kim_bytes[10] = {0}; // Max 10 bytes for kim - - if (value < 0) { - // Negative number: first byte is 0x80, then encode -value-1 - kim_bytes[0] = 0x80; - value = -value - 1; - - // Encode remaining bytes - for (int i = bytes - 1; i > 0; i--) { - kim_bytes[i] = value & 0x7F; - value >>= 7; - if (i > 1) kim_bytes[i] |= 0x80; // Set continuation bit - } - } else { - // Positive number - for (int i = bytes - 1; i >= 0; i--) { - kim_bytes[i] = value & 0x7F; - value >>= 7; - if (i > 0) kim_bytes[i] |= 0x80; // Set continuation bit - } - } - - // Write the kim bytes as bits - for (int i = 0; i < bytes; i++) { - for (int b = 0; b < 8; b++) { - if (js_blob_write_bit_internal(ctx, bd, (kim_bytes[i] >> b) & 1) < 0) - return -1; - } - } - - return 0; -} - -// ----------------------------------------------------------------------------- -// Helpers for reading/writing bits -// ----------------------------------------------------------------------------- - - - - -// Turn a blob into the "stone" state. This discards any extra capacity. -static void js_blob_make_stone(blob *bd) { - bd->is_stone = 1; - // Optionally shrink the buffer to exactly bit_length in size - if (bd->bit_capacity > bd->bit_length) { - size_t size_in_bytes = (bd->bit_length + 7) >> 3; // round up to full bytes - uint8_t *new_ptr = NULL; - if (size_in_bytes) { - new_ptr = realloc(bd->data, size_in_bytes); - if (new_ptr) { - bd->data = new_ptr; - } - } else { - // zero length - free(bd->data); - bd->data = NULL; - } - bd->bit_capacity = bd->bit_length; // capacity in bits now matches length - } -} - -// ----------------------------------------------------------------------------- -// JS Functions (blob.make, blob.write_bit, blob.read_logical, etc.) -// ----------------------------------------------------------------------------- // Constructor function for blob static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - blob *bd = calloc(1, sizeof(*bd)); - if (!bd) return JS_ThrowOutOfMemory(ctx); - - // default - bd->data = NULL; - bd->bit_length = 0; - bd->bit_capacity = 0; - bd->is_stone = 0; // initially antestone + 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) { - free(bd); return JS_EXCEPTION; } if (capacity_bits < 0) capacity_bits = 0; - bd->bit_capacity = (size_t)capacity_bits; - if (bd->bit_capacity % 8) { - bd->bit_capacity += 8 - (bd->bit_capacity % 8); - } - if (bd->bit_capacity) { - size_t bytes = bd->bit_capacity / 8; - bd->data = calloc(bytes, 1); - if (!bd->data) { - free(bd); - return JS_ThrowOutOfMemory(ctx); - } - } + 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) { - free(bd); return JS_EXCEPTION; } if (length_bits < 0) length_bits = 0; - bd->bit_length = (size_t)length_bits; - bd->bit_capacity = bd->bit_length; - if (bd->bit_capacity % 8) { - bd->bit_capacity += 8 - (bd->bit_capacity % 8); - } - size_t bytes = bd->bit_capacity / 8; - if (bytes) { - bd->data = malloc(bytes); - if (!bd->data) { - free(bd); - return JS_ThrowOutOfMemory(ctx); - } - - if (JS_IsBool(argv[1])) { - // Fill with all 0s or all 1s - int is_one = JS_ToBool(ctx, argv[1]); - memset(bd->data, is_one ? 0xff : 0x00, bytes); - // if length_bits isn't a multiple of 8, we need to clear the unused bits - size_t used_bits_in_last_byte = (size_t)length_bits & 7; - if (used_bits_in_last_byte && is_one) { - // clear top bits in the last byte - uint8_t mask = (1 << used_bits_in_last_byte) - 1; - bd->data[bytes - 1] &= mask; - } - } else if (JS_IsFunction(ctx, argv[1])) { - // Random function provided - call it for each bit + + 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)) { - free(bd->data); - free(bd); + blob_destroy(bd); return JS_EXCEPTION; } int64_t fitval; @@ -348,11 +75,9 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, bd->data[byte_idx] &= ~(1 << bit_idx); } } - } else { - free(bd->data); - free(bd); - return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function"); } + } else { + return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function"); } } // new Blob(blob, from, to) @@ -360,7 +85,6 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, // we try copying from another blob if it's of the same class blob *src = js2blob(ctx, argv[0]); if (!src) { - free(bd); return JS_ThrowTypeError(ctx, "Blob constructor: argument 1 not a blob"); } int64_t from = 0, to = (int64_t)src->bit_length; @@ -373,42 +97,17 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target, if (to < from) to = from; if (to > (int64_t)src->bit_length) to = (int64_t)src->bit_length; } - size_t copy_len = (size_t)(to - from); - bd->bit_length = copy_len; - bd->bit_capacity = copy_len; - if (bd->bit_capacity % 8) { - bd->bit_capacity += 8 - (bd->bit_capacity % 8); - } - size_t bytes = bd->bit_capacity / 8; - if (bytes) { - bd->data = calloc(bytes, 1); - if (!bd->data) { - free(bd); - return JS_ThrowOutOfMemory(ctx); - } - } - // Now copy the bits. - // For simplicity, let's do a naive bit copy one by one: - for (size_t i = 0; i < copy_len; i++) { - size_t src_bit_index = from + i; - size_t src_byte = src_bit_index >> 3; - size_t src_off = src_bit_index & 7; - int bit_val = (src->data[src_byte] >> src_off) & 1; - size_t dst_byte = i >> 3; - size_t dst_off = i & 7; - if (bit_val) { - bd->data[dst_byte] |= (1 << dst_off); - } else { - bd->data[dst_byte] &= ~(1 << dst_off); - } - } + bd = blob_new_from_blob(src, (size_t)from, (size_t)to); } // else fail else { - free(bd); return JS_ThrowTypeError(ctx, "Blob constructor: invalid arguments"); } + if (!bd) { + return JS_ThrowOutOfMemory(ctx); + } + return blob2js(ctx, bd); } @@ -436,7 +135,7 @@ static JSValue js_blob_write_bit(JSContext *ctx, JSValueConst this_val, bit_val = JS_ToBool(ctx, argv[0]); } - if (js_blob_write_bit_internal(ctx, bd, bit_val) < 0) { + if (blob_write_bit(bd, bit_val) < 0) { return JS_ThrowTypeError(ctx, "write_bit: cannot write (maybe stone or OOM)"); } return JS_UNDEFINED; @@ -457,18 +156,8 @@ static JSValue js_blob_write_blob(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_blob: argument must be a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_blob: cannot write to stone blob"); - } - - // Append all bits from second blob - for (size_t i = 0; i < second->bit_length; i++) { - size_t byte_idx = i / 8; - size_t bit_idx = i % 8; - int bit = (second->data[byte_idx] >> bit_idx) & 1; - if (js_blob_write_bit_internal(ctx, bd, bit) < 0) { - return JS_ThrowTypeError(ctx, "write_blob: out of memory"); - } + if (blob_write_blob(bd, second) < 0) { + return JS_ThrowTypeError(ctx, "write_blob: cannot write to stone blob or OOM"); } return JS_UNDEFINED; @@ -485,25 +174,14 @@ static JSValue js_blob_write_dec64(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_dec64: not called on a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_dec64: cannot write to stone blob"); - } - // Get the number as a double and convert to DEC64 double d; if (JS_ToFloat64(ctx, &d, argv[0]) < 0) { return JS_EXCEPTION; } - // Simple DEC64 encoding: store as IEEE 754 double (64 bits) - uint64_t bits; - memcpy(&bits, &d, sizeof(bits)); - - // Write 64 bits - for (int i = 0; i < 64; i++) { - if (js_blob_write_bit_internal(ctx, bd, (bits >> i) & 1) < 0) { - return JS_ThrowTypeError(ctx, "write_dec64: out of memory"); - } + if (blob_write_dec64(bd, d) < 0) { + return JS_ThrowTypeError(ctx, "write_dec64: cannot write to stone blob or OOM"); } return JS_UNDEFINED; @@ -520,32 +198,13 @@ static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_fit: not called on a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_fit: cannot write to stone 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 (length < 0 || length > 64) { - return JS_ThrowTypeError(ctx, "write_fit: length must be between 0 and 64"); - } - - // Check if fit requires more bits than allowed - if (length < 64) { - int64_t max = (1LL << length) - 1; - if (fit < 0 || fit > max) { - return JS_ThrowTypeError(ctx, "write_fit: fit value requires more bits than allowed by length"); - } - } - - // Write the bits - for (int i = 0; i < length; i++) { - if (js_blob_write_bit_internal(ctx, bd, (fit >> i) & 1) < 0) { - return JS_ThrowTypeError(ctx, "write_fit: out of memory"); - } + 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; @@ -562,10 +221,6 @@ static JSValue js_blob_write_kim(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_kim: not called on a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob"); - } - // Handle number or single character string int64_t value; if (JS_IsString(argv[0])) { @@ -584,8 +239,8 @@ static JSValue js_blob_write_kim(JSContext *ctx, JSValueConst this_val, if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION; } - if (write_kim_internal(bd, ctx, value) < 0) { - return JS_ThrowTypeError(ctx, "write_kim: out of memory"); + if (blob_write_kim(bd, value) < 0) { + return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM"); } return JS_UNDEFINED; @@ -602,32 +257,11 @@ static JSValue js_blob_write_pad(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_pad: not called on a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_pad: cannot write to stone blob"); - } - int32_t block_size; if (JS_ToInt32(ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; - if (block_size <= 0) { - return JS_ThrowTypeError(ctx, "write_pad: block_size must be positive"); - } - - // Write a 1 bit - if (js_blob_write_bit_internal(ctx, bd, 1) < 0) { - return JS_ThrowTypeError(ctx, "write_pad: out of memory"); - } - - // Calculate how many 0 bits to add - size_t current_len = bd->bit_length; - size_t remainder = current_len % block_size; - if (remainder > 0) { - size_t zeros_needed = block_size - remainder; - for (size_t i = 0; i < zeros_needed; i++) { - if (js_blob_write_bit_internal(ctx, bd, 0) < 0) { - return JS_ThrowTypeError(ctx, "write_pad: out of memory"); - } - } + 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; @@ -644,31 +278,16 @@ static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "write_text: not called on a blob"); } - if (bd->is_stone) { - return JS_ThrowTypeError(ctx, "write_text: cannot write to stone blob"); - } - const char *str = JS_ToCString(ctx, argv[0]); if (!str) return JS_EXCEPTION; - size_t len = strlen(str); - - // Write kim-encoded length - if (write_kim_internal(bd, ctx, len) < 0) { - JS_FreeCString(ctx, str); - return JS_ThrowTypeError(ctx, "write_text: out of memory"); - } - - // Write each character as kim-encoded UTF-32 - // For simplicity, assuming ASCII - for (size_t i = 0; i < len; i++) { - if (write_kim_internal(bd, ctx, (unsigned char)str[i]) < 0) { - JS_FreeCString(ctx, str); - return JS_ThrowTypeError(ctx, "write_text: out of memory"); - } - } - + 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; } @@ -690,7 +309,7 @@ static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val, return JS_NULL; // out of range } int bit_val; - if (js_blob_read_bit_internal(bd, (size_t)pos, &bit_val) < 0) { + 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); @@ -720,45 +339,9 @@ static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val, if (to > (int64_t)bd->bit_length) to = bd->bit_length; } - if (from >= to) { - // Return empty blob - blob *new_bd = calloc(1, sizeof(*new_bd)); - if (!new_bd) return JS_ThrowOutOfMemory(ctx); - return blob2js(ctx, new_bd); - } - - // Create new blob with copy of bits - size_t copy_len = to - from; - blob *new_bd = calloc(1, sizeof(*new_bd)); - if (!new_bd) return JS_ThrowOutOfMemory(ctx); - - new_bd->bit_length = copy_len; - new_bd->bit_capacity = copy_len; - if (new_bd->bit_capacity % 8) { - new_bd->bit_capacity += 8 - (new_bd->bit_capacity % 8); - } - - size_t bytes = new_bd->bit_capacity / 8; - if (bytes) { - new_bd->data = calloc(bytes, 1); - if (!new_bd->data) { - free(new_bd); - return JS_ThrowOutOfMemory(ctx); - } - - // Copy bits - for (size_t i = 0; i < copy_len; i++) { - size_t src_idx = from + i; - size_t src_byte = src_idx / 8; - size_t src_bit = src_idx % 8; - int bit = (bd->data[src_byte] >> src_bit) & 1; - - size_t dst_byte = i / 8; - size_t dst_bit = i % 8; - if (bit) { - new_bd->data[dst_byte] |= (1 << dst_bit); - } - } + blob *new_bd = blob_read_blob(bd, from, to); + if (!new_bd) { + return JS_ThrowOutOfMemory(ctx); } return blob2js(ctx, new_bd); @@ -782,22 +365,11 @@ static JSValue js_blob_read_dec64(JSContext *ctx, JSValueConst this_val, int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (from < 0 || from + 64 > (int64_t)bd->bit_length) { + double d; + if (blob_read_dec64(bd, from, &d) < 0) { return JS_ThrowRangeError(ctx, "read_dec64: out of range"); } - // Read 64 bits - uint64_t bits = 0; - for (int i = 0; i < 64; i++) { - int bit; - js_blob_read_bit_internal(bd, from + i, &bit); - if (bit) bits |= (1ULL << i); - } - - // Convert to double - double d; - memcpy(&d, &bits, sizeof(d)); - return JS_NewFloat64(ctx, d); } @@ -821,20 +393,9 @@ static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val, if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &length, argv[1]) < 0) return JS_EXCEPTION; - if (length < 0 || length > 64) { - return JS_ThrowRangeError(ctx, "read_fit: length must be between 0 and 64"); - } - - if (from < 0 || from + length > (int64_t)bd->bit_length) { - return JS_ThrowRangeError(ctx, "read_fit: out of range"); - } - - // Read bits - int64_t value = 0; - for (int i = 0; i < length; i++) { - int bit; - js_blob_read_bit_internal(bd, from + i, &bit); - if (bit) value |= (1LL << i); + 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); @@ -858,14 +419,10 @@ static JSValue js_blob_read_kim(JSContext *ctx, JSValueConst this_val, int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (from < 0 || from >= (int64_t)bd->bit_length) { - return JS_ThrowRangeError(ctx, "read_kim: out of range"); - } - int64_t value; size_t bits_read; - if (read_kim_internal(bd, from, &value, &bits_read) < 0) { - return JS_ThrowRangeError(ctx, "read_kim: invalid kim encoding"); + 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 @@ -893,46 +450,19 @@ static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val, int64_t from; if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (from < 0 || from >= (int64_t)bd->bit_length) { - return JS_ThrowRangeError(ctx, "read_text: out of range"); - } - - // Read kim-encoded length - int64_t length; + char *text; size_t bits_read; - if (read_kim_internal(bd, from, &length, &bits_read) < 0) { - return JS_ThrowRangeError(ctx, "read_text: invalid kim encoding"); + if (blob_read_text(bd, from, &text, &bits_read) < 0) { + return JS_ThrowRangeError(ctx, "read_text: out of range or invalid encoding"); } - if (length < 0) { - return JS_ThrowRangeError(ctx, "read_text: invalid text length"); - } - - size_t pos = from + bits_read; - - // Read characters - char *str = malloc(length + 1); - if (!str) return JS_ThrowOutOfMemory(ctx); - - for (int64_t i = 0; i < length; i++) { - int64_t ch; - if (read_kim_internal(bd, pos, &ch, &bits_read) < 0) { - free(str); - return JS_ThrowRangeError(ctx, "read_text: invalid character encoding"); - } - // For simplicity, assuming ASCII - str[i] = (char)(ch & 0xFF); - pos += bits_read; - } - str[length] = '\0'; - - JSValue result = JS_NewString(ctx, str); - free(str); + 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, pos - from)); + JS_SetPropertyStr(ctx, obj, "bits_read", JS_NewInt64(ctx, bits_read)); return obj; } @@ -956,35 +486,7 @@ static JSValue js_blob_pad_q(JSContext *ctx, JSValueConst this_val, if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32(ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; - if (block_size <= 0) { - return JS_ThrowTypeError(ctx, "pad?: block_size must be positive"); - } - - // Check if blob length is multiple of block_size - if (bd->bit_length % block_size != 0) { - return JS_FALSE; - } - - // Check if difference between length and from is <= block_size - int64_t diff = bd->bit_length - from; - if (diff <= 0 || diff > block_size) { - return JS_FALSE; - } - - // Check if bit at from is 1 - int bit; - if (js_blob_read_bit_internal(bd, from, &bit) < 0 || bit != 1) { - return JS_FALSE; - } - - // Check if remaining bits are 0 - for (int64_t i = from + 1; i < bd->bit_length; i++) { - if (js_blob_read_bit_internal(bd, i, &bit) < 0 || bit != 0) { - return JS_FALSE; - } - } - - return JS_TRUE; + return JS_NewBool(ctx, blob_pad_check(bd, from, block_size)); } // blob.stone() @@ -995,7 +497,7 @@ static JSValue js_blob_stone(JSContext *ctx, JSValueConst this_val, return JS_ThrowTypeError(ctx, "stone: not called on a blob"); } if (!bd->is_stone) { - js_blob_make_stone(bd); + blob_make_stone(bd); } return JS_UNDEFINED; } @@ -1010,42 +512,6 @@ static JSValue js_blob_get_length(JSContext *ctx, JSValueConst this_val, int mag return JS_NewInt64(ctx, bd->bit_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_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), - - // 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), - 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 -// ----------------------------------------------------------------------------- - // Static kim_length function static JSValue js_blob_kim_length(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1081,6 +547,40 @@ static JSValue js_blob_kim_length(JSContext *ctx, JSValueConst this_val, 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), + + // 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), + 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); @@ -1098,4 +598,4 @@ JSValue js_blob_use(JSContext *js) { JS_NewCFunction(js, js_blob_kim_length, "kim_length", 1)); return ctor; -} +} \ No newline at end of file