New API for relocating the buffer

This commit is contained in:
Francesco Cozzuto
2023-04-01 19:29:43 +02:00
parent a1224382b7
commit 6d6613dddf
5 changed files with 173 additions and 264 deletions
+1
View File
@@ -0,0 +1 @@
test
+143 -246
View File
@@ -1,65 +1,40 @@
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "gap_buffer.h"
#ifdef GAPBUFFER_DEBUG
#define PRIVATE
#else
#define PRIVATE static
#endif
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
typedef struct {
const uint8_t *data;
size_t size;
const char *data;
size_t size;
} String;
struct GapBuffer {
bool is_static;
bool no_resize;
void (*free)(void*);
size_t gap_offset;
size_t gap_length;
size_t total;
uint8_t data[];
char data[];
};
/* Symbol: GapBuffer_getByteCount
** Return the length in bytes of the string stored
** in the buffer.
**
** Notes:
** - This information isn't very useful to the owner
** of the gap instance. It's only really used while
** testing.
**
** - This isn't declared in the header file, so
** if a program wants to use this function it need
** to specify its prototype explicitly.
*/
size_t GapBuffer_getByteCount(GapBuffer *buff)
PRIVATE size_t getByteCount(GapBuffer *buff)
{
return buff->total - buff->gap_length;
}
/* Symbol: GapBuffer_createUsingMemory
** Instanciate a gap buffer by providing it the memory
** it should use. The gap buffer won't be able to relocate
** to a bigger memory region if needed.
**
** Arguments:
** - mem: Address of the memory region used by the buffer
** - len: Length in bytes of the region referred to by [mem]
**
** Returns:
** The pointer to a buffer instance or NULL if the
** provided memory wasn't enough.
**
** Notes:
** - If you only instanciate buffers this way, you basically
** can drop the dependency on the libc allocator and potentially
** libc entirely.
*/
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len)
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len, void (*free)(void*))
{
if (mem == NULL || len < sizeof(GapBuffer))
if (mem == NULL || len < sizeof(GapBuffer)) {
if (free) free(mem);
return NULL;
}
size_t capacity = len - sizeof(GapBuffer);
@@ -67,37 +42,7 @@ GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len)
buff->gap_offset = 0;
buff->gap_length = capacity;
buff->total = capacity;
buff->no_resize = true;
buff->is_static = true;
return buff;
}
/* Symbol: GapBuffer_create
** Instanciate a gap buffer by allocating memory through
** the libc allocator.
**
** Gap buffers instanciated this way will allocate and
** relocate to bigger memory regions if necessary.
**
** Arguments:
** - capacity: The maximum string length (in bytes) that
** will be possible to store in the buffer
** without the need of allocating more memory.
**
** Returns:
** The pointer to a buffer instance or NULL if the
** dynamic memory allocation failed.
*/
GapBuffer *GapBuffer_create(size_t capacity)
{
GapBuffer *buff = malloc(sizeof(GapBuffer) + capacity);
if (buff == NULL)
return NULL;
buff->gap_offset = 0;
buff->gap_length = capacity;
buff->total = capacity;
buff->no_resize = false;
buff->is_static = false;
buff->free = free;
return buff;
}
@@ -106,15 +51,15 @@ GapBuffer *GapBuffer_create(size_t capacity)
*/
void GapBuffer_destroy(GapBuffer *buff)
{
if (!buff->is_static)
free(buff);
if (buff->free)
buff->free(buff);
}
/* Symbol: getStringBeforeGap
** Returns a slice to the memory region before the gap
** in the form of a (pointer, length) pair.
*/
static String getStringBeforeGap(GapBuffer *buff)
PRIVATE String getStringBeforeGap(const GapBuffer *buff)
{
return (String) {
@@ -130,7 +75,7 @@ static String getStringBeforeGap(GapBuffer *buff)
** Returns a slice to the memory region after the gap
** in the form of a (pointer, length) pair.
*/
static String getStringAfterGap(GapBuffer *buff)
PRIVATE String getStringAfterGap(const GapBuffer *buff)
{
// The first byte after the gap is the offset
// of the text that comes after the gap and
@@ -146,111 +91,53 @@ static String getStringAfterGap(GapBuffer *buff)
};
}
/* Forward declaration of [growGap] because [growGap]
** and [ensureSpace] refer to each other recursively,
** even though isn't not possible to have recursive
** calls during execution.
*/
static GapBuffer *growGap(GapBuffer *buff, size_t min);
/* Symbol: ensureSpace
** Make sure that the buffer has at least [min]
** unused bytes by relocating the buffer to a
** bigger memory region if necessary.
**
** Arguments:
** - buff: Reference to the buffer's reference. If a relocation
** occurres, the caller's reference will be changed.
** I
** - min: Minimum number of free bytes the call must ensure.
** If the buffer has a gap smaller than [min], relocation
** will occur.
**
** Returns:
** True if the buffer has more than [min] free
** space or if relocation occurred succesfully.
** If relocation failed or the buffer isn't allowed
** to grow, false is returned.
**
** Notes:
** - Only gaps instanciated with [GapBuffer_create]
** are allowed to be relocated.
*/
static bool ensureSpace(GapBuffer **buff, size_t min)
PRIVATE bool insertBytesBeforeCursor(GapBuffer *buff, String str)
{
if ((*buff)->gap_length < min) {
if ((*buff)->no_resize)
return false;
GapBuffer *new_buff = growGap(*buff, min);
if (new_buff == NULL)
return false;
// Free up the old buffer and swap the old
// parent's reference with the new one.
GapBuffer_destroy(*buff);
*buff = new_buff;
}
return true;
}
static bool insertBytesBeforeCursor(GapBuffer **buff, String str)
{
if (!ensureSpace(buff, str.size))
if (buff->gap_length < str.size)
return false;
memcpy((*buff)->data + (*buff)->gap_offset, str.data, str.size);
(*buff)->gap_offset += str.size;
(*buff)->gap_length -= str.size;
memcpy(buff->data + buff->gap_offset, str.data, str.size);
buff->gap_offset += str.size;
buff->gap_length -= str.size;
return true;
}
static bool insertBytesAfterCursor(GapBuffer **buff, String str)
PRIVATE bool insertBytesAfterCursor(GapBuffer *buff, String str)
{
if (!ensureSpace(buff, str.size))
if (buff->gap_length < str.size)
return false;
memcpy((*buff)->data + (*buff)->gap_offset + (*buff)->gap_length - str.size, str.data, str.size);
(*buff)->gap_length -= str.size;
memcpy(buff->data + buff->gap_offset + buff->gap_length - str.size, str.data, str.size);
buff->gap_length -= str.size;
return true;
}
static GapBuffer *growGap(GapBuffer *buff, size_t min)
GapBuffer *GapBuffer_cloneUsingMemory(void *mem, size_t len,
void (*free)(void*),
const GapBuffer *src)
{
size_t capacity = MAX(2 * buff->total, buff->total + min);
GapBuffer *new_buff = GapBuffer_create(capacity);
if (new_buff == NULL)
GapBuffer *clone = GapBuffer_createUsingMemory(mem, len, free);
if (!clone)
return NULL;
if(!insertBytesBeforeCursor(&new_buff, getStringBeforeGap(buff)) ||
!insertBytesAfterCursor(&new_buff, getStringAfterGap(buff))) {
GapBuffer_destroy(new_buff);
return NULL;
}
return new_buff;
String before = getStringBeforeGap(src);
if (!insertBytesBeforeCursor(clone, before))
goto oopsie;
String after = getStringAfterGap(src);
if (!insertBytesAfterCursor(clone, after))
goto oopsie;
return clone;
oopsie:
GapBuffer_destroy(clone);
return NULL;
}
// Returns true if and only if the [byte] is in the form 10xxxxxx
static bool isSymbolAuxiliaryByte(uint8_t byte)
PRIVATE bool isSymbolAuxiliaryByte(uint8_t byte)
{
/*
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A 10
1011 B 11
1100 C 12
1101 D 13
1110 E 14
1111 F 15
*/
// Hex Binary
// +-----+----------+
// | C0 | 11000000 |
@@ -261,30 +148,7 @@ static bool isSymbolAuxiliaryByte(uint8_t byte)
return (byte & 0xC0) == 0x80;
}
/* Symbol: getSymbolRune
** Converts a UTF-8 symbol to UTF-32.
**
** Arguments:
** - sym: string of bytes containing the UTF-8 symbol.
**
** - symlen: Length (in bytes) of the string [sym].
** I
** - rune: The location where the UTF-32 result will
** be stored. This argument isn't optional
** (you can pass NULL).
**
** Returns:
** The number of bytes of [sym] considered to
** decode the unicode symbol, 0 if the source
** string was empty or -1 if the source contained
** invalid UTF-8.
**
** Notes:
** - The (sym, symlen) pair can refer to a string of
** more than one unicode symbol.
*/
#warning "temporarily not static"
int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune)
PRIVATE int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune)
{
if(symlen == 0)
return 0;
@@ -375,34 +239,7 @@ int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune)
return 1;
}
/* Symbol: GapBuffer_insertString
** Insert a UTF-8 encoded string in the gap buffer,
** right before the cursor. If there's not enough
** space to store the string and the gap buffer
** was't instanciated with [GapBuffer_createUsingMemory],
** relocation to a bigger memory region occurres.
**
** Arguments:
** - buff: Reference to the caller's reference to the
** buffer. The caller's handle is changed if
** relocation occurres.
**
** - str: Pointer to the UTF-8 string to be inserted.
** I
** - len: Length (in bytes) of the input string.
**
** Returns:
** True if the text was inserted and false if either:
**
** - The buffer couldn't be relocated because
** instanciated with a fixed memory pool.
**
** - The buffer couldn't be relocated because
** a new memory region couldn't be allocated.
**
** - The input string wasn't valid UTF-8.
*/
bool GapBuffer_insertString(GapBuffer **buff, const char *str, size_t len)
PRIVATE bool isValidUTF8(const char *str, size_t len)
{
size_t i = 0;
while (i < len) {
@@ -412,10 +249,35 @@ bool GapBuffer_insertString(GapBuffer **buff, const char *str, size_t len)
return false;
i += n;
}
return insertBytesBeforeCursor(buff, (String) {.data=(const uint8_t*) str, .size=len});
return true;
}
static size_t getPrecedingSymbol(GapBuffer *buff, size_t num)
bool GapBuffer_insertString(GapBuffer *buff, const char *str, size_t len)
{
if (!isValidUTF8(str, len))
return false;
return insertBytesBeforeCursor(buff, (String) {.data=str, .size=len});
}
/* Symbol: getPrecedingSymbol
**
** Calculate the absolute byte offset of the
** [num]-th unicode symbol preceding the cursor.
**
** If less than [num] symbols precede the
** cursor, 0 is returned.
**
** Arguments:
** - buff: Reference to the gap buffer
**
** - num: Position of the unicode symbol preceding
** the cursor of which the offset should be
** returned, relative to the cursor.
**
** Notes:
** - It's analogous to getFollowingSymbol.
*/
PRIVATE size_t getPrecedingSymbol(GapBuffer *buff, size_t num)
{
size_t i = buff->gap_offset;
@@ -441,7 +303,7 @@ static size_t getPrecedingSymbol(GapBuffer *buff, size_t num)
return i;
}
static size_t getSymbolLengthFromFirstByte(uint8_t first)
PRIVATE size_t getSymbolLengthFromFirstByte(uint8_t first)
{
// NOTE: It's assumed a valid first byte
if (first >= 0xf0)
@@ -453,7 +315,25 @@ static size_t getSymbolLengthFromFirstByte(uint8_t first)
return 1;
}
static size_t getFollowingSymbol(GapBuffer *buff, size_t num)
/* Symbol: getFollowingSymbol
**
** Calculate the absolute byte offset of the
** [num]-th unicode symbol following the cursor.
**
** If less than [num] symbols follow the cursor,
** 0 is returned.
**
** Arguments:
** - buff: Reference to the gap buffer
**
** - num: Position of the unicode symbol following
** the cursor of which the offset should be
** returned, relative to the cursor.
**
** Notes:
** - It's analogous to getPrecedingSymbol.
*/
PRIVATE size_t getFollowingSymbol(GapBuffer *buff, size_t num)
{
size_t i = buff->gap_offset + buff->gap_length;
while (num > 0 && i < buff->total) {
@@ -476,7 +356,7 @@ void GapBuffer_removeBackwards(GapBuffer *buff, size_t num)
buff->gap_offset = i;
}
static void moveBytesAfterGap(GapBuffer *buff, size_t num)
PRIVATE void moveBytesAfterGap(GapBuffer *buff, size_t num)
{
assert(buff->gap_offset >= num);
@@ -490,7 +370,7 @@ static void moveBytesAfterGap(GapBuffer *buff, size_t num)
buff->gap_offset -= num;
}
static void moveBytesBeforeGap(GapBuffer *buff, size_t num)
PRIVATE void moveBytesBeforeGap(GapBuffer *buff, size_t num)
{
assert(buff->total - buff->gap_offset - buff->gap_length >= num); // FIXME: This triggers sometimes
@@ -550,18 +430,12 @@ void GapBufferIter_init(GapBufferIter *iter, GapBuffer *buff)
void GapBufferIter_free(GapBufferIter *iter)
{
if (iter->mem != NULL) {
free(iter->mem);
iter->mem = NULL;
}
iter->mem = NULL;
}
bool GapBufferIter_next(GapBufferIter *iter, GapBufferLine *line)
{
if (iter->mem != NULL) {
free(iter->mem);
iter->mem = NULL;
}
iter->mem = NULL;
size_t i = iter->cur;
size_t total = iter->buff->total;
@@ -611,24 +485,15 @@ bool GapBufferIter_next(GapBufferIter *iter, GapBufferLine *line)
iter->crossed_gap = true;
if (line_length + line_length_2 > sizeof(iter->maybe)) {
char *str = malloc(line_length + line_length_2);
if (str == NULL) {
// Line will be truncated
if (line_length > sizeof(iter->maybe))
memcpy(iter->maybe, data + line_offset, sizeof(iter->maybe));
else {
memcpy(iter->maybe, data + line_offset, line_length);
memcpy(iter->maybe + line_offset, data + line_offset_2, sizeof(iter->maybe) - line_length);
}
line->str = iter->maybe;
line->len = line_length + line_length_2;
} else {
iter->mem = str;
memcpy(str, data + line_offset, line_length);
memcpy(str + line_length, data + line_offset_2, line_length_2);
line->str = str;
line->len = line_length + line_length_2;
// Line will be truncated
if (line_length > sizeof(iter->maybe))
memcpy(iter->maybe, data + line_offset, sizeof(iter->maybe));
else {
memcpy(iter->maybe, data + line_offset, line_length);
memcpy(iter->maybe + line_offset, data + line_offset_2, sizeof(iter->maybe) - line_length);
}
line->str = iter->maybe;
line->len = line_length + line_length_2;
} else {
memcpy(iter->maybe, data + line_offset, line_length);
memcpy(iter->maybe + line_length, data + line_offset_2, line_length_2);
@@ -646,3 +511,35 @@ bool GapBufferIter_next(GapBufferIter *iter, GapBufferLine *line)
iter->cur = i;
return true;
}
#ifndef GAPBUFFER_NOMALLOC
#include <stdlib.h>
GapBuffer *GapBuffer_create(size_t capacity)
{
size_t len = sizeof(GapBuffer) + capacity;
void *mem = malloc(len);
return GapBuffer_createUsingMemory(mem, len, free);
}
bool GapBuffer_insertStringMaybeRelocate(GapBuffer **buff, const char *str, size_t len)
{
if (!GapBuffer_insertString(*buff, str, len)) {
// Need to relocate
GapBuffer *buff2 = GapBuffer_create(getByteCount(*buff) + len);
if (buff2 == NULL)
return false; // Failed to create new location
if (!GapBuffer_insertString(buff2, str, len)) {
// Insertion failed unexpectedly. The gap was created
// with enough free memory to hold the new text..
GapBuffer_destroy(buff2);
return false;
}
// Swap the parent buffer with the new one
GapBuffer_destroy(*buff);
*buff = buff2;
}
return true;
}
#endif
+8 -9
View File
@@ -1,6 +1,3 @@
#warning "temp"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
@@ -11,7 +8,7 @@ typedef struct {
bool crossed_gap;
size_t cur;
void *mem;
char maybe[256];
char maybe[512];
} GapBufferIter;
typedef struct {
@@ -19,10 +16,10 @@ typedef struct {
size_t len;
} GapBufferLine;
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len);
GapBuffer *GapBuffer_create(size_t capacity);
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len, void (*free)(void*));
GapBuffer *GapBuffer_cloneUsingMemory(void *mem, size_t len, void (*free)(void*), const GapBuffer *src);
void GapBuffer_destroy(GapBuffer *buff);
bool GapBuffer_insertString(GapBuffer **buff, const char *str, size_t len);
bool GapBuffer_insertString(GapBuffer *buff, const char *str, size_t len);
void GapBuffer_moveRelative(GapBuffer *buff, int off);
void GapBuffer_moveAbsolute(GapBuffer *buff, size_t num);
void GapBuffer_removeForwards(GapBuffer *buff, size_t num);
@@ -31,5 +28,7 @@ void GapBufferIter_init(GapBufferIter *iter, GapBuffer *buff);
void GapBufferIter_free(GapBufferIter *iter);
bool GapBufferIter_next(GapBufferIter *iter, GapBufferLine *line);
#warning "temporarily not static"
int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune);
#ifndef GAPBUFFER_NOMALLOC
GapBuffer *GapBuffer_create(size_t capacity);
bool GapBuffer_insertStringMaybeRelocate(GapBuffer **buff, const char *str, size_t len);
#endif
+7
View File
@@ -0,0 +1,7 @@
all: test
test: test.c gap_buffer.c
gcc $^ -o $@ -Wall -Wextra -DGAPBUFFER_DEBUG
clean:
rm test
+13 -8
View File
@@ -6,6 +6,9 @@
#include <assert.h>
#include "gap_buffer.h"
size_t getByteCount(GapBuffer *buff);
int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune);
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
static size_t generateUnsignedIntegerBetween(size_t min, size_t max)
@@ -63,7 +66,7 @@ static size_t generateUTF8String(char *dst, size_t max)
uint32_t rune2;
int k = getSymbolRune((char*) temp, num, &rune2);
assert(k >= 0);
assert(k == num);
assert((size_t) k == num);
assert(rune == rune2);
memcpy(dst + len, temp, num);
@@ -72,7 +75,8 @@ static size_t generateUTF8String(char *dst, size_t max)
return len;
}
void printStringAsHex(char *str, size_t len, FILE *stream)
/*
static void printStringAsHex(char *str, size_t len, FILE *stream)
{
fprintf(stream, "[ ");
for (size_t i = 0; i < len; i++) {
@@ -81,6 +85,7 @@ void printStringAsHex(char *str, size_t len, FILE *stream)
}
fprintf(stream, "]");
}
*/
int main(void)
{
@@ -94,7 +99,7 @@ int main(void)
case 0:
{
size_t len = generateString(buffer, sizeof(buffer));
bool done = GapBuffer_insertString(&gap_buffer, buffer, len);
bool done = GapBuffer_insertStringMaybeRelocate(&gap_buffer, buffer, len);
fprintf(stderr, "INSERT %ld \"%.*s\" .. %s\n", len, (int) len, buffer, done ? "DONE" : "NOT DONE");
//printStringAsHex(buffer, len, stderr);
//fprintf(stderr, "\n");
@@ -104,7 +109,7 @@ int main(void)
case 1:
{
size_t len = generateUTF8String(buffer, sizeof(buffer));
bool done = GapBuffer_insertString(&gap_buffer, buffer, len);
bool done = GapBuffer_insertStringMaybeRelocate(&gap_buffer, buffer, len);
fprintf(stderr, "INSERT %ld \"%.*s\" .. %s\n", len, (int) len, buffer, done ? "DONE" : "NOT DONE");
//printStringAsHex(buffer, len, stderr);
//fprintf(stderr, "\n");
@@ -113,7 +118,7 @@ int main(void)
case 2:
{
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
size_t limit = 1.5 * getByteCount(gap_buffer);
size_t index = generateUnsignedIntegerBetween(0, limit);
fprintf(stderr, "MOVE_ABSOLUTE %ld\n", index);
GapBuffer_moveAbsolute(gap_buffer, index);
@@ -122,7 +127,7 @@ int main(void)
case 3:
{
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
size_t limit = 1.5 * getByteCount(gap_buffer);
size_t index = generateUnsignedIntegerBetween(0, limit);
fprintf(stderr, "MOVE_RELATIVE %ld\n", index);
GapBuffer_moveRelative(gap_buffer, index);
@@ -132,7 +137,7 @@ int main(void)
case 4:
{
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
size_t limit = 1.5 * getByteCount(gap_buffer);
size_t length = generateUnsignedIntegerBetween(0, limit);
fprintf(stderr, "REMOVE_FORWARDS %ld\n", length);
GapBuffer_removeForwards(gap_buffer, length);
@@ -141,7 +146,7 @@ int main(void)
case 5:
{
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
size_t limit = 1.5 * getByteCount(gap_buffer);
size_t length = generateUnsignedIntegerBetween(0, limit);
fprintf(stderr, "REMOVE_BACKWARDS %ld\n", length);
GapBuffer_removeBackwards(gap_buffer, length);