Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Cisastor is a collection of C libraries and utilities.
- [Iterator](docs/libs/containers/iterator.md)
- [String View](docs/libs/containers/string_view.md)
- [String](docs/libs/containers/string.md)
- [String Builder](docs/libs/containers/string_builder.md)
- [Progress Bar](docs/libs/progressbar.md)
- [Testing Framework](docs/libs/testing.md)
- [Utils](docs/libs/utils.md)
Expand Down
40 changes: 40 additions & 0 deletions docs/libs/containers/string_builder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# String Builder

`string_builder` is a utility for efficiently concatenating multiple strings together. It is built on top of `array_list` to dynamically collect string pointers, preventing the O(N^2) reallocation overhead associated with repeatedly appending parts.

## Why String Builder?

When you construct a string piece by piece (e.g., in a loop), using `strcat` or `string_concat` repeatedly results in poor performance because the intermediate strings keep getting copied. `string_builder` solves this by:
- **Deferred Allocation**: It collects string parts and delays calculating the exact required capacity until `string_builder_build` is invoked, preventing repeated memory reallocations.
- **Single Pass Construction**: Allocates the exact needed memory once and copies all parts linearly.
- **Dynamic Collection**: Under the hood, it uses an `array_list` of `const char *` to securely and dynamically manage an arbitrary number of references.

## Usage

```c
#include "containers/string_builder.h"
#include "containers/string.h"

void example() {
// 1. Create the builder (Note: string_builder is a typedef)
string_builder *sb = string_builder_create();

// 2. Join string literals or C-strings
string_builder_join(sb, "Hello, ");
string_builder_join(sb, "world");
string_builder_join(sb, "!");

// 3. Build the final dynamic `string`
string *final_str = string_builder_build(sb);

// Output: "Hello, world!"
}
```

## API Reference

| Function | Description |
| --- | --- |
| `string_builder_create()` | Creates a new `string_builder` instance. |
| `string_builder_join(sb, cstr)` | Adds a null-terminated C-string to the builder. Does not copy the string content, only stores the pointer. |
| `string_builder_build(sb)` | Calculates the final required length, allocates the memory, and constructs the concatenated `string` from all joined parts. |
1 change: 0 additions & 1 deletion examples/array_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <stdio.h>
#include <string.h>

array_list_init(int);
struct foo {
int num;
};
Expand Down
8 changes: 8 additions & 0 deletions libs/data/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_library(cisastor_data_containers STATIC
containers/string_view.c
containers/iterator.c
containers/string.c
containers/string_builder.c
)

target_include_directories(cisastor_data_containers INTERFACE
Expand Down Expand Up @@ -47,3 +48,10 @@ cisastor_add_test(
IMPLS containers/string.c containers/array_list.c containers/iterator.c
LIBS cisastor::arena cisastor::utils
)

cisastor_add_test(
NAME string_builder_test
SRCS containers/string_builder_test.c
IMPLS containers/string_builder.c containers/string.c containers/array_list.c containers/iterator.c
LIBS cisastor::arena cisastor::utils
)
3 changes: 3 additions & 0 deletions libs/data/containers/array_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ byte *array_list_append(struct array_list *al, const void *data);
size_t array_list_get_length(struct array_list *al);

size_t array_list_get_capacity(struct array_list *al);

array_list_init(int);
array_list_init(char);
2 changes: 0 additions & 2 deletions libs/data/containers/array_list_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include "array_list.h"

array_list_init(int);

IT(should_behave_like_normal_array) {
struct array_list_int *al = array_list_create_int();
cassert(al != NULL, "failed to create array_list of size 10");
Expand Down
2 changes: 0 additions & 2 deletions libs/data/containers/iterator_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "iterator.h"
#include <string.h>

array_list_init(int);

IT(should_iterate_over_array_list) {
struct array_list_int *al = array_list_create_int();
for (int i = 0; i < 5; i++) {
Expand Down
2 changes: 0 additions & 2 deletions libs/data/containers/string.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "macros.h"
#include <stdint.h>

array_list_init(char);

typedef struct array_list_char string;

// Returns the length of the string
Expand Down
32 changes: 32 additions & 0 deletions libs/data/containers/string_builder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "string_builder.h"
#include "array_list.h"
#include "string.h"

#include <string.h>

string_builder *string_builder_create() {
struct array_list_const_char_ptr *al = array_list_create_const_char_ptr();
return al;
}

string_builder *string_builder_join(string_builder *sb,
const char *const cstr) {
array_list_append_const_char_ptr(cast(struct array_list_const_char_ptr *, sb),
cstr);
return sb;
}

string *string_builder_build(string_builder *sb) {
size_t capacity = 0;
for (int i = 0; i < array_list_get_length(sb); i++) {
capacity += strlen(*array_list_get_const_char_ptr_at(sb, i));
}
string *str = cast(string *, array_list_create_char_with_capacity(capacity));
for (int i = 0; i < array_list_get_length(sb); i++) {
const char *cstr = *array_list_get_const_char_ptr_at(sb, i);
for (int j = 0; cstr[j] != '\0'; j++) {
string_append_char(str, cstr[j]);
}
}
return str;
}
15 changes: 15 additions & 0 deletions libs/data/containers/string_builder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include "array_list.h"
#include "string.h"

typedef const char *const_char_ptr;
array_list_init(const_char_ptr);

// Helps build the final string from multiple parts
typedef struct array_list_const_char_ptr string_builder;

string_builder *string_builder_create();
string_builder *string_builder_join(string_builder *sb, const char *const cstr);

string *string_builder_build(string_builder *sb);
32 changes: 32 additions & 0 deletions libs/data/containers/string_builder_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "testing.h"
#include "string_builder.h"

IT(should_create_string_builder) {
string_builder *sb = string_builder_create();
cassert(sb != NULL, "failed to create string_builder");
}

IT(should_join_strings) {
string_builder *sb = string_builder_create();
string_builder_join(sb, "hello ");
string_builder_join(sb, "world");

string *str = string_builder_build(sb);
cassert(string_length(str) == 11, "length should be 11");

string *expected = string_create("hello world");
cassert(string_cmp(str, expected) == 0, "string mismatch");
}

IT(should_handle_many_joins) {
string_builder *sb = string_builder_create();
for (int i = 0; i < 1000; i++) {
string_builder_join(sb, "a");
}

string *str = string_builder_build(sb);
cassert(string_length(str) == 1000, "length should be 1000");
for(int i = 0; i < 1000; i++) {
cassertf(string_char_at(str, i) == 'a', "mismatch at idx %d", i);
}
}
Loading