Files
cell/source/blob.h
John Alanbrook e86bdf52fe
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m29s
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
switch to blobs from arraybuffers
2025-05-28 22:33:32 -05:00

558 lines
14 KiB
C

#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);
int blob_write_bytes(blob *b, void *data, size_t length);
// 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; j++) {
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 */