Compare commits
9 Commits
c7f6ed82c9
...
78b5ff5955
| Author | SHA1 | Date | |
|---|---|---|---|
| 78b5ff5955 | |||
|
de22c706af
|
|||
|
f8a77a6056
|
|||
|
6110fc963c
|
|||
|
1871035cd6
|
|||
|
b4a704f1b4
|
|||
|
086446039b
|
|||
|
55b14ee949
|
|||
|
110ccae8b9
|
@@ -16,8 +16,8 @@ jobs:
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
./test_vector && ./test_map && ./test_bigint
|
||||
./test_vector && ./test_map && ./test_bigint && ./test_string
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
./benchmark_datum
|
||||
./benchmark_datum
|
||||
|
||||
@@ -13,8 +13,8 @@ jobs:
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
./test_vector && ./test_map && ./test_bigint
|
||||
./test_vector && ./test_map && ./test_bigint && ./test_string
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
./benchmark_datum
|
||||
./benchmark_datum
|
||||
|
||||
26
Makefile
26
Makefile
@@ -13,23 +13,19 @@ BENCH_OBJ_DIR = bench_obj
|
||||
|
||||
TESTS_SRC = tests
|
||||
|
||||
TARGET = usage
|
||||
TEST_V_TARGET = test_vector
|
||||
TEST_M_TARGET = test_map
|
||||
TEST_B_TARGET = test_bigint
|
||||
TEST_S_TARGET = test_string
|
||||
BENCH_TARGET = benchmark_datum
|
||||
|
||||
LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o
|
||||
PROG_OBJS = $(OBJ_DIR)/usage.o
|
||||
LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o
|
||||
|
||||
.PHONY: all clean
|
||||
.PHONY: all clean examples
|
||||
|
||||
all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET)
|
||||
all: $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) examples
|
||||
bench: $(BENCH_TARGET)
|
||||
|
||||
$(TARGET): $(PROG_OBJS) $(LIB_OBJS)
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
$(TEST_V_TARGET): $(OBJ_DIR)/test_vector.o $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
@@ -39,10 +35,13 @@ $(TEST_M_TARGET): $(OBJ_DIR)/test_map.o $(OBJ_DIR)/map.o
|
||||
$(TEST_B_TARGET): $(OBJ_DIR)/test_bigint.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(TEST_S_TARGET): $(OBJ_DIR)/test_string.o $(OBJ_DIR)/string.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
$(OBJ_DIR)/usage.o: usage.c | $(OBJ_DIR)
|
||||
examples: $(LIB_OBJS)
|
||||
$(MAKE) -C examples
|
||||
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJ_DIR)/%.o: $(TESTS_SRC)/%.c | $(OBJ_DIR)
|
||||
@@ -52,7 +51,7 @@ $(OBJ_DIR):
|
||||
mkdir -p $(OBJ_DIR)
|
||||
|
||||
# Benchmark rules
|
||||
$(BENCH_TARGET): $(BENCH_OBJ_DIR)/bench.o $(BENCH_OBJ_DIR)/vector.o $(BENCH_OBJ_DIR)/map.o $(BENCH_OBJ_DIR)/bigint.o
|
||||
$(BENCH_TARGET): $(BENCH_OBJ_DIR)/bench.o $(BENCH_OBJ_DIR)/vector.o $(BENCH_OBJ_DIR)/map.o $(BENCH_OBJ_DIR)/bigint.o $(BENCH_OBJ_DIR)/string.o
|
||||
$(CC) $(BENCH_FLAGS) -o $@ $^
|
||||
|
||||
$(BENCH_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(BENCH_OBJ_DIR)
|
||||
@@ -65,4 +64,5 @@ $(BENCH_OBJ_DIR):
|
||||
mkdir -p $(BENCH_OBJ_DIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET)
|
||||
rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET)
|
||||
$(MAKE) -C examples clean
|
||||
|
||||
55
README.md
55
README.md
@@ -11,7 +11,8 @@ the standard library. It currently features:
|
||||
|
||||
- [**Vector**](/docs/vector.md): a growable, contiguous array of homogenous generic data types;
|
||||
- [**Map**](/docs/map.md): an associative array of generic heterogenous data types;
|
||||
- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers.
|
||||
- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers;
|
||||
- [**String**](/docs/string.md): an immutable, null-terminated string type with partial UTF-8 support.
|
||||
|
||||
## Usage
|
||||
At its simplest, you can use this library as follows:
|
||||
@@ -134,20 +135,44 @@ int main(void) {
|
||||
}
|
||||
```
|
||||
|
||||
For a more exhaustive example, refer to the `usage.c` file. There, you will find a program with proper error management
|
||||
and a sample usage for every available method. To run it, first issue the following command:
|
||||
### `String` usage
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
#include "src/string.h"
|
||||
|
||||
/*
|
||||
* Compile with: gcc main.c src/string.c
|
||||
* Output: Final string: "Hello,World,😀" Splitted: ["Hello" "World" "😀" ]
|
||||
*/
|
||||
int main(void) {
|
||||
string_t *x = string_new(" Hello, ").value.string;
|
||||
string_t *x_trm = string_trim(x).value.string;
|
||||
|
||||
string_t *y = string_new("😀,dlroW").value.string;
|
||||
string_t *y_rev = string_reverse(y).value.string;
|
||||
|
||||
string_t *str = string_concat(x_trm, y_rev).value.string;
|
||||
string_t **strings = string_split(str, ",").value.split.strings;
|
||||
|
||||
printf("Final string: \"%s\" Splitted: [", str->data);
|
||||
for (int idx = 0; idx < 3; idx++) { printf("\"%s\" ", strings[idx]->data); }
|
||||
printf("]\n");
|
||||
|
||||
string_split_destroy(strings, 3); string_destroy(str);
|
||||
string_destroy(x); string_destroy(y);
|
||||
string_destroy(x_trm); string_destroy(y_rev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
For additional usage samples, refer to the [`examples/`](/examples) directory. You can compile these example programs with the following command:
|
||||
|
||||
```sh
|
||||
$ make clean all
|
||||
```
|
||||
|
||||
This will compile the library as well as the `usage.c` file, the unit tests and the benchmark. After that, you can run it by typing `./usage`.
|
||||
|
||||
> [!NOTE]
|
||||
> This project is primarily developed for learning purposes and was not created with industrial
|
||||
> or production use in mind. As such, it is not intended to compete with any existing C library such as the
|
||||
> GNU Multiple Precision Arithmetic Library (GMP).
|
||||
|
||||
## Documentation
|
||||
For additional details about this library (internal design, memory management, data ownership, etc.) go to the [docs folder](/docs).
|
||||
|
||||
@@ -162,14 +187,16 @@ $ ./test_bigint
|
||||
```
|
||||
|
||||
## Benchmark
|
||||
Under the [`benchmark/`](/benchmark/) folder, you can find a simple benchmark program that stress the `Vector` and the `Map` data structures. You can run it by issuing the following command:
|
||||
Under the [`benchmark/`](/benchmark/) folder, you can find a very simple benchmark program that stress the data structures.
|
||||
You can run it by issuing the following command:
|
||||
|
||||
```sh
|
||||
$ make clean all CC=clang
|
||||
$ ./benchmark_datum
|
||||
omputing Vector average time...average time: 8 ms
|
||||
Computing Map average time...average time: 53 ms
|
||||
Computing BigInt average time...average time: 76 ms
|
||||
Computing Vector average time...average time: 6 ms
|
||||
Computing Map average time...average time: 49 ms
|
||||
Computing BigInt average time...average time: 67 ms
|
||||
Computing String average time...average time: 13 ms
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../src/vector.h"
|
||||
#include "../src/map.h"
|
||||
#include "../src/bigint.h"
|
||||
#include "../src/string.h"
|
||||
|
||||
typedef void (*test_fn_t)(size_t iterations);
|
||||
|
||||
@@ -116,6 +117,31 @@ void test_bigint(size_t iterations) {
|
||||
}
|
||||
}
|
||||
|
||||
void test_string(size_t iterations) {
|
||||
volatile size_t total_len = 0;
|
||||
|
||||
for (size_t idx = 0; idx < iterations; idx++) {
|
||||
string_t *str1 = string_new("hello").value.string;
|
||||
string_t *str2 = string_new(" World").value.string;
|
||||
|
||||
string_result_t concat = string_concat(str1, str2);
|
||||
string_result_t upper = string_to_upper(concat.value.string);
|
||||
total_len += string_size(upper.value.string);
|
||||
string_result_t needle = string_new("WORLD");
|
||||
string_result_t contains = string_contains(upper.value.string, needle.value.string);
|
||||
|
||||
if (contains.value.idx >= 0) {
|
||||
total_len += contains.value.idx;
|
||||
}
|
||||
|
||||
string_destroy(str1);
|
||||
string_destroy(str2);
|
||||
string_destroy(concat.value.string);
|
||||
string_destroy(upper.value.string);
|
||||
string_destroy(needle.value.string);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint64_t now_ns(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
@@ -155,5 +181,9 @@ int main(void) {
|
||||
fflush(stdout);
|
||||
printf("average time: %lld ms\n", benchmark(test_bigint, 1e5, 30));
|
||||
|
||||
printf("Computing String average time...");
|
||||
fflush(stdout);
|
||||
printf("average time: %lld ms\n", benchmark(test_string, 1e5, 30));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ At the time being, this documentation includes the following pages:
|
||||
|
||||
- [vector.md](vector.md): vector documentation;
|
||||
- [map.md](map.md): map documentation;
|
||||
- [bigint.md](bigint.md): bigint documentation.
|
||||
- [bigint.md](bigint.md): bigint documentation;
|
||||
- [string.md](string.md): string documentation.
|
||||
|
||||
@@ -80,8 +80,6 @@ Each method that returns such type indicates whether the operation was successfu
|
||||
by setting the `status` field and by providing a descriptive message on the `message`
|
||||
field. If the operation was successful (that is, `status == BIGINT_OK`), you can either
|
||||
move on with the rest of the program or read the returned value from the sum data type.
|
||||
Of course, you can choose to ignore the return value (if you're brave enough :D) as
|
||||
illustrated on the first part of the README.
|
||||
|
||||
The sum data type (i.e., the `value` union) defines four different variables. Each
|
||||
of them has an unique scope as described below:
|
||||
|
||||
@@ -72,5 +72,4 @@ typedef struct {
|
||||
Each method that returns such type indicates whether the operation was successful or not by setting
|
||||
the `status` field and by providing a descriptive message on the `message` field. If the operation was
|
||||
successful (that is, `status == MAP_OK`), you can either move on with the rest of the program or read
|
||||
the returned value from the sum data type. Of course, you can choose to ignore the return value (if you're brave enough :D) as illustrated
|
||||
on the first part of the README.
|
||||
the returned value from the sum data type.
|
||||
|
||||
85
docs/string.md
Normal file
85
docs/string.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# String Technical Details
|
||||
In this document you can find a quick overview of the technical aspects (internal design, memory layout, etc.) of the `String` data structure.
|
||||
|
||||
`String` is an immutable, null-terminated string data type with partial UTF-8 support.
|
||||
This means that methods return a new string instance rather than modifying the string in-place.
|
||||
Internally, this data structure is represented by the following layout:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t byte_size;
|
||||
size_t byte_capacity;
|
||||
size_t char_count;
|
||||
} string_t;
|
||||
```
|
||||
|
||||
where the `data` field represent the actual string, `byte_size`
|
||||
indicates the actual size (in bytes), `byte_capacity` represents the total number
|
||||
of allocated memory (in bytes) and `char_count` represents the number of symbols.
|
||||
|
||||
As mentioned earlier, this data type provides partial UTF-8 support. It is able
|
||||
to recognize UTF-8 byte sequences as individual Unicode code points and has
|
||||
full support for Unicode symbols such as emojis. However, it does not support
|
||||
localization. In particular, it does not perform local-aware conversions. For instance, uppercase/lowercase transformations are limited to ASCII characters only. As a result, the German scharfes S (`ß`) is not convert to `SS`, the Spanish `Ñ` is not converted to `ñ` and the Italian `é` (and its variants) is not treated as a single symbol but rather as a base letter combined with an accent.
|
||||
|
||||
At the time being, `String` supports the following methods:
|
||||
|
||||
- `string_result_t string_new(c_str)`: create a new string;
|
||||
- `string_result_t string_clone(str)`: clone an existing string;
|
||||
- `string_result_t string_concat(x, y)`: concatenate two strings together;
|
||||
- `string_result_t string_contains(haystack, needle)`: search whether the `haystack` string contains `needle`;
|
||||
- `string_result_t string_slice(str, start, end)`: return a slice (a new string) from `str` between `start` and `end` indices (inclusive);
|
||||
- `string_result_t string_eq(x, y, case_sensitive)`: check whether `x` and `y` are equal;
|
||||
- `string_result_t string_get_at(str, position)`: get the UTF-8 symbol indexed by `position` from `str`;
|
||||
- `string_result_t string_set_at(str, position, utf8_char)`: write a UTF-8 symbol into `str` at index `position`;
|
||||
- `string_result_t string_to_lower(str)`: convert a string to lowercase;
|
||||
- `string_result_t string_to_upper(str)`: convert a string to uppercase;
|
||||
- `string_result_t string_reverse(str)`: reverse a string;
|
||||
- `string_result_t string_trim(str)`: remove leading and trailing white space from a string;
|
||||
- `string_result_t string_split(str, delim)`: split a string into an array of `string_t` by specifying a separator;
|
||||
- `string_result_t string_destroy(str)`: remove a string from memory;
|
||||
- `string_result_t string_split_destroy(split, count)`: remove an array of strings from memory;
|
||||
- `size_t string_size(str)`: return string character count.
|
||||
|
||||
As you can see from the previous function signatures, most methods that operate on the `String`
|
||||
data type return a custom type called `string_result_t` which is defined as follows:
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
STRING_OK = 0x0,
|
||||
STRING_ERR_ALLOCATE,
|
||||
STRING_ERR_INVALID,
|
||||
STRING_ERR_INVALID_UTF8,
|
||||
STRING_ERR_OVERFLOW
|
||||
} string_status_t;
|
||||
|
||||
typedef struct {
|
||||
string_status_t status;
|
||||
uint8_t message[RESULT_MSG_SIZE];
|
||||
union {
|
||||
string_t *string;
|
||||
char *symbol;
|
||||
int64_t idx;
|
||||
bool is_equ;
|
||||
struct {
|
||||
string_t **strings;
|
||||
size_t count;
|
||||
} split;
|
||||
} value;
|
||||
} string_result_t;
|
||||
```
|
||||
|
||||
Each method that returns such type indicates whether the operation was successful or not
|
||||
by setting the `status` field and by providing a descriptive message on the `message`
|
||||
field. If the operation was successful (that is, `status == STRING_OK`) you can either
|
||||
move on with the rest of your program or read the returned value from the sum data type.
|
||||
|
||||
The sum data type (i.e., the `value` union) defines five different variables.
|
||||
Each of them has an unique scope as described below:
|
||||
|
||||
- `string`: result of `new`, `clone`, `slice`, `reverse` and `trim` functions;
|
||||
- `symbol`: result of `get_at` function;
|
||||
- `idx`: result of `contains` function;
|
||||
- `is_eq`: result of `equ` function. It's true when two strings are equal, false otherwise;
|
||||
- `split`: result of `split` function. It contains an array of `string_t` and its number of elements.
|
||||
@@ -65,8 +65,7 @@ typedef struct {
|
||||
Each method that returns such type indicates whether the operation was successful or not
|
||||
by setting the `status` field and by providing a descriptive message on the `message`
|
||||
field. If the operation was successful (that is, `status == VECTOR_OK`), you can either
|
||||
move on with the rest of the program or read the returned value from the sum data type. Of course, you can choose to
|
||||
ignore the return value (if you're brave enough :D) as illustrated on the first part of the README.
|
||||
move on with the rest of the program or read the returned value from the sum data type.
|
||||
|
||||
## Functional methods
|
||||
`Vector` provides three functional methods called `map`, `filter` and `reduce` which allow the caller to apply a computation to the vector,
|
||||
|
||||
37
examples/Makefile
Normal file
37
examples/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Werror -pedantic-errors -fstack-protector-strong \
|
||||
-fsanitize=address -fsanitize=undefined -fstack-clash-protection \
|
||||
-Wwrite-strings -g -std=c99
|
||||
|
||||
SRC_DIR = ../src
|
||||
OBJ_DIR = ../obj
|
||||
|
||||
TARGETS = vector_basic vector_sorting vector_functional map_basic bigint_operations string_basic string_advanced
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
vector_basic: vector_basic.c $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
vector_sorting: vector_sorting.c $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
vector_functional: vector_functional.c $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
map_basic: map_basic.c $(OBJ_DIR)/map.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
bigint_operations: bigint_operations.c $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
string_basic: string_basic.c $(OBJ_DIR)/string.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
string_advanced: string_advanced.c $(OBJ_DIR)/string.o
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f $(TARGETS)
|
||||
118
examples/bigint_operations.c
Normal file
118
examples/bigint_operations.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Bigint operations example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/bigint.h"
|
||||
|
||||
int main(void) {
|
||||
const char *x_origin = "8036732204560262312865077650774313136023641621894661847778962273940232785242208265819059749867858355";
|
||||
const char *y_origin = "7078840479830524979114102683681365071561983635405714511439038016617918064981439736383067887133445937";
|
||||
const size_t x_len = strlen(x_origin);
|
||||
const size_t y_len = strlen(y_origin);
|
||||
const size_t large_x_size = x_len * 100 + 1;
|
||||
const size_t large_y_size = y_len * 100 + 1;
|
||||
|
||||
char *large_x = malloc(large_x_size);
|
||||
char *large_y = malloc(large_y_size);
|
||||
|
||||
if (large_x == NULL || large_y == NULL) {
|
||||
printf("Error while allocating memory for strings\n");
|
||||
free(large_x);
|
||||
free(large_y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
large_x[0] = '\0';
|
||||
large_y[0] = '\0';
|
||||
|
||||
// Concatenate 100 times
|
||||
for (size_t idx = 0; idx < 100; idx++) {
|
||||
strcat(large_x, x_origin);
|
||||
strcat(large_y, y_origin);
|
||||
}
|
||||
|
||||
// Create two big integers from previous strings
|
||||
bigint_result_t x_res = bigint_from_string(large_x);
|
||||
if (x_res.status != BIGINT_OK) {
|
||||
printf("Error while creating big number: %s\n", x_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_result_t y_res = bigint_from_string(large_y);
|
||||
if (x_res.status != BIGINT_OK) {
|
||||
printf("Error while creating big number: %s\n", x_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *x = x_res.value.number;
|
||||
bigint_t *y = y_res.value.number;
|
||||
|
||||
// Sum two big integers
|
||||
bigint_result_t sum_res = bigint_add(x, y);
|
||||
if (sum_res.status != BIGINT_OK) {
|
||||
printf("Error while summing two big numbers: %s\n", sum_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *sum = sum_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("Sum result = %B\n", sum);
|
||||
|
||||
// Subtract two big integers
|
||||
bigint_result_t diff_res = bigint_sub(x, y);
|
||||
if (diff_res.status != BIGINT_OK) {
|
||||
printf("Error while subtracting two big numbers: %s\n", diff_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *diff = diff_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("difference result = %B\n", diff);
|
||||
|
||||
// Multiply two big integers
|
||||
bigint_result_t prod_res = bigint_prod(x, y);
|
||||
if (prod_res.status != BIGINT_OK) {
|
||||
printf("Error while multiplying two big numbers: %s\n", prod_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *prod = prod_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("multiplication result = %B\n", prod);
|
||||
|
||||
bigint_t *a = bigint_from_string(large_x).value.number;
|
||||
bigint_t *b = bigint_from_string(y_origin).value.number;
|
||||
|
||||
// Divide two big integers
|
||||
bigint_result_t div_res = bigint_divmod(a, b);
|
||||
if (div_res.status != BIGINT_OK) {
|
||||
printf("Error while dividing two big numbers: %s\n", div_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *quotient = div_res.value.division.quotient;
|
||||
bigint_t *remainder = div_res.value.division.remainder;
|
||||
|
||||
// Print result
|
||||
bigint_printf(
|
||||
"division result = %B\
|
||||
\nmod result = %B\n",
|
||||
quotient, remainder);
|
||||
|
||||
// Destroy big numbers and strings
|
||||
bigint_destroy(x); bigint_destroy(y);
|
||||
bigint_destroy(a); bigint_destroy(b);
|
||||
bigint_destroy(sum); bigint_destroy(diff);
|
||||
bigint_destroy(prod); bigint_destroy(quotient); bigint_destroy(remainder);
|
||||
free(large_x); free(large_y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
101
examples/map_basic.c
Normal file
101
examples/map_basic.c
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Basic map operations example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/map.h"
|
||||
|
||||
int main(void) {
|
||||
// Create a new map
|
||||
map_result_t res = map_new();
|
||||
if (res.status != MAP_OK) {
|
||||
printf("Error while creating map: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
map_t *map = res.value.map;
|
||||
|
||||
// Add some values
|
||||
const int x = 0xB00B5;
|
||||
const char *y = "Hello";
|
||||
|
||||
map_result_t add_res = map_add(map, "x", (void*)&x);
|
||||
if (add_res.status != MAP_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
add_res = map_add(map, "y", (void*)y);
|
||||
if (add_res.status != MAP_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Print size and capacity
|
||||
printf("Map size (should be 2): %zu\n", map_size(map));
|
||||
printf("Map capacity (should be > 2): %zu\n\n", map_capacity(map));
|
||||
|
||||
// Retrieve keys
|
||||
map_result_t get_res = map_get(map, "x");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'x': %s\n", get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (const int*)get_res.value.element;
|
||||
printf("Key 'x' contains (should be 'B00B5'): %X\n", *val);
|
||||
}
|
||||
|
||||
get_res = map_get(map, "y");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'y': %s\n", get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
const char *val = (const char*)get_res.value.element;
|
||||
printf("Key 'y' contains (should be 'Hello') : %s\n\n", val);
|
||||
}
|
||||
|
||||
// Update key
|
||||
const int new_x = 0xC0FFEE;
|
||||
map_result_t up_res = map_add(map, "x", (void*)&new_x);
|
||||
|
||||
up_res = map_get(map, "x");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'x': %s\n", get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (const int*)up_res.value.element;
|
||||
printf("Key 'x' (should be updated to 'C0FFEE'): %X\n\n", *val);
|
||||
}
|
||||
|
||||
// Remove an element
|
||||
map_result_t rm_res = map_remove(map, "y");
|
||||
if (rm_res.status != MAP_OK) {
|
||||
printf("Cannot remove map element 'y': %s\n", rm_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
printf("Map element 'y' removed (size should be 1): %zu\n\n", map_size(map));
|
||||
}
|
||||
|
||||
// Clear the map
|
||||
map_result_t clear_res = map_clear(map);
|
||||
if (clear_res.status != MAP_OK) {
|
||||
printf("Cannot clear map: %s\n", clear_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
printf("Map cleared (size should be 0): %zu\n", map_size(map));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Delete the map
|
||||
map_result_t del_res = map_destroy(map);
|
||||
if (del_res.status != MAP_OK) {
|
||||
printf("Error while destroying the map: %s\n", del_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
103
examples/string_advanced.c
Normal file
103
examples/string_advanced.c
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Advanced string manipulation example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/string.h"
|
||||
|
||||
int main(void) {
|
||||
// Create a string for manipulation
|
||||
string_result_t res = string_new("Hello, World! 😜");
|
||||
if (res.status != STRING_OK) {
|
||||
printf("Error: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
string_t *str = res.value.string;
|
||||
printf("Original string: \"%s\"\n\n", str->data);
|
||||
|
||||
// Uppercase string
|
||||
string_result_t res_upper = string_to_upper(str);
|
||||
if (res_upper.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_upper.message);
|
||||
return 1;
|
||||
}
|
||||
printf("Uppercase: \"%s\"\n", res_upper.value.string->data);
|
||||
string_destroy(res_upper.value.string);
|
||||
|
||||
// Lowercase string
|
||||
string_result_t res_lower = string_to_lower(str);
|
||||
if (res_lower.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_lower.message);
|
||||
return 1;
|
||||
}
|
||||
printf("Lowercase: \"%s\"\n\n", res_lower.value.string->data);
|
||||
string_destroy(res_lower.value.string);
|
||||
|
||||
// Reverse string
|
||||
string_result_t res_rev = string_reverse(str);
|
||||
if (res_rev.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_rev.message);
|
||||
return 1;
|
||||
}
|
||||
printf("Reversed: \"%s\"\n\n", res_rev.value.string->data);
|
||||
string_destroy(res_rev.value.string);
|
||||
|
||||
// Change first character of the string
|
||||
string_result_t res_set = string_set_at(str, 0, "J");
|
||||
if (res_set.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_set.message);
|
||||
return 1;
|
||||
}
|
||||
printf("Updated string: \"%s\"\n\n", res_set.value.string->data);
|
||||
string_destroy(res_set.value.string);
|
||||
|
||||
// Get character from string (the emoji)
|
||||
string_result_t res_get = string_get_at(str, 14);
|
||||
if (res_get.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_get.message);
|
||||
return 1;
|
||||
}
|
||||
printf("Extracted symbol: \"%s\"\n", res_get.value.symbol);
|
||||
free(res_get.value.symbol);
|
||||
|
||||
// Trim string
|
||||
string_t *to_trim = string_new(" foo ").value.string;
|
||||
string_result_t res_trim = string_trim(to_trim);
|
||||
if (res_trim.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_trim.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Trimmed string: \"%s\"\n\n", res_trim.value.string->data);
|
||||
string_destroy(to_trim);
|
||||
string_destroy(res_trim.value.string);
|
||||
|
||||
// Split string
|
||||
string_t *to_split = string_new("foo/bar/biz").value.string;
|
||||
string_result_t res_split = string_split(to_split, "/");
|
||||
if (res_split.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_split.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t count = res_split.value.split.count;
|
||||
string_t **strings = res_split.value.split.strings;
|
||||
|
||||
printf("Original string: \"%s\"\nSplitted string: ", to_split->data);
|
||||
for (size_t idx = 0; idx < count; idx++) {
|
||||
printf("\"%s\" ", strings[idx]->data);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
string_split_destroy(strings, count);
|
||||
string_destroy(to_split);
|
||||
|
||||
string_destroy(str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
95
examples/string_basic.c
Normal file
95
examples/string_basic.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Basic string operations example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/string.h"
|
||||
|
||||
int main(void) {
|
||||
// Create a new string
|
||||
string_result_t res = string_new("Hello, ");
|
||||
if (res.status != STRING_OK) {
|
||||
printf("Error: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
string_t *str1 = res.value.string;
|
||||
printf("Created string: \"%s\"\n", str1->data);
|
||||
printf("Character count: %zu (%zu actual bytes)\n", string_size(str1), str1->byte_size);
|
||||
|
||||
string_result_t res_clone = string_clone(str1);
|
||||
if (res_clone.status != STRING_OK) {
|
||||
printf("Error: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
string_t *cloned = res_clone.value.string;
|
||||
printf("Cloned string: \"%s\"\n\n", cloned->data);
|
||||
string_destroy(cloned);
|
||||
|
||||
// Concatenation of strings
|
||||
string_result_t res_suffix = string_new("World! 😜");
|
||||
if (res_suffix.status != STRING_OK) {
|
||||
printf("Error: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
string_t *suffix = res_suffix.value.string;
|
||||
printf("Created another string: \"%s\"\n", suffix->data);
|
||||
printf("Character count: %zu (%zu actual bytes)\n\n", string_size(suffix), suffix->byte_size);
|
||||
|
||||
string_result_t res_cat = string_concat(str1, suffix);
|
||||
if (res_cat.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_cat.message);
|
||||
return 1;
|
||||
}
|
||||
string_destroy(suffix);
|
||||
|
||||
string_t *concat_str = res_cat.value.string;
|
||||
printf("Concatenation result: \"%s\"\n\n", concat_str->data);
|
||||
|
||||
// String contains
|
||||
string_t *haystack = string_new("The quick brown fox jumps over the lazy dog.").value.string;
|
||||
string_t *needle = string_new("brown fox").value.string;
|
||||
|
||||
string_result_t res_contains = string_contains(haystack, needle);
|
||||
if (res_contains.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_contains.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (res_contains.value.idx != -1) {
|
||||
printf("Substring found. Starting at index %ld\n\n", res_contains.value.idx);
|
||||
}
|
||||
|
||||
string_destroy(haystack);
|
||||
string_destroy(needle);
|
||||
|
||||
// String slicing
|
||||
string_result_t res_slice = string_slice(concat_str, 7, 14);
|
||||
if (res_slice.status != STRING_OK) {
|
||||
printf("Error: %s\n", res_slice.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Slice of string: \"%s\"\n\n", res_slice.value.string->data);
|
||||
string_destroy(res_slice.value.string);
|
||||
|
||||
// String equality
|
||||
string_t *compare = string_new("hello, World! 😜").value.string;
|
||||
string_result_t res_eq = string_eq(concat_str, compare, true);
|
||||
if (res_eq.value.is_equ) {
|
||||
printf("The two strings are equal\n\n");
|
||||
} else {
|
||||
printf("The two strings are not equal\n\n");
|
||||
}
|
||||
|
||||
string_destroy(compare);
|
||||
string_destroy(concat_str);
|
||||
string_destroy(str1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
78
examples/vector_basic.c
Normal file
78
examples/vector_basic.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Basic vector operations example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/vector.h"
|
||||
|
||||
int main(void) {
|
||||
// Create a vector of 3 integers
|
||||
vector_result_t res = vector_new(3, sizeof(int));
|
||||
if (res.status != VECTOR_OK) {
|
||||
printf("Error while creating vector: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
vector_t *vector = res.value.vector;
|
||||
|
||||
// Push some values to trigger reallocation
|
||||
for (int idx = 0; idx < 5; idx++) {
|
||||
vector_result_t add_res = vector_push(vector, &idx);
|
||||
if (add_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Print vector size and capacity
|
||||
printf("Vector size (should be 5): %zu\n", vector_size(vector));
|
||||
printf("Vector capacity (should be > 5): %zu\n\n", vector_capacity(vector));
|
||||
|
||||
// Print the whole vector
|
||||
size_t sz = vector_size(vector);
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t get_res = vector_get(vector, idx);
|
||||
if (get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
int val = *(int *)get_res.value.element;
|
||||
printf("vec[%zu] (should be '%zu') = %d\n", idx, idx, val);
|
||||
}
|
||||
}
|
||||
|
||||
// Set an element at index 2
|
||||
int new_val = 0xBABE;
|
||||
vector_result_t set_res = vector_set(vector, 2, &new_val);
|
||||
if (set_res.status == VECTOR_OK) {
|
||||
printf("vec[2] (should be updated to 'BABE'): %X\n\n", new_val);
|
||||
}
|
||||
|
||||
// Pop last element
|
||||
vector_result_t pop_res = vector_pop(vector);
|
||||
if (pop_res.status == VECTOR_OK) {
|
||||
int val = *(int *)pop_res.value.element;
|
||||
printf("Popped value (should be 5) : %d\n\n", val);
|
||||
}
|
||||
|
||||
// Clear vector
|
||||
vector_result_t clear_res = vector_clear(vector);
|
||||
if (clear_res.status != VECTOR_OK) {
|
||||
printf("Cannot clear vector: %s\n", clear_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
printf("Vector cleared (size should be 0): %zu\n\n", vector_size(vector));
|
||||
}
|
||||
|
||||
// Free vector
|
||||
vector_result_t del_res = vector_destroy(vector);
|
||||
if (del_res.status != VECTOR_OK) {
|
||||
printf("Error while destroying the vector: %s\n", del_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
118
examples/vector_functional.c
Normal file
118
examples/vector_functional.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Vector functional operations example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/vector.h"
|
||||
|
||||
#define UNUSED(X) (void)(X)
|
||||
|
||||
static void square(void *element, void *env);
|
||||
static int is_even(const void *element, void *env);
|
||||
static void adder(void *accumulator, const void *element, void *env);
|
||||
|
||||
int main(void) {
|
||||
// Create a vector
|
||||
vector_result_t res = vector_new(1, sizeof(int));
|
||||
if (res.status != VECTOR_OK) {
|
||||
printf("Error while creating vector: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
vector_t *vector = res.value.vector;
|
||||
|
||||
// Map vector elements
|
||||
for (size_t idx = 1; idx <= 5; idx++) {
|
||||
vector_result_t map_push_res = vector_push(vector, &idx);
|
||||
if (map_push_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", map_push_res.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t sz = vector_size(vector);
|
||||
|
||||
// Square vector elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
|
||||
vector_result_t map_res = vector_map(vector, square, NULL);
|
||||
if (map_res.status != VECTOR_OK) {
|
||||
printf("Error while mapping vector: %s\n", map_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Squared vector: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t map_get_res = vector_get(vector, idx);
|
||||
if (map_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)map_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Filter vector elements: [1, 4, 9, 16, 25] -> [4, 16]
|
||||
vector_result_t filter_res = vector_filter(vector, is_even, NULL);
|
||||
if (filter_res.status != VECTOR_OK) {
|
||||
printf("Error while filtering vector: %s\n", filter_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sz = vector_size(vector);
|
||||
|
||||
printf("Filtered vector: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t map_get_res = vector_get(vector, idx);
|
||||
if (map_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)map_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Reduce vector elements: [4, 16] -> 20
|
||||
int sum = 0;
|
||||
vector_result_t reduce_res = vector_reduce(vector, &sum, adder, NULL);
|
||||
if (reduce_res.status != VECTOR_OK) {
|
||||
printf("Error while reducing vector: %s\n", reduce_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Sum of vector: %d\n", sum);
|
||||
|
||||
// Free vector
|
||||
vector_result_t del_res = vector_destroy(vector);
|
||||
if (del_res.status != VECTOR_OK) {
|
||||
printf("Error while destroying the vector: %s\n", del_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void square(void *element, void *env) {
|
||||
UNUSED(env);
|
||||
int *value = (int*)element;
|
||||
*value = (*value) * (*value);
|
||||
}
|
||||
|
||||
int is_even(const void *element, void *env) {
|
||||
UNUSED(env);
|
||||
int value = *(int*)element;
|
||||
|
||||
return (value % 2) == 0;
|
||||
}
|
||||
|
||||
void adder(void *accumulator, const void *element, void *env) {
|
||||
UNUSED(env);
|
||||
*(int*)accumulator += *(int*)element;
|
||||
}
|
||||
|
||||
110
examples/vector_sorting.c
Normal file
110
examples/vector_sorting.c
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Vector sorting example.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/vector.h"
|
||||
|
||||
static vector_order_t cmp_int_asc(const void *x, const void *y);
|
||||
static vector_order_t cmp_int_desc(const void *x, const void *y);
|
||||
|
||||
int main(void) {
|
||||
// Create a vector
|
||||
vector_result_t res = vector_new(1, sizeof(int));
|
||||
if (res.status != VECTOR_OK) {
|
||||
printf("Error while creating vector: %s\n", res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
vector_t *vector = res.value.vector;
|
||||
|
||||
// Sort vector in ascending order
|
||||
int values[] = {5, 10, -9, 3, 1, 0, 4};
|
||||
for (size_t idx = 0; idx < 7; idx++) {
|
||||
vector_result_t sort_push_res = vector_push(vector, &values[idx]);
|
||||
if (sort_push_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", sort_push_res.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Added new elements. Before sort: ");
|
||||
|
||||
size_t sz = vector_size(vector);
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
vector_result_t sort_asc_res = vector_sort(vector, cmp_int_asc);
|
||||
if (sort_asc_res.status != VECTOR_OK) {
|
||||
printf("Cannot sort array: %s\n", sort_asc_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("After sort in ascending order: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Sort vector in descending order
|
||||
vector_result_t sort_desc_res = vector_sort(vector, cmp_int_desc);
|
||||
if (sort_desc_res.status != VECTOR_OK) {
|
||||
printf("Cannot sort array: %s\n", sort_desc_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("After sort in descending order: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
// Free vector
|
||||
vector_result_t del_res = vector_destroy(vector);
|
||||
if (del_res.status != VECTOR_OK) {
|
||||
printf("Error while destroying the vector: %s\n", del_res.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vector_order_t cmp_int_asc(const void *x, const void *y) {
|
||||
int x_int = *(const int*)x;
|
||||
int y_int = *(const int*)y;
|
||||
|
||||
if (x_int < y_int) return VECTOR_ORDER_LT;
|
||||
if (x_int > y_int) return VECTOR_ORDER_GT;
|
||||
|
||||
return VECTOR_ORDER_EQ;
|
||||
}
|
||||
|
||||
vector_order_t cmp_int_desc(const void *x, const void *y) {
|
||||
return cmp_int_asc(y, x);
|
||||
}
|
||||
|
||||
941
src/string.c
Normal file
941
src/string.c
Normal file
@@ -0,0 +1,941 @@
|
||||
#define SET_MSG(result, msg) \
|
||||
do { \
|
||||
snprintf((char *)(result).message, RESULT_MSG_SIZE, "%s", (const char *)msg); \
|
||||
} while (0)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "string.h"
|
||||
|
||||
// Check if a character is a space
|
||||
static inline bool is_space(unsigned char c) {
|
||||
return (c == ' ' || c == '\t' ||
|
||||
c == '\n' || c == '\r' ||
|
||||
c == '\f' || c == '\v');
|
||||
}
|
||||
|
||||
// Get byte length of an UTF-8 sequence
|
||||
static inline int utf8_char_len(unsigned char byte) {
|
||||
if ((byte & 0x80) == 0x00) return 1;
|
||||
if ((byte & 0xE0) == 0xC0) return 2;
|
||||
if ((byte & 0xF0) == 0xE0) return 3;
|
||||
if ((byte & 0xF8) == 0xF0) return 4;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate an UTF-8 symbol
|
||||
static bool utf8_is_char_valid(const char *utf8_char, int *out_len) {
|
||||
if (utf8_char == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t len = utf8_char_len((unsigned char)utf8_char[0]);
|
||||
if (len <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t idx = 1; idx < len; idx++) {
|
||||
if ((utf8_char[idx] & 0xC0) != 0x80) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (utf8_char[len] != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_len) {
|
||||
*out_len = len;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate an UTF-8 symbol and measure byte length and character count
|
||||
static bool utf8_scan(const char *str, size_t *out_byte_size, size_t *out_char_count) {
|
||||
size_t b_size = 0;
|
||||
size_t c_count = 0;
|
||||
const unsigned char *p = (const unsigned char *)str;
|
||||
|
||||
while (p[b_size] != '\0') {
|
||||
size_t len = utf8_char_len(p[b_size]);
|
||||
if (len <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t idx = 1; idx < len; idx++) {
|
||||
if (p[b_size + idx] == '\0' || (p[b_size + idx] & 0xC0) != 0x80) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
b_size += len;
|
||||
c_count++;
|
||||
}
|
||||
|
||||
*out_byte_size = b_size;
|
||||
*out_char_count = c_count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decode an UTF-8 symbol to a codepoint
|
||||
static uint32_t utf8_decode(const char *str, int *char_len) {
|
||||
unsigned char byte = (unsigned char)*str;
|
||||
*char_len = utf8_char_len(byte);
|
||||
|
||||
uint32_t result = 0;
|
||||
|
||||
switch (*char_len) {
|
||||
case 1:
|
||||
result = byte;
|
||||
break;
|
||||
case 2:
|
||||
result = ((byte & 0x1F) << 6) |
|
||||
(str[1] & 0x3F);
|
||||
break;
|
||||
case 3:
|
||||
result = ((byte & 0x0F) << 12) |
|
||||
((str[1] & 0x3F) << 6) |
|
||||
(str[2] & 0x3F);
|
||||
break;
|
||||
case 4:
|
||||
result = ((byte & 0x07) << 18) |
|
||||
((str[1] & 0x3F) << 12) |
|
||||
((str[2] & 0x3F) << 6) |
|
||||
(str[3] & 0x3F);
|
||||
break;
|
||||
default:
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encode a codepoint to an UTF-8 symbol
|
||||
static int utf8_encode(uint32_t codepoint, char *out) {
|
||||
if (codepoint <= 0x7F) {
|
||||
out[0] = (char)codepoint;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (codepoint <= 0x7FF) {
|
||||
out[0] = (char)(0xC0 | (codepoint >> 6));
|
||||
out[1] = (char)(0x80 | (codepoint & 0x3F));
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (codepoint <= 0xFFFF) {
|
||||
out[0] = (char)(0xE0 | (codepoint >> 12));
|
||||
out[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
|
||||
out[2] = (char)(0x80 | (codepoint & 0x3F));
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (codepoint <= 0x10FFFF) {
|
||||
out[0] = (char)(0xF0 | (codepoint >> 18));
|
||||
out[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
|
||||
out[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
|
||||
out[3] = (char)(0x80 | (codepoint & 0x3F));
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_new
|
||||
* @c_str: a C-string
|
||||
*
|
||||
* Returns a string_result_t containing a new String data type
|
||||
*/
|
||||
string_result_t string_new(const char *c_str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (c_str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid input string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t b_size, c_count;
|
||||
if (utf8_scan(c_str, &b_size, &c_count) == 0) {
|
||||
result.status = STRING_ERR_INVALID_UTF8;
|
||||
SET_MSG(result, "Malformed UTF-8 sequence");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string_t *str = malloc(sizeof(string_t));
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
str->data = malloc(b_size + 1);
|
||||
if (str->data == NULL) {
|
||||
free(str);
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(str->data, c_str, b_size);
|
||||
str->data[b_size] = '\0';
|
||||
str->byte_size = b_size;
|
||||
str->byte_capacity = b_size + 1;
|
||||
str->char_count = c_count;
|
||||
|
||||
result.status = STRING_OK;
|
||||
result.value.string = str;
|
||||
SET_MSG(result, "String successfully created");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_clone
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Deep copies @str
|
||||
*
|
||||
* Returns a string_result_t containing the copied string
|
||||
*/
|
||||
string_result_t string_clone(const string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string_t *str_copy = malloc(sizeof(string_t));
|
||||
if (str_copy == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
str_copy->data = malloc(str->byte_size + 1);
|
||||
if (str_copy->data == NULL) {
|
||||
free(str_copy);
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(str_copy->data, str->data, str->byte_size);
|
||||
str_copy->data[str->byte_size] = '\0';
|
||||
str_copy->byte_size = str->byte_size;
|
||||
str_copy->byte_capacity = str->byte_size + 1;
|
||||
str_copy->char_count = str->char_count;
|
||||
|
||||
result.status = STRING_OK;
|
||||
result.value.string = str_copy;
|
||||
SET_MSG(result, "String successfully copied");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_concat
|
||||
* @x: a non-null string
|
||||
* @y: a non-null string
|
||||
*
|
||||
* Concats @x and @y in a new String
|
||||
*
|
||||
* Returns a string_result_t containing the new string
|
||||
*/
|
||||
string_result_t string_concat(const string_t *x, const string_t *y) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (x == NULL || y == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid strings");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (x->byte_size > SIZE_MAX - y->byte_size - 1) {
|
||||
result.status = STRING_ERR_OVERFLOW;
|
||||
SET_MSG(result, "Concatenation exceeds size limits");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t new_size = x->byte_size + y->byte_size;
|
||||
char *buf = malloc(new_size + 1);
|
||||
if (buf == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(buf, x->data, x->byte_size);
|
||||
memcpy(buf + x->byte_size, y->data, y->byte_size);
|
||||
buf[new_size] = '\0';
|
||||
result = string_new(buf);
|
||||
free(buf);
|
||||
|
||||
SET_MSG(result, "String successfully concatenated");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_contains
|
||||
* @haystack: a non-null string
|
||||
* @needle: a non-null string
|
||||
*
|
||||
* Finds @needle on @haystack
|
||||
*
|
||||
* Returns a string_result_t containing the index to the beginning of the located string
|
||||
* (if the substring has been found)
|
||||
*/
|
||||
string_result_t string_contains(const string_t *haystack, const string_t *needle) {
|
||||
string_result_t result = {
|
||||
.status = STRING_OK,
|
||||
.value.idx = -1
|
||||
};
|
||||
|
||||
if (haystack == NULL || needle == NULL || needle->byte_size == 0) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid substrings");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *found = strstr(haystack->data, needle->data);
|
||||
if (found) {
|
||||
size_t char_idx = 0;
|
||||
const char *ptr = haystack->data;
|
||||
while (ptr < found) {
|
||||
ptr += utf8_char_len((unsigned char)*ptr);
|
||||
char_idx++;
|
||||
}
|
||||
|
||||
result.value.idx = (int64_t)char_idx;
|
||||
SET_MSG(result, "Substring found");
|
||||
} else {
|
||||
SET_MSG(result, "Substring not found");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_slice
|
||||
* @str: a non-null string
|
||||
* @start: the lower bound (inclusive)
|
||||
* @end: the upper bound (inclusive)
|
||||
*
|
||||
* Extracts a slice from @str between @start and @end (inclusive)
|
||||
*
|
||||
* Returns a string_result_t data type containing the slice
|
||||
*/
|
||||
string_result_t string_slice(const string_t *str, size_t start, size_t end) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (start > end || end >= str->char_count) {
|
||||
result.status = STRING_ERR_OVERFLOW;
|
||||
SET_MSG(result, "Index out of bounds");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t start_byte_offset = 0;
|
||||
for (size_t idx = 0; idx < start; idx++) {
|
||||
start_byte_offset += utf8_char_len((unsigned char)str->data[start_byte_offset]);
|
||||
}
|
||||
|
||||
size_t end_byte_offset = start_byte_offset;
|
||||
for (size_t idx = start; idx <= end; idx++) {
|
||||
end_byte_offset += utf8_char_len((unsigned char)str->data[end_byte_offset]);
|
||||
}
|
||||
|
||||
const size_t slice_byte_size = (end_byte_offset - start_byte_offset);
|
||||
|
||||
string_t *slice = malloc(sizeof(string_t));
|
||||
if (slice == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
slice->data = malloc(slice_byte_size + 1);
|
||||
if (slice->data == NULL) {
|
||||
free(slice);
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(slice->data, str->data + start_byte_offset, slice_byte_size);
|
||||
slice->data[slice_byte_size] = '\0';
|
||||
|
||||
slice->byte_size = slice_byte_size;
|
||||
slice->byte_capacity = slice_byte_size + 1;
|
||||
slice->char_count = end - start + 1;
|
||||
|
||||
result.status = STRING_OK;
|
||||
result.value.string = slice;
|
||||
SET_MSG(result, "String sliced successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_eq
|
||||
* @x: a non-null string
|
||||
* @y: a non-null string
|
||||
* @case_sensitive: boolean value for case sensitive comparison
|
||||
*
|
||||
* Compares two Strings
|
||||
*
|
||||
* Returns a string_result_t containing the comparison result
|
||||
*/
|
||||
string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensitive) {
|
||||
string_result_t result = {
|
||||
.status = STRING_OK,
|
||||
.value.is_equ = false
|
||||
};
|
||||
|
||||
if (x == NULL || y == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid strings");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (x->char_count != y->char_count) {
|
||||
result.value.is_equ = false;
|
||||
result.status = STRING_OK;
|
||||
SET_MSG(result, "Comparison completed successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (case_sensitive) {
|
||||
result.value.is_equ = (strcmp(x->data, y->data) == 0);
|
||||
} else {
|
||||
const char *p1 = x->data, *p2 = y->data;
|
||||
while (*p1 && *p2) {
|
||||
int l1, l2;
|
||||
|
||||
const uint32_t codepoint1 = utf8_decode(p1, &l1);
|
||||
const uint32_t codepoint2 = utf8_decode(p2, &l2);
|
||||
const uint32_t c1 = (codepoint1 >= 'A' && codepoint1 <= 'Z') ? codepoint1 + 32 : codepoint1;
|
||||
const uint32_t c2 = (codepoint2 >= 'A' && codepoint2 <= 'Z') ? codepoint2 + 32 : codepoint2;
|
||||
|
||||
if (c1 != c2) {
|
||||
result.value.is_equ = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
p1 += l1;
|
||||
p2 += l2;
|
||||
}
|
||||
|
||||
result.value.is_equ = (*p1 == *p2);
|
||||
}
|
||||
|
||||
SET_MSG(result, "Comparison completed successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_get_at
|
||||
* @str: a non-null string
|
||||
* @position: the position of the symbol to read
|
||||
*
|
||||
* Gets symbol indexed by @position from @str
|
||||
*
|
||||
* Returns a string_result_t containing the symbol as a C string
|
||||
*/
|
||||
string_result_t string_get_at(const string_t *str, size_t position) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (position >= str->char_count) {
|
||||
result.status = STRING_ERR_OVERFLOW;
|
||||
SET_MSG(result, "Index out of bounds");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *ptr = str->data;
|
||||
for (size_t idx = 0; idx < position; idx++) {
|
||||
ptr += utf8_char_len((unsigned char)*ptr);
|
||||
}
|
||||
|
||||
int char_len = utf8_char_len((unsigned char)*ptr);
|
||||
char *utf8_char = malloc(char_len + 1);
|
||||
if (utf8_char == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(utf8_char, ptr, char_len);
|
||||
utf8_char[char_len] = '\0';
|
||||
|
||||
result.value.symbol = utf8_char;
|
||||
result.status = STRING_OK;
|
||||
SET_MSG(result, "Symbol successfully retrieved");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_set_at
|
||||
* @str: a non-null string
|
||||
* @position: the position to write into
|
||||
* @utf8_char: an UTF8 symbol
|
||||
*
|
||||
* Writes @utf8_char into @str at index @position
|
||||
*
|
||||
* Returns a string_result_t data type
|
||||
*/
|
||||
string_result_t string_set_at(const string_t *str, size_t position, const char *utf8_char) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int new_char_bytes;
|
||||
if (utf8_is_char_valid(utf8_char, &new_char_bytes) == 0) {
|
||||
result.status = STRING_ERR_INVALID_UTF8;
|
||||
SET_MSG(result, "Invalid UTF-8 character");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (position >= str->char_count) {
|
||||
result.status = STRING_ERR_OVERFLOW;
|
||||
SET_MSG(result, "Index out of bounds");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Locate the byte offset of the character to replace
|
||||
const char *pos = str->data;
|
||||
for (size_t idx = 0; idx < position; idx++) {
|
||||
pos += utf8_char_len((unsigned char)*pos);
|
||||
}
|
||||
|
||||
const size_t prefix_len = pos - str->data;
|
||||
const int old_char_bytes = utf8_char_len((unsigned char)*pos);
|
||||
const size_t suffix_len = str->byte_size - prefix_len - old_char_bytes;
|
||||
const size_t new_total_bytes = prefix_len + new_char_bytes + suffix_len;
|
||||
|
||||
string_t *new_str = malloc(sizeof(string_t));
|
||||
if (new_str == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
new_str->data = malloc(new_total_bytes + 1);
|
||||
if (new_str->data == NULL) {
|
||||
free(new_str);
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Copy prefix data from original string
|
||||
memcpy(new_str->data, str->data, prefix_len);
|
||||
// Copy the new character at requested index
|
||||
memcpy(new_str->data + prefix_len, utf8_char, new_char_bytes);
|
||||
// Copy suffix data from the original string by skipping the overwritten character
|
||||
memcpy(new_str->data + prefix_len + new_char_bytes, pos + old_char_bytes, suffix_len);
|
||||
new_str->data[new_total_bytes] = '\0';
|
||||
|
||||
new_str->byte_size = new_total_bytes;
|
||||
new_str->byte_capacity = new_total_bytes + 1;
|
||||
new_str->char_count = str->char_count;
|
||||
|
||||
result.status = STRING_OK;
|
||||
result.value.string = new_str;
|
||||
SET_MSG(result, "Symbol successfully set");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_to_lower
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Converts a String to lowercase
|
||||
*
|
||||
* Returns a string_result_t containing a new string
|
||||
*/
|
||||
string_result_t string_to_lower(const string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *buf = malloc(str->byte_capacity);
|
||||
if (buf == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *src = str->data;
|
||||
char *dst = buf;
|
||||
|
||||
while (*src) {
|
||||
int len;
|
||||
uint32_t codepoint = utf8_decode(src, &len);
|
||||
uint32_t lower = (codepoint >= 'A' && codepoint <= 'Z') ? codepoint + 32 : codepoint;
|
||||
dst += utf8_encode(lower, dst);
|
||||
src += len;
|
||||
}
|
||||
*dst = '\0';
|
||||
result = string_new(buf);
|
||||
free(buf);
|
||||
|
||||
SET_MSG(result, "String successfully converted to lowercase");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_to_upper
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Converts a String to uppercase
|
||||
*
|
||||
* Returns a string_result_t containing a new string
|
||||
*/
|
||||
string_result_t string_to_upper(const string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *buf = malloc(str->byte_capacity);
|
||||
if (buf == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *src = str->data;
|
||||
char *dst = buf;
|
||||
while (*src) {
|
||||
int len;
|
||||
uint32_t codepoint = utf8_decode(src, &len);
|
||||
uint32_t upper = (codepoint >= 'a' && codepoint <= 'z') ? codepoint - 32 : codepoint;
|
||||
dst += utf8_encode(upper, dst);
|
||||
src += len;
|
||||
}
|
||||
*dst = '\0';
|
||||
result = string_new(buf);
|
||||
free(buf);
|
||||
|
||||
SET_MSG(result, "String successfully converted to uppercase");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_reverse
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Reverses @str
|
||||
*
|
||||
* Returns a new string_result_t containing the reversed string
|
||||
*/
|
||||
string_result_t string_reverse(const string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *buf = malloc(str->byte_capacity);
|
||||
if (buf == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char **pos = malloc(str->char_count * sizeof(char *));
|
||||
if (pos == NULL) {
|
||||
free(buf);
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *ptr = str->data;
|
||||
for (size_t idx = 0; idx < str->char_count; idx++) {
|
||||
pos[idx] = ptr;
|
||||
ptr += utf8_char_len((unsigned char)*ptr);
|
||||
}
|
||||
|
||||
char *dst = buf;
|
||||
for (int64_t idx = (int64_t)str->char_count - 1; idx >= 0; idx--) {
|
||||
int len = utf8_char_len((unsigned char)*pos[idx]);
|
||||
memcpy(dst, pos[idx], len);
|
||||
dst += len;
|
||||
}
|
||||
|
||||
*dst = '\0';
|
||||
free(pos);
|
||||
result = string_new(buf);
|
||||
free(buf);
|
||||
|
||||
SET_MSG(result, "String successfully reversed");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_trim
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Trims whitespace from @str
|
||||
*
|
||||
* Returns a string_result_t containing the trimmed string
|
||||
*/
|
||||
string_result_t string_trim(const string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *start = str->data;
|
||||
while (*start && is_space((unsigned char)*start)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
if (*start == '\0') {
|
||||
return string_new("");
|
||||
}
|
||||
|
||||
const char *end = str->data + str->byte_size - 1;
|
||||
while (end > start && is_space((unsigned char)*end)) {
|
||||
end--;
|
||||
}
|
||||
|
||||
const size_t len = (end - start) + 1;
|
||||
char *trimmed = malloc(len + 1);
|
||||
if (trimmed == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(trimmed, start, len);
|
||||
trimmed[len] = '\0';
|
||||
result = string_new(trimmed);
|
||||
free(trimmed);
|
||||
|
||||
result.status = STRING_OK;
|
||||
SET_MSG(result, "String successfully trimmed");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_split
|
||||
* @str: a non-null string
|
||||
* @delim: delimiter string
|
||||
*
|
||||
* Splits @str by @delim
|
||||
*
|
||||
* Returns a string_result_t containing an array of String pointers
|
||||
*/
|
||||
string_result_t string_split(const string_t *str, const char *delim) {
|
||||
string_result_t result = {0};
|
||||
string_result_t tmp_res = {0};
|
||||
|
||||
if (str == NULL || delim == NULL || delim[0] == '\0') {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid strings");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *ptr = str->data;
|
||||
const size_t delim_len = strlen(delim);
|
||||
size_t count = 1;
|
||||
|
||||
while ((ptr = strstr(ptr, delim))) {
|
||||
count++;
|
||||
ptr += delim_len;
|
||||
}
|
||||
|
||||
string_t **string_array = malloc(count * sizeof(string_t *));
|
||||
if (string_array == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *start = str->data;
|
||||
size_t idx = 0;
|
||||
|
||||
while ((ptr = strstr(start, delim))) {
|
||||
const size_t part_len = ptr - start;
|
||||
char *tmp = malloc(part_len + 1);
|
||||
if (tmp == NULL) {
|
||||
result.status = STRING_ERR_ALLOCATE;
|
||||
SET_MSG(result, "Cannot allocate memory");
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memcpy(tmp, start, part_len);
|
||||
tmp[part_len] = '\0';
|
||||
|
||||
tmp_res = string_new(tmp);
|
||||
free(tmp);
|
||||
if (tmp_res.status != STRING_OK) { result = tmp_res; goto cleanup; }
|
||||
|
||||
string_array[idx++] = tmp_res.value.string;
|
||||
start = ptr + delim_len;
|
||||
}
|
||||
|
||||
tmp_res = string_new(start);
|
||||
if (tmp_res.status != STRING_OK) { result = tmp_res; goto cleanup; }
|
||||
|
||||
string_array[idx] = tmp_res.value.string;
|
||||
|
||||
result.status = STRING_OK;
|
||||
result.value.split.strings = string_array;
|
||||
result.value.split.count = count;
|
||||
SET_MSG(result, "String successfully split");
|
||||
|
||||
return result;
|
||||
|
||||
cleanup:
|
||||
for (size_t j = 0; j < idx; j++) { string_destroy(string_array[j]); }
|
||||
free(string_array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_destroy
|
||||
* @str: a non-null string
|
||||
*
|
||||
* Destroys @str
|
||||
*
|
||||
* Returns a string_result_t data type
|
||||
*/
|
||||
string_result_t string_destroy(string_t *str) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (str == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
free(str->data);
|
||||
free(str);
|
||||
|
||||
result.status = STRING_OK;
|
||||
SET_MSG(result, "String successfully deleted");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* string_split_destroy
|
||||
* @split: an array of pointers of String
|
||||
* @count: the number of elements
|
||||
*
|
||||
* Destroys the @split array of Strings
|
||||
*
|
||||
* Returns a string_result_t data type
|
||||
*/
|
||||
string_result_t string_split_destroy(string_t **split, size_t count) {
|
||||
string_result_t result = {0};
|
||||
|
||||
if (split == NULL) {
|
||||
result.status = STRING_ERR_INVALID;
|
||||
SET_MSG(result, "Invalid string");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < count; idx++) {
|
||||
string_destroy(split[idx]);
|
||||
}
|
||||
|
||||
free(split);
|
||||
|
||||
result.status = STRING_OK;
|
||||
SET_MSG(result, "Array of strings successfully deleted");
|
||||
|
||||
return result;
|
||||
}
|
||||
70
src/string.h
Normal file
70
src/string.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef STRING_H
|
||||
#define STRING_H
|
||||
|
||||
#define RESULT_MSG_SIZE 64
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
STRING_OK = 0x0,
|
||||
STRING_ERR_ALLOCATE,
|
||||
STRING_ERR_INVALID,
|
||||
STRING_ERR_INVALID_UTF8,
|
||||
STRING_ERR_OVERFLOW
|
||||
} string_status_t;
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t byte_size; // Size in bytes minus the NULL terminator
|
||||
size_t byte_capacity; // Total allocated memory
|
||||
size_t char_count; // Number of symbols
|
||||
} string_t;
|
||||
|
||||
typedef struct {
|
||||
string_status_t status;
|
||||
uint8_t message[RESULT_MSG_SIZE];
|
||||
union {
|
||||
string_t *string; // For new, clone, slice, reverse, trim
|
||||
char *symbol; // For get_at
|
||||
int64_t idx; // For contains
|
||||
bool is_equ; // For comparison
|
||||
struct { // For split
|
||||
string_t **strings;
|
||||
size_t count;
|
||||
} split;
|
||||
} value;
|
||||
} string_result_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
#extern "C" {
|
||||
#endif
|
||||
|
||||
// Public APIs
|
||||
string_result_t string_new(const char *c_str);
|
||||
string_result_t string_clone(const string_t *str);
|
||||
string_result_t string_concat(const string_t *x, const string_t *y);
|
||||
string_result_t string_contains(const string_t *haystack, const string_t *needle);
|
||||
string_result_t string_slice(const string_t *str, size_t start, size_t end);
|
||||
string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensitive);
|
||||
string_result_t string_get_at(const string_t *str, size_t position);
|
||||
string_result_t string_set_at(const string_t *str, size_t position, const char *utf8_char);
|
||||
string_result_t string_to_lower(const string_t *str);
|
||||
string_result_t string_to_upper(const string_t *str);
|
||||
string_result_t string_reverse(const string_t *str);
|
||||
string_result_t string_trim(const string_t *str);
|
||||
string_result_t string_split(const string_t *str, const char *delim);
|
||||
string_result_t string_destroy(string_t *str);
|
||||
string_result_t string_split_destroy(string_t **split, size_t count);
|
||||
|
||||
// Inline methods
|
||||
static inline size_t string_size(const string_t *str) {
|
||||
return str ? str->char_count : 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
329
tests/test_string.c
Normal file
329
tests/test_string.c
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Unit tests for String data type
|
||||
*/
|
||||
|
||||
#define TEST(NAME) do { \
|
||||
printf("Running test_%s...", #NAME); \
|
||||
test_##NAME(); \
|
||||
printf(" PASSED\n"); \
|
||||
} while(0)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/string.h"
|
||||
|
||||
// Test string creation
|
||||
void test_string_new(void) {
|
||||
string_result_t res = string_new("hello");
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(res.value.string != NULL);
|
||||
assert(strcmp(res.value.string->data, "hello") == 0);
|
||||
assert(string_size(res.value.string) == 5);
|
||||
assert(res.value.string->byte_size == 5);
|
||||
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
void test_string_new_empty(void) {
|
||||
string_result_t res = string_new("");
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(string_size(res.value.string) == 0);
|
||||
assert(res.value.string->byte_size == 0);
|
||||
assert(res.value.string->data[0] == '\0');
|
||||
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test cloning an existing string
|
||||
void test_string_clone(void) {
|
||||
string_t *original = string_new("Original").value.string;
|
||||
string_result_t res = string_clone(original);
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(res.value.string != original); // Different memory address
|
||||
assert(strcmp(res.value.string->data, original->data) == 0);
|
||||
assert(res.value.string->byte_size == original->byte_size);
|
||||
|
||||
string_destroy(original);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test string concatenation
|
||||
void test_string_concat(void) {
|
||||
string_t *str1 = string_new("Foo").value.string;
|
||||
string_t *str2 = string_new(" Bar").value.string;
|
||||
|
||||
string_result_t res = string_concat(str1, str2);
|
||||
assert(res.status == STRING_OK);
|
||||
assert(strcmp(res.value.string->data, "Foo Bar") == 0);
|
||||
assert(string_size(res.value.string) == 7);
|
||||
|
||||
string_destroy(str1);
|
||||
string_destroy(str2);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test if string contains substring
|
||||
void test_string_contains(void) {
|
||||
string_t *haystack = string_new("Hello 🌍 World").value.string;
|
||||
string_t *needle_ascii = string_new("World").value.string;
|
||||
string_t *needle_utf8 = string_new("🌍").value.string;
|
||||
string_t *needle_none = string_new("not found").value.string;
|
||||
|
||||
// World starts at symbol 8
|
||||
string_result_t res1 = string_contains(haystack, needle_ascii);
|
||||
assert(res1.status == STRING_OK);
|
||||
assert(res1.value.idx == 8);
|
||||
|
||||
// 🌍 is at position 6
|
||||
string_result_t res2 = string_contains(haystack, needle_utf8);
|
||||
assert(res2.status == STRING_OK);
|
||||
assert(res2.value.idx == 6);
|
||||
|
||||
// Not found should return -1
|
||||
string_result_t res3 = string_contains(haystack, needle_none);
|
||||
assert(res3.status == STRING_OK);
|
||||
assert(res3.value.idx == -1);
|
||||
|
||||
string_destroy(haystack);
|
||||
string_destroy(needle_ascii);
|
||||
string_destroy(needle_utf8);
|
||||
string_destroy(needle_none);
|
||||
}
|
||||
|
||||
// Test string slicing
|
||||
void test_string_slice(void) {
|
||||
// ASCII slice
|
||||
string_t *str1 = string_new("foobar").value.string;
|
||||
string_result_t res1 = string_slice(str1, 2, 4);
|
||||
|
||||
assert(res1.status == STRING_OK);
|
||||
assert(strcmp(res1.value.string->data, "oba") == 0);
|
||||
assert(res1.value.string->char_count == 3);
|
||||
|
||||
// UTF-8 slice
|
||||
string_t *str2 = string_new("AB😀🌍").value.string;
|
||||
string_result_t res2 = string_slice(str2, 2, 2);
|
||||
|
||||
assert(res2.status == STRING_OK);
|
||||
assert(strcmp(res2.value.string->data, "😀") == 0);
|
||||
assert(res2.value.string->byte_size == 4); // emoji = 4 bytes
|
||||
|
||||
// UTF-8 + ASCII slice
|
||||
string_result_t res3 = string_slice(str2, 0, 2);
|
||||
assert(res3.status == STRING_OK);
|
||||
assert(strcmp(res3.value.string->data, "AB😀") == 0);
|
||||
|
||||
// Invalid bounds
|
||||
string_result_t res4 = string_slice(str1, 5, 2);
|
||||
assert(res4.status == STRING_ERR_OVERFLOW);
|
||||
|
||||
res4 = string_slice(str1, 1, 50);
|
||||
assert(res4.status == STRING_ERR_OVERFLOW);
|
||||
|
||||
string_destroy(str1);
|
||||
string_destroy(str2);
|
||||
string_destroy(res1.value.string);
|
||||
string_destroy(res2.value.string);
|
||||
string_destroy(res3.value.string);
|
||||
}
|
||||
|
||||
// Test case-insensitive and sensitive comparison
|
||||
void test_string_eq(void) {
|
||||
string_t *str1 = string_new("Foo").value.string;
|
||||
string_t *str2 = string_new("foo").value.string;
|
||||
|
||||
// Case sensitive comparison should be false
|
||||
assert(string_eq(str1, str2, true).value.is_equ == false);
|
||||
// Case insensitive comparison should be true
|
||||
assert(string_eq(str1, str2, false).value.is_equ == true);
|
||||
|
||||
string_destroy(str1);
|
||||
string_destroy(str2);
|
||||
}
|
||||
|
||||
// Test string reverse using UTF-8 symbols
|
||||
void test_string_reverse_utf8(void) {
|
||||
string_t *str = string_new("A🌍Z").value.string;
|
||||
|
||||
string_result_t res = string_reverse(str);
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(string_size(res.value.string) == 3);
|
||||
assert(strcmp(res.value.string->data, "Z🌍A") == 0);
|
||||
assert(string_size(res.value.string) == 3);
|
||||
|
||||
string_destroy(str);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test string get_at
|
||||
void test_string_get_at(void) {
|
||||
string_t *str = string_new("AB😀🌍").value.string;
|
||||
|
||||
// 😀 is at index 2
|
||||
string_result_t res1 = string_get_at(str, 2);
|
||||
assert(res1.status == STRING_OK);
|
||||
assert(strcmp((char*)res1.value.symbol, "😀") == 0);
|
||||
free(res1.value.symbol);
|
||||
|
||||
// 🌍 is at index 3
|
||||
string_result_t res2 = string_get_at(str, 3);
|
||||
assert(res2.status == STRING_OK);
|
||||
assert(strcmp((char*)res2.value.symbol, "🌍") == 0);
|
||||
free(res2.value.symbol);
|
||||
|
||||
string_destroy(str);
|
||||
}
|
||||
|
||||
// Test string get_at with invalid index
|
||||
void test_string_get_at_overflow(void) {
|
||||
string_t *str = string_new("ABC").value.string;
|
||||
|
||||
string_result_t res = string_get_at(str, 50);
|
||||
assert(res.status == STRING_ERR_OVERFLOW);
|
||||
|
||||
string_destroy(str);
|
||||
}
|
||||
|
||||
// Test mutation of UTF-8 symbol
|
||||
void test_string_set_at(void) {
|
||||
string_t *str = string_new("ABC").value.string;
|
||||
|
||||
// Replace 'B' with an emoji
|
||||
string_result_t res = string_set_at(str, 1, "😀");
|
||||
string_t *altered = res.value.string;
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(strcmp(altered->data, "A😀C") == 0);
|
||||
assert(string_size(altered) == 3);
|
||||
assert(altered->byte_size == 6); // that is: A (1B) + emoji (4B) + C (1B)
|
||||
|
||||
string_destroy(str);
|
||||
string_destroy(altered);
|
||||
}
|
||||
|
||||
// Test mutation of invalid UTF-8 symbol
|
||||
void test_string_set_at_invalid_utf8(void) {
|
||||
string_t *str = string_new("ABC").value.string;
|
||||
|
||||
const char * const invalid_sym1 = "\xFF";
|
||||
const char * const invalid_sym2 = "\x80";
|
||||
|
||||
string_result_t res1 = string_set_at(str, 1, invalid_sym1);
|
||||
assert(res1.status == STRING_ERR_INVALID_UTF8);
|
||||
|
||||
string_result_t res2 = string_set_at(str, 1, invalid_sym2);
|
||||
assert(res2.status == STRING_ERR_INVALID_UTF8);
|
||||
|
||||
string_destroy(str);
|
||||
}
|
||||
|
||||
// Test mutation with overflow
|
||||
void test_string_set_at_overflow(void) {
|
||||
string_t *str = string_new("ABC").value.string;
|
||||
|
||||
string_result_t res = string_set_at(str, 10, "a");
|
||||
assert(res.status == STRING_ERR_OVERFLOW);
|
||||
|
||||
string_destroy(str);
|
||||
}
|
||||
|
||||
// Test string to lowercase
|
||||
void test_string_to_lower(void) {
|
||||
string_t *str = string_new("AbC").value.string;
|
||||
string_result_t res = string_to_lower(str);
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(strcmp(res.value.string->data, "abc") == 0);
|
||||
|
||||
string_destroy(str);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test string to uppercase
|
||||
void test_string_to_upper(void) {
|
||||
string_t *str = string_new("aBc").value.string;
|
||||
string_result_t res = string_to_upper(str);
|
||||
|
||||
assert(res.status == STRING_OK);
|
||||
assert(strcmp(res.value.string->data, "ABC") == 0);
|
||||
|
||||
string_destroy(str);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test whitespace trimming
|
||||
void test_string_trim(void) {
|
||||
string_t *str = string_new(" \t Foo Bar \n ").value.string;
|
||||
|
||||
string_result_t res = string_trim(str);
|
||||
assert(res.status == STRING_OK);
|
||||
assert(strcmp(res.value.string->data, "Foo Bar") == 0);
|
||||
|
||||
string_destroy(str);
|
||||
string_destroy(res.value.string);
|
||||
}
|
||||
|
||||
// Test string splitting into an array
|
||||
void test_string_split(void) {
|
||||
string_t *str = string_new("Red,Green,Blue").value.string;
|
||||
|
||||
string_result_t res = string_split(str, ",");
|
||||
assert(res.status == STRING_OK);
|
||||
assert(res.value.split.count == 3);
|
||||
|
||||
const size_t count = res.value.split.count;
|
||||
string_t **strings = res.value.split.strings;
|
||||
|
||||
const char *expected[] = { "Red", "Green", "Blue" };
|
||||
for (size_t idx = 0; idx < count; idx++) {
|
||||
assert(strcmp(strings[idx]->data, expected[idx]) == 0);
|
||||
}
|
||||
|
||||
string_split_destroy(strings, count);
|
||||
string_destroy(str);
|
||||
}
|
||||
|
||||
// Test string destroy
|
||||
void test_string_destroy(void) {
|
||||
string_t *str = string_new("delete me").value.string;
|
||||
|
||||
string_result_t res = string_destroy(str);
|
||||
assert(res.status == STRING_OK);
|
||||
|
||||
string_result_t res_null = string_destroy(NULL);
|
||||
assert(res_null.status == STRING_ERR_INVALID);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Running String unit tests ===\n\n");
|
||||
|
||||
TEST(string_new);
|
||||
TEST(string_new_empty);
|
||||
TEST(string_clone);
|
||||
TEST(string_concat);
|
||||
TEST(string_contains);
|
||||
TEST(string_slice);
|
||||
TEST(string_eq);
|
||||
TEST(string_reverse_utf8);
|
||||
TEST(string_get_at);
|
||||
TEST(string_get_at_overflow);
|
||||
TEST(string_set_at);
|
||||
TEST(string_set_at_overflow);
|
||||
TEST(string_set_at_invalid_utf8);
|
||||
TEST(string_to_lower);
|
||||
TEST(string_to_upper);
|
||||
TEST(string_trim);
|
||||
TEST(string_split);
|
||||
TEST(string_destroy);
|
||||
|
||||
printf("\n=== All tests passed! ===\n");
|
||||
return 0;
|
||||
}
|
||||
526
usage.c
526
usage.c
@@ -1,526 +0,0 @@
|
||||
/*
|
||||
* Sample usage of the Datum library.
|
||||
*
|
||||
* This program is a complete example on how to use Datum
|
||||
* with *verbose* error checking. For a more minimal usage, you may want to ignore
|
||||
* return messages/codes and get straight to the actual result. See the early
|
||||
* part of the README.md file for such example (use it at your own risk).
|
||||
*
|
||||
* Developed by Marco Cetica (c) 2025, <email@marcocetica.com>
|
||||
*
|
||||
*/
|
||||
#define SEP(SIZE) do { \
|
||||
for (size_t i = 0; i < SIZE; i++) { \
|
||||
printf("="); \
|
||||
}; \
|
||||
puts("\n"); \
|
||||
} while(0)
|
||||
|
||||
#define UNUSED(X) (void)(X)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "src/vector.h"
|
||||
#include "src/map.h"
|
||||
#include "src/bigint.h"
|
||||
|
||||
static int vector_usage(void);
|
||||
static int map_usage(void);
|
||||
static int bigint_usage(void);
|
||||
|
||||
static vector_order_t cmp_int_asc(const void *x, const void *y);
|
||||
static vector_order_t cmp_int_desc(const void *x, const void *y);
|
||||
static void square(void *element, void *env);
|
||||
static int is_even(const void *element, void *env);
|
||||
static void adder(void *accumulator, const void *element, void *env);
|
||||
|
||||
int main(void) {
|
||||
int st;
|
||||
|
||||
st = vector_usage();
|
||||
if (st) { return st; }
|
||||
|
||||
SEP(50);
|
||||
|
||||
st = map_usage();
|
||||
if (st) { return st; }
|
||||
|
||||
SEP(50);
|
||||
|
||||
st = bigint_usage();
|
||||
if (st) { return st; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
vector_order_t cmp_int_asc(const void *x, const void *y) {
|
||||
int x_int = *(const int*)x;
|
||||
int y_int = *(const int*)y;
|
||||
|
||||
if (x_int < y_int) return VECTOR_ORDER_LT;
|
||||
if (x_int > y_int) return VECTOR_ORDER_GT;
|
||||
|
||||
return VECTOR_ORDER_EQ;
|
||||
}
|
||||
|
||||
vector_order_t cmp_int_desc(const void *x, const void *y) {
|
||||
return cmp_int_asc(y, x);
|
||||
}
|
||||
|
||||
void square(void *element, void *env) {
|
||||
UNUSED(env);
|
||||
int *value = (int*)element;
|
||||
*value = (*value) * (*value);
|
||||
}
|
||||
|
||||
int is_even(const void *element, void *env) {
|
||||
UNUSED(env);
|
||||
int value = *(int*)element;
|
||||
|
||||
return (value % 2) == 0;
|
||||
}
|
||||
|
||||
void adder(void *accumulator, const void *element, void *env) {
|
||||
UNUSED(env);
|
||||
*(int*)accumulator += *(int*)element;
|
||||
}
|
||||
|
||||
int vector_usage(void) {
|
||||
// Create a vector of 3 integers
|
||||
vector_result_t res = vector_new(3, sizeof(int));
|
||||
if (res.status != VECTOR_OK) {
|
||||
printf("Error while creating vector: %s\n", res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
vector_t *vector = res.value.vector;
|
||||
|
||||
// Push some values to trigger reallocation
|
||||
for (int idx = 0; idx < 5; idx++) {
|
||||
vector_result_t add_res = vector_push(vector, &idx);
|
||||
if (add_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Print vector size and capacity
|
||||
printf("Vector size (should be 5): %zu\n", vector_size(vector));
|
||||
printf("Vector capacity (should be > 5): %zu\n\n", vector_capacity(vector));
|
||||
|
||||
// Print the whole vector
|
||||
size_t sz = vector_size(vector);
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t get_res = vector_get(vector, idx);
|
||||
if (get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int val = *(int *)get_res.value.element;
|
||||
printf("vec[%zu] (should be '%zu') = %d\n", idx, idx, val);
|
||||
}
|
||||
}
|
||||
|
||||
// Set an element at index 2
|
||||
int new_val = 0xBABE;
|
||||
vector_result_t set_res = vector_set(vector, 2, &new_val);
|
||||
if (set_res.status == VECTOR_OK) {
|
||||
printf("vec[2] (should be updated to 'BABE'): %X\n\n", new_val);
|
||||
}
|
||||
|
||||
// Pop last element
|
||||
vector_result_t pop_res = vector_pop(vector);
|
||||
if (pop_res.status == VECTOR_OK) {
|
||||
int val = *(int *)pop_res.value.element;
|
||||
printf("Popped value (should be 5) : %d\n\n", val);
|
||||
}
|
||||
|
||||
// Clear vector
|
||||
vector_result_t clear_res = vector_clear(vector);
|
||||
if (clear_res.status != VECTOR_OK) {
|
||||
printf("Cannot clear vector: %s\n", clear_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
printf("Vector cleared (size should be 0): %zu\n\n", vector_size(vector));
|
||||
}
|
||||
|
||||
// Sort vector in ascending order
|
||||
int values[] = {5, 10, -9, 3, 1, 0, 4};
|
||||
for (size_t idx = 0; idx < 7; idx++) {
|
||||
vector_result_t sort_push_res = vector_push(vector, &values[idx]);
|
||||
if (sort_push_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", sort_push_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Added new elements. Before sort: ");
|
||||
|
||||
sz = vector_size(vector);
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
vector_result_t sort_asc_res = vector_sort(vector, cmp_int_asc);
|
||||
if (sort_asc_res.status != VECTOR_OK) {
|
||||
printf("Cannot sort array: %s\n", sort_asc_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("After sort in ascending order: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// Sort vector in descending order
|
||||
vector_result_t sort_desc_res = vector_sort(vector, cmp_int_desc);
|
||||
if (sort_desc_res.status != VECTOR_OK) {
|
||||
printf("Cannot sort array: %s\n", sort_desc_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("After sort in descending order: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t sort_get_res = vector_get(vector, idx);
|
||||
if (sort_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)sort_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
vector_result_t map_clear_res = vector_clear(vector);
|
||||
if (map_clear_res.status != VECTOR_OK) {
|
||||
printf("Cannot clear vector: %s\n", map_clear_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Map vector elements
|
||||
for (size_t idx = 1; idx <= 5; idx++) {
|
||||
vector_result_t map_push_res = vector_push(vector, &idx);
|
||||
if (map_push_res.status != VECTOR_OK) {
|
||||
printf("Error while adding elements: %s\n", map_push_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
sz = vector_size(vector);
|
||||
|
||||
// Square vector elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25]
|
||||
vector_result_t map_res = vector_map(vector, square, NULL);
|
||||
if (map_res.status != VECTOR_OK) {
|
||||
printf("Error while mapping vector: %s\n", map_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Squared vector: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t map_get_res = vector_get(vector, idx);
|
||||
if (map_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)map_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Filter vector elements: [1, 4, 9, 16, 25] -> [4, 16]
|
||||
vector_result_t filter_res = vector_filter(vector, is_even, NULL);
|
||||
if (filter_res.status != VECTOR_OK) {
|
||||
printf("Error while filtering vector: %s\n", filter_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sz = vector_size(vector);
|
||||
|
||||
printf("Filtered vector: ");
|
||||
for (size_t idx = 0; idx < sz; idx++) {
|
||||
vector_result_t map_get_res = vector_get(vector, idx);
|
||||
if (map_get_res.status != VECTOR_OK) {
|
||||
printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int *val = (int*)map_get_res.value.element;
|
||||
printf("%d ", *val);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Reduce vector elements: [4, 16] -> 20
|
||||
int sum = 0;
|
||||
vector_result_t reduce_res = vector_reduce(vector, &sum, adder, NULL);
|
||||
if (reduce_res.status != VECTOR_OK) {
|
||||
printf("Error while reducing vector: %s\n", reduce_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Sum of vector: %d\n\n", sum);
|
||||
|
||||
// Free vector
|
||||
vector_result_t del_res = vector_destroy(vector);
|
||||
if (del_res.status != VECTOR_OK) {
|
||||
printf("Error while destroying the vector: %s\n", del_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int map_usage(void) {
|
||||
// Create a new map
|
||||
map_result_t res = map_new();
|
||||
if (res.status != MAP_OK) {
|
||||
printf("Error while creating map: %s\n", res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
map_t *map = res.value.map;
|
||||
|
||||
// Add some values
|
||||
const int x = 0xB00B5;
|
||||
const char *y = "Hello";
|
||||
|
||||
map_result_t add_res = map_add(map, "x", (void*)&x);
|
||||
if (add_res.status != MAP_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
add_res = map_add(map, "y", (void*)y);
|
||||
if (add_res.status != MAP_OK) {
|
||||
printf("Error while adding elements: %s\n", add_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Print size and capacity
|
||||
printf("Map size (should be 2): %zu\n", map_size(map));
|
||||
printf("Map capacity (should be > 2): %zu\n\n", map_capacity(map));
|
||||
|
||||
// Retrieve keys
|
||||
map_result_t get_res = map_get(map, "x");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'x': %s\n", get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (const int*)get_res.value.element;
|
||||
printf("Key 'x' contains (should be 'B00B5'): %X\n", *val);
|
||||
}
|
||||
|
||||
get_res = map_get(map, "y");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'y': %s\n", get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
const char *val = (const char*)get_res.value.element;
|
||||
printf("Key 'y' contains (should be 'Hello') : %s\n\n", val);
|
||||
}
|
||||
|
||||
// Update key
|
||||
const int new_x = 0xC0FFEE;
|
||||
map_result_t up_res = map_add(map, "x", (void*)&new_x);
|
||||
|
||||
up_res = map_get(map, "x");
|
||||
if (get_res.status != MAP_OK) {
|
||||
printf("Cannot retrieve map element 'x': %s\n", get_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
const int *val = (const int*)up_res.value.element;
|
||||
printf("Key 'x' (should be updated to 'C0FFEE'): %X\n\n", *val);
|
||||
}
|
||||
|
||||
// Remove an element
|
||||
map_result_t rm_res = map_remove(map, "y");
|
||||
if (rm_res.status != MAP_OK) {
|
||||
printf("Cannot remove map element 'y': %s\n", rm_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
printf("Map element 'y' removed (size should be 1): %zu\n\n", map_size(map));
|
||||
}
|
||||
|
||||
// Clear the map
|
||||
map_result_t clear_res = map_clear(map);
|
||||
if (clear_res.status != MAP_OK) {
|
||||
printf("Cannot clear map: %s\n", clear_res.message);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
printf("Map cleared (size should be 0): %zu\n", map_size(map));
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Delete the map
|
||||
map_result_t del_res = map_destroy(map);
|
||||
if (del_res.status != MAP_OK) {
|
||||
printf("Error while destroying the map: %s\n", del_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bigint_usage(void) {
|
||||
const char *x_origin = "8036732204560262312865077650774313136023641621894661847778962273940232785242208265819059749867858355";
|
||||
const char *y_origin = "7078840479830524979114102683681365071561983635405714511439038016617918064981439736383067887133445937";
|
||||
const size_t x_len = strlen(x_origin);
|
||||
const size_t y_len = strlen(y_origin);
|
||||
const size_t large_x_size = x_len * 100 + 1;
|
||||
const size_t large_y_size = y_len * 100 + 1;
|
||||
|
||||
char *large_x = malloc(large_x_size);
|
||||
char *large_y = malloc(large_y_size);
|
||||
|
||||
if (large_x == NULL || large_y == NULL) {
|
||||
printf("Error while allocating memory for strings\n");
|
||||
free(large_x);
|
||||
free(large_y);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
large_x[0] = '\0';
|
||||
large_y[0] = '\0';
|
||||
|
||||
// Concatenate 100 times
|
||||
for (size_t idx = 0; idx < 100; idx++) {
|
||||
strcat(large_x, x_origin);
|
||||
strcat(large_y, y_origin);
|
||||
}
|
||||
|
||||
// Create two big integers from previous strings
|
||||
bigint_result_t x_res = bigint_from_string(large_x);
|
||||
if (x_res.status != BIGINT_OK) {
|
||||
printf("Error while creating big number: %s\n", x_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_result_t y_res = bigint_from_string(large_y);
|
||||
if (x_res.status != BIGINT_OK) {
|
||||
printf("Error while creating big number: %s\n", x_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *x = x_res.value.number;
|
||||
bigint_t *y = y_res.value.number;
|
||||
|
||||
// Sum two big integers
|
||||
bigint_result_t sum_res = bigint_add(x, y);
|
||||
if (sum_res.status != BIGINT_OK) {
|
||||
printf("Error while summing two big numbers: %s\n", sum_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *sum = sum_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("Sum result = %B\n", sum);
|
||||
|
||||
// Subtract two big integers
|
||||
bigint_result_t diff_res = bigint_sub(x, y);
|
||||
if (diff_res.status != BIGINT_OK) {
|
||||
printf("Error while subtracting two big numbers: %s\n", diff_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *diff = diff_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("difference result = %B\n", diff);
|
||||
|
||||
// Multiply two big integers
|
||||
bigint_result_t prod_res = bigint_prod(x, y);
|
||||
if (prod_res.status != BIGINT_OK) {
|
||||
printf("Error while multiplying two big numbers: %s\n", prod_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *prod = prod_res.value.number;
|
||||
|
||||
// Print result
|
||||
bigint_printf("multiplication result = %B\n", prod);
|
||||
|
||||
bigint_t *a = bigint_from_string(large_x).value.number;
|
||||
bigint_t *b = bigint_from_string(y_origin).value.number;
|
||||
|
||||
// Divide two big integers
|
||||
bigint_result_t div_res = bigint_divmod(a, b);
|
||||
if (div_res.status != BIGINT_OK) {
|
||||
printf("Error while dividing two big numbers: %s\n", div_res.message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bigint_t *quotient = div_res.value.division.quotient;
|
||||
bigint_t *remainder = div_res.value.division.remainder;
|
||||
|
||||
// Print result
|
||||
bigint_printf(
|
||||
"division result = %B\
|
||||
\nmod result = %B\n",
|
||||
quotient, remainder);
|
||||
|
||||
// Destroy big numbers and strings
|
||||
bigint_destroy(x); bigint_destroy(y);
|
||||
bigint_destroy(a); bigint_destroy(b);
|
||||
bigint_destroy(sum); bigint_destroy(diff);
|
||||
bigint_destroy(prod); bigint_destroy(quotient); bigint_destroy(remainder);
|
||||
free(large_x); free(large_y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user