separate out blob and quickjs hooks into a blob.h header

This commit is contained in:
2025-05-28 14:55:35 -05:00
parent f334a2ad56
commit c603e8f006
2 changed files with 651 additions and 595 deletions

556
source/blob.h Normal file
View File

@@ -0,0 +1,556 @@
#ifndef BLOB_H
#define BLOB_H
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// -----------------------------------------------------------------------------
// 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 */

View File

@@ -1,338 +1,65 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#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);