first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# GapBuffer
|
||||
This repository implements a self-contained gap buffer data structure that's easily embeddable in your own C project!
|
||||
|
||||
(support for unicode (utf8))
|
||||
|
||||
# Table of contents
|
||||
1. License
|
||||
1. Context
|
||||
1. Usage
|
||||
1. Install
|
||||
1. Instanciate
|
||||
1. Text insertion
|
||||
1. Cursor position
|
||||
1. Text deletion
|
||||
1. Querying
|
||||
1. Testing
|
||||
|
||||
## License
|
||||
This code is MIT licensed
|
||||
|
||||
> Copyright 2023 Francesco Cozzuto
|
||||
>
|
||||
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
>
|
||||
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
>
|
||||
> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
## Context
|
||||
A gap buffer is a data structure that stores strings of text in a way that's optimized for operations you do in a text editor.
|
||||
|
||||
## Usage
|
||||
An overview of how to use this code follows. You can find more information in the documentation that comes with the code.
|
||||
|
||||
### Install
|
||||
To use it, first you need to drop `gap_buffer.c` and `gap_buffer.h` in your project directory and link them during compilation
|
||||
like they were your files.
|
||||
|
||||
### Instanciate
|
||||
You can instanciate a gap buffer in one of two ways:
|
||||
|
||||
```c
|
||||
GapBuffer *GapBuffer_create(size_t capacity);
|
||||
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len);
|
||||
```
|
||||
|
||||
The basic option is using `GapBuffer *GapBuffer_create(size_t capacity)`, which instanciates a buffer with a given initial capacity allocating space through the libc allocator. If you fill the buffer up and insert more text, the buffer will grow by moving to a bigger memory region (also allocated through `malloc`). Conversely, `GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len)` doesn't use dynamic memory and does its job using only memory provided by the caller. Either way, if it wasn't possible to instanciate the gap buffer (either because the dynamic allocation failed or the provided memory isn't big enough), NULL is returned.
|
||||
Once you're done with the buffer, you'll need to deallocate it using
|
||||
|
||||
```c
|
||||
void GapBuffer_destroy(GapBuffer *buff);
|
||||
```
|
||||
|
||||
### Text insertion
|
||||
To insert text, you must use the function
|
||||
```c
|
||||
bool GapBuffer_insertString(GapBuffer **buff, const char *str, size_t len);
|
||||
```
|
||||
which expects a UTF-8 string `str` as input and inserts it into the buffer at the cursor's position (the `len` argument refers to the number of bytes of the string, not the number of characters). After insertion, the cursor is moved after the inserted text, just like a cursor of a text editor!
|
||||
|
||||
The validity of the string is checked before insertion to make sure the buffer only contains valid UTF-8. If the string is inserted then `true` is returned, else if the string is invalid UTF-8 or relocation fails, false is returned.
|
||||
|
||||
An important thing to note is that the caller's handle of the buffer is passed by reference because the function may need to change the pointer if relocation occurres.
|
||||
|
||||
### Cursor position
|
||||
To move the cursor position you can use the functions
|
||||
```c
|
||||
void GapBuffer_moveRelative(GapBuffer *buff, int off);
|
||||
void GapBuffer_moveAbsolute(GapBuffer *buff, size_t num);
|
||||
```
|
||||
which move the cursor position relative to the start of the buffer or the current position of the cursor. Both the `off` and `num` quantities refer tu number of unicode characters, not raw bytes.
|
||||
|
||||
### Text deletion
|
||||
To delete text, you need to do so relative to the cursor's position. You can either remove text before or after the cursor using these functions
|
||||
```c
|
||||
void GapBuffer_removeForwards(GapBuffer *buff, size_t num);
|
||||
void GapBuffer_removeBackwards(GapBuffer *buff, size_t num);
|
||||
```
|
||||
Where `num` is the number of unicode characters to be removed. If less than `num` characters are available, then they are all removed.
|
||||
|
||||
### Querying
|
||||
To get the byte count using the getter function
|
||||
```c
|
||||
size_t GapBuffer_getByteCount(GapBuffer *buff);
|
||||
```
|
||||
This is currently only used for testing.
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "gap_buffer.h"
|
||||
|
||||
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *data;
|
||||
size_t size;
|
||||
} String;
|
||||
|
||||
struct GapBuffer {
|
||||
bool is_static;
|
||||
bool no_resize;
|
||||
size_t gap_offset;
|
||||
size_t gap_length;
|
||||
size_t total;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
size_t GapBuffer_getByteCount(GapBuffer *buff)
|
||||
{
|
||||
return buff->total - buff->gap_length;
|
||||
}
|
||||
|
||||
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len)
|
||||
{
|
||||
if (len < sizeof(GapBuffer))
|
||||
return NULL;
|
||||
|
||||
size_t capacity = len - sizeof(GapBuffer);
|
||||
|
||||
GapBuffer *buff = mem;
|
||||
buff->gap_offset = 0;
|
||||
buff->gap_length = capacity;
|
||||
buff->total = capacity;
|
||||
buff->no_resize = true;
|
||||
buff->is_static = true;
|
||||
return buff;
|
||||
}
|
||||
|
||||
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;
|
||||
return buff;
|
||||
}
|
||||
|
||||
void GapBuffer_destroy(GapBuffer *buff)
|
||||
{
|
||||
if (!buff->is_static)
|
||||
free(buff);
|
||||
}
|
||||
|
||||
static String getStringBeforeGap(GapBuffer *buff)
|
||||
{
|
||||
return (String) {
|
||||
.data=buff->data,
|
||||
.size=buff->gap_offset,
|
||||
};
|
||||
}
|
||||
|
||||
static String getStringAfterGap(GapBuffer *buff)
|
||||
{
|
||||
size_t first_byte_after_gap = buff->gap_offset + buff->gap_length;
|
||||
return (String) {
|
||||
.data = buff->data + first_byte_after_gap,
|
||||
.size = buff->total - first_byte_after_gap,
|
||||
};
|
||||
}
|
||||
|
||||
static GapBuffer *growGap(GapBuffer *buff, size_t min);
|
||||
static bool ensureSpace(GapBuffer **buff, size_t min)
|
||||
{
|
||||
if ((*buff)->gap_length < min) {
|
||||
|
||||
if ((*buff)->no_resize)
|
||||
return false;
|
||||
|
||||
GapBuffer *new_buff = growGap(*buff, min);
|
||||
if (new_buff == NULL)
|
||||
return false;
|
||||
|
||||
GapBuffer_destroy(*buff);
|
||||
*buff = new_buff;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool insertBytesBeforeCursor(GapBuffer **buff, String str)
|
||||
{
|
||||
if (!ensureSpace(buff, str.size))
|
||||
return false;
|
||||
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)
|
||||
{
|
||||
if (!ensureSpace(buff, str.size))
|
||||
return false;
|
||||
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)
|
||||
{
|
||||
size_t capacity = MAX(2 * buff->total, buff->total + min);
|
||||
GapBuffer *new_buff = GapBuffer_create(capacity);
|
||||
if (new_buff == NULL)
|
||||
return NULL;
|
||||
|
||||
if(!insertBytesBeforeCursor(&new_buff, getStringBeforeGap(buff)) ||
|
||||
!insertBytesAfterCursor(&new_buff, getStringAfterGap(buff))) {
|
||||
GapBuffer_destroy(new_buff);
|
||||
return NULL;
|
||||
}
|
||||
return new_buff;
|
||||
}
|
||||
|
||||
static bool isSymbolAuxiliaryByte(uint8_t byte)
|
||||
{
|
||||
return (byte & 0xC0) == 0x80;
|
||||
}
|
||||
|
||||
#warning "temporarily not static"
|
||||
int getSymbolRune(const char *sym, size_t symlen, uint32_t *rune)
|
||||
{
|
||||
if(symlen == 0)
|
||||
return 0;
|
||||
|
||||
if(sym[0] & 0x80) {
|
||||
|
||||
// May be UTF-8.
|
||||
|
||||
if((unsigned char) sym[0] >= 0xF0) {
|
||||
|
||||
// 4 bytes.
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
|
||||
if(symlen < 4)
|
||||
return -1;
|
||||
|
||||
if (!isSymbolAuxiliaryByte(sym[1]) ||
|
||||
!isSymbolAuxiliaryByte(sym[2]) ||
|
||||
!isSymbolAuxiliaryByte(sym[3]))
|
||||
return -1;
|
||||
|
||||
uint32_t temp
|
||||
= (((uint32_t) sym[0] & 0x07) << 18)
|
||||
| (((uint32_t) sym[1] & 0x3f) << 12)
|
||||
| (((uint32_t) sym[2] & 0x3f) << 6)
|
||||
| (((uint32_t) sym[3] & 0x3f));
|
||||
|
||||
if(temp < 0x010000 || temp > 0x10ffff)
|
||||
return -1;
|
||||
|
||||
*rune = temp;
|
||||
return 4;
|
||||
}
|
||||
|
||||
if((unsigned char) sym[0] >= 0xE0) {
|
||||
|
||||
// 3 bytes.
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
|
||||
if(symlen < 3)
|
||||
return -1;
|
||||
|
||||
if (!isSymbolAuxiliaryByte(sym[1]) ||
|
||||
!isSymbolAuxiliaryByte(sym[2]))
|
||||
return -1;
|
||||
|
||||
uint32_t temp
|
||||
= (((uint32_t) sym[0] & 0x0f) << 12)
|
||||
| (((uint32_t) sym[1] & 0x3f) << 6)
|
||||
| (((uint32_t) sym[2] & 0x3f));
|
||||
|
||||
if (temp < 0x0800 || temp > 0xffff)
|
||||
return -1;
|
||||
|
||||
*rune = temp;
|
||||
return 3;
|
||||
}
|
||||
|
||||
if((unsigned char) sym[0] >= 0xC0) {
|
||||
|
||||
// 2 bytes.
|
||||
// 110xxxxx 10xxxxxx
|
||||
|
||||
if(symlen < 2)
|
||||
return -1;
|
||||
|
||||
if (!isSymbolAuxiliaryByte(sym[1]))
|
||||
return -1;
|
||||
|
||||
*rune
|
||||
= (((uint32_t) sym[0] & 0x1f) << 6)
|
||||
| (((uint32_t) sym[1] & 0x3f));
|
||||
|
||||
if (*rune < 0x80 || *rune > 0x07ff)
|
||||
return -1;
|
||||
|
||||
assert(*rune <= 0x10ffff);
|
||||
return 2;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// It's ASCII
|
||||
// 0xxxxxxx
|
||||
|
||||
*rune = (uint32_t) sym[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool GapBuffer_insertString(GapBuffer **buff, const char *str, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
uint32_t rune; // Unused
|
||||
int n = getSymbolRune(str + i, len - i, &rune);
|
||||
if (n < 0)
|
||||
return false;
|
||||
i += n;
|
||||
}
|
||||
return insertBytesBeforeCursor(buff, (String) {.data=(const uint8_t*) str, .size=len});
|
||||
}
|
||||
|
||||
static size_t getPrecedingSymbol(GapBuffer *buff, size_t num)
|
||||
{
|
||||
size_t i = buff->gap_offset;
|
||||
|
||||
while (num > 0 && i > 0) {
|
||||
|
||||
// Consume the auxiliary bytes of the
|
||||
// UTF-8 sequence (those in the form
|
||||
// 10xxxxxx) preceding the cursor
|
||||
do {
|
||||
assert(i > 0); // FIXME: This triggers sometimes
|
||||
i--;
|
||||
// The index can never be negative because
|
||||
// this loop only iterates over the auxiliary
|
||||
// bytes of a UTF-8 byte sequence. If the
|
||||
// buffer only contains valid UTF-8, it will
|
||||
// not underflow.
|
||||
} while (isSymbolAuxiliaryByte(buff->data[i]));
|
||||
|
||||
// A character was consumed.
|
||||
num--;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static size_t getSymbolLengthFromFirstByte(uint8_t first)
|
||||
{
|
||||
// NOTE: It's assumed a valid first byte
|
||||
if (first >= 0xf0)
|
||||
return 4;
|
||||
if (first >= 0xe0)
|
||||
return 3;
|
||||
if (first >= 0xc0)
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t getFollowingSymbol(GapBuffer *buff, size_t num)
|
||||
{
|
||||
size_t i = buff->gap_offset + buff->gap_length;
|
||||
while (num > 0 && i < buff->total) {
|
||||
i += getSymbolLengthFromFirstByte(buff->data[i]);
|
||||
num--;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void GapBuffer_removeForwards(GapBuffer *buff, size_t num)
|
||||
{
|
||||
size_t i = getFollowingSymbol(buff, num);
|
||||
buff->gap_length = i - buff->gap_offset;
|
||||
}
|
||||
|
||||
void GapBuffer_removeBackwards(GapBuffer *buff, size_t num)
|
||||
{
|
||||
size_t i = getPrecedingSymbol(buff, num);
|
||||
buff->gap_length += buff->gap_offset - i;
|
||||
buff->gap_offset = i;
|
||||
}
|
||||
|
||||
static void moveBytesAfterGap(GapBuffer *buff, size_t num)
|
||||
{
|
||||
assert(buff->gap_offset >= num);
|
||||
|
||||
assert(buff->gap_offset <= buff->total);
|
||||
assert(buff->gap_offset <= buff->total);
|
||||
assert(buff->gap_offset + buff->gap_length <= buff->total);
|
||||
|
||||
memmove(buff->data + buff->gap_offset + buff->gap_length - num,
|
||||
buff->data + buff->gap_offset - num,
|
||||
num);
|
||||
buff->gap_offset -= num;
|
||||
}
|
||||
|
||||
static void moveBytesBeforeGap(GapBuffer *buff, size_t num)
|
||||
{
|
||||
assert(buff->total - buff->gap_offset - buff->gap_length >= num); // FIXME: This triggers sometimes
|
||||
|
||||
assert(buff->gap_offset <= buff->total);
|
||||
assert(buff->gap_offset <= buff->total);
|
||||
assert(buff->gap_offset + buff->gap_length <= buff->total);
|
||||
|
||||
memmove(buff->data + buff->gap_offset,
|
||||
buff->data + buff->gap_offset + buff->gap_length,
|
||||
num);
|
||||
buff->gap_offset += num;
|
||||
}
|
||||
|
||||
void GapBuffer_moveRelative(GapBuffer *buff, int off)
|
||||
{
|
||||
if (off < 0) {
|
||||
size_t i = getPrecedingSymbol(buff, -off);
|
||||
moveBytesAfterGap(buff, buff->gap_offset - i);
|
||||
} else {
|
||||
size_t i = getFollowingSymbol(buff, off);
|
||||
moveBytesBeforeGap(buff, i - buff->gap_offset - buff->gap_length);
|
||||
}
|
||||
}
|
||||
|
||||
void GapBuffer_moveAbsolute(GapBuffer *buff, size_t num)
|
||||
{
|
||||
size_t i;
|
||||
if (buff->gap_offset > 0)
|
||||
i = 0;
|
||||
else
|
||||
i = buff->gap_length;
|
||||
|
||||
while (num > 0 && i < buff->total) {
|
||||
|
||||
i += getSymbolLengthFromFirstByte(buff->data[i]);
|
||||
|
||||
// If the cursor reached the gap, jump over it.
|
||||
if (i == buff->gap_offset)
|
||||
i += buff->gap_length;
|
||||
|
||||
num--;
|
||||
}
|
||||
|
||||
if (i <= buff->gap_offset)
|
||||
moveBytesAfterGap(buff, buff->gap_offset - i);
|
||||
else
|
||||
moveBytesBeforeGap(buff, i - buff->gap_offset - buff->gap_length);
|
||||
}
|
||||
|
||||
void GapBufferIter_init(GapBufferIter *iter, GapBuffer *buff)
|
||||
{
|
||||
iter->crossed_gap = false;
|
||||
iter->buff = buff;
|
||||
iter->cur = 0;
|
||||
iter->mem = NULL;
|
||||
}
|
||||
|
||||
void GapBufferIter_free(GapBufferIter *iter)
|
||||
{
|
||||
if (iter->mem != NULL) {
|
||||
free(iter->mem);
|
||||
iter->mem = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool GapBufferIter_next(GapBufferIter *iter, GapBufferLine *line)
|
||||
{
|
||||
if (iter->mem != NULL) {
|
||||
free(iter->mem);
|
||||
iter->mem = NULL;
|
||||
}
|
||||
|
||||
size_t i = iter->cur;
|
||||
size_t total = iter->buff->total;
|
||||
size_t gap_offset = iter->buff->gap_offset;
|
||||
char *data = iter->buff->data;
|
||||
|
||||
if (iter->crossed_gap) {
|
||||
|
||||
size_t line_offset = iter->cur;
|
||||
while (i < total && data[i] != '\n')
|
||||
i++;
|
||||
size_t line_length = i - line_offset;
|
||||
|
||||
if (i < total)
|
||||
i++;
|
||||
else {
|
||||
if (line_length == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
line->str = data + line_offset;
|
||||
line->len = line_length;
|
||||
|
||||
} else {
|
||||
|
||||
size_t line_offset = i;
|
||||
while (i < gap_offset && data[i] != '\n')
|
||||
i++;
|
||||
size_t line_length = i - line_offset;
|
||||
|
||||
if (i == gap_offset) {
|
||||
|
||||
i += iter->buff->gap_length;
|
||||
|
||||
size_t line_offset_2 = i;
|
||||
while (i < total && data[i] != '\n')
|
||||
i++;
|
||||
size_t line_length_2 = i - line_offset_2;
|
||||
|
||||
if (i < total)
|
||||
i++; // Consume "\n"
|
||||
else {
|
||||
if (line_length + line_length_2 == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
memcpy(iter->maybe, data + line_offset, line_length);
|
||||
memcpy(iter->maybe + line_length, data + line_offset_2, line_length_2);
|
||||
line->str = iter->maybe;
|
||||
line->len = line_length + line_length_2;
|
||||
}
|
||||
|
||||
} else {
|
||||
i++; // Consume "\n"
|
||||
|
||||
line->str = data + line_offset;
|
||||
line->len = line_length;
|
||||
}
|
||||
}
|
||||
iter->cur = i;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#warning "temp"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct GapBuffer GapBuffer;
|
||||
|
||||
typedef struct {
|
||||
GapBuffer *buff;
|
||||
bool crossed_gap;
|
||||
size_t cur;
|
||||
void *mem;
|
||||
char maybe[256];
|
||||
} GapBufferIter;
|
||||
|
||||
typedef struct {
|
||||
const char *str;
|
||||
size_t len;
|
||||
} GapBufferLine;
|
||||
|
||||
GapBuffer *GapBuffer_createUsingMemory(void *mem, size_t len);
|
||||
GapBuffer *GapBuffer_create(size_t capacity);
|
||||
void GapBuffer_destroy(GapBuffer *buff);
|
||||
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);
|
||||
void GapBuffer_removeBackwards(GapBuffer *buff, size_t num);
|
||||
size_t GapBuffer_getByteCount(GapBuffer *buff);
|
||||
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);
|
||||
@@ -0,0 +1,166 @@
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "gap_buffer.h"
|
||||
|
||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
static size_t generateUnsignedIntegerBetween(size_t min, size_t max)
|
||||
{
|
||||
assert(max >= min);
|
||||
return min + rand() % (max - min + 1);
|
||||
}
|
||||
|
||||
static size_t generateString(char *dst, size_t max)
|
||||
{
|
||||
size_t len = generateUnsignedIntegerBetween(0, max);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (char) generateUnsignedIntegerBetween(0, 255);
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t generateUTF8String(char *dst, size_t max)
|
||||
{
|
||||
if (max == 0)
|
||||
return 0;
|
||||
|
||||
size_t max_len = generateUnsignedIntegerBetween(1, max);
|
||||
size_t len = 0;
|
||||
do {
|
||||
size_t num = generateUnsignedIntegerBetween(1, MIN(4, max_len-len));
|
||||
|
||||
uint8_t temp[4];
|
||||
|
||||
uint32_t rune;
|
||||
|
||||
switch (num) {
|
||||
case 1: rune = generateUnsignedIntegerBetween(0x0000, 0x007f); break;
|
||||
case 2: rune = generateUnsignedIntegerBetween(0x0080, 0x07ff); break;
|
||||
case 3: rune = generateUnsignedIntegerBetween(0x0800, 0xffff); break;
|
||||
case 4: rune = generateUnsignedIntegerBetween(0x10000, 0x10ffff); break;
|
||||
}
|
||||
|
||||
switch (num) {
|
||||
case 1: temp[0] = rune;
|
||||
break;
|
||||
case 2: temp[0] = ((rune >> 6) & 0x1f) | 0xC0;
|
||||
temp[1] = ((rune >> 0) & 0x3f) | 0x80;
|
||||
break;
|
||||
case 3: temp[0] = ((rune >> 12) & 0x0f) | 0xe0;
|
||||
temp[1] = ((rune >> 6) & 0x3f) | 0x80;
|
||||
temp[2] = ((rune >> 0) & 0x3f) | 0x80;
|
||||
break;
|
||||
case 4: temp[0] = ((rune >> 18) & 0x07) | 0xf0;
|
||||
temp[1] = ((rune >> 12) & 0x3f) | 0x80;
|
||||
temp[2] = ((rune >> 6) & 0x3f) | 0x80;
|
||||
temp[3] = ((rune >> 0) & 0x3f) | 0x80;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t rune2;
|
||||
int k = getSymbolRune((char*) temp, num, &rune2);
|
||||
assert(k >= 0);
|
||||
assert(k == num);
|
||||
assert(rune == rune2);
|
||||
|
||||
memcpy(dst + len, temp, num);
|
||||
len += num;
|
||||
} while (len < max_len);
|
||||
return len;
|
||||
}
|
||||
|
||||
void printStringAsHex(char *str, size_t len, FILE *stream)
|
||||
{
|
||||
fprintf(stream, "[ ");
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
static const char table[] = "0123456789abcdef";
|
||||
fprintf(stream, "%c%c ", table[(unsigned char) str[i] >> 4], table[(unsigned char) str[i] & 0xf]);
|
||||
}
|
||||
fprintf(stream, "]");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
srand(time(NULL));
|
||||
char buffer[32/*65536*/];
|
||||
GapBuffer *gap_buffer = GapBuffer_create(0);
|
||||
assert(gap_buffer != NULL);
|
||||
while (1) {
|
||||
switch (generateUnsignedIntegerBetween(0, 6)) {
|
||||
|
||||
case 0:
|
||||
{
|
||||
size_t len = generateString(buffer, sizeof(buffer));
|
||||
bool done = GapBuffer_insertString(&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");
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
size_t len = generateUTF8String(buffer, sizeof(buffer));
|
||||
bool done = GapBuffer_insertString(&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");
|
||||
break;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
|
||||
size_t index = generateUnsignedIntegerBetween(0, limit);
|
||||
fprintf(stderr, "MOVE_ABSOLUTE %ld\n", index);
|
||||
GapBuffer_moveAbsolute(gap_buffer, index);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
|
||||
size_t index = generateUnsignedIntegerBetween(0, limit);
|
||||
fprintf(stderr, "MOVE_RELATIVE %ld\n", index);
|
||||
GapBuffer_moveRelative(gap_buffer, index);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 4:
|
||||
{
|
||||
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
|
||||
size_t length = generateUnsignedIntegerBetween(0, limit);
|
||||
fprintf(stderr, "REMOVE_FORWARDS %ld\n", length);
|
||||
GapBuffer_removeForwards(gap_buffer, length);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5:
|
||||
{
|
||||
size_t limit = 1.5 * GapBuffer_getSize(gap_buffer);
|
||||
size_t length = generateUnsignedIntegerBetween(0, limit);
|
||||
fprintf(stderr, "REMOVE_BACKWARDS %ld\n", length);
|
||||
GapBuffer_removeBackwards(gap_buffer, length);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6:
|
||||
{
|
||||
fprintf(stderr, "PRINT\n");
|
||||
GapBufferIter iter;
|
||||
GapBufferLine line;
|
||||
GapBufferIter_init(&iter, gap_buffer);
|
||||
while (GapBufferIter_next(&iter, &line));
|
||||
GapBufferIter_free(&iter);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
GapBuffer_destroy(gap_buffer);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user