aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClecio Jung2023-03-19 09:15:26 -0300
committerClecio Jung2023-03-19 09:15:26 -0300
commit852e58a3e6d82362b66ace7f35056ba6693795c3 (patch)
tree55793c979586a3d38e6f5e085c6d7c9ebd899d8d
parent0113ec2c4358a5a6f4ad473372a63d2aa3c249c0 (diff)
downloadlibini-852e58a3e6d82362b66ace7f35056ba6693795c3.tar.xz
libini-852e58a3e6d82362b66ace7f35056ba6693795c3.tar.zst
Custom string allocator
Introduced a function to convert properties to unsigned long And also improved the documentation of the library
-rwxr-xr-xMakefile5
-rw-r--r--examples/incorrect.ini10
-rw-r--r--ini_file.c116
-rw-r--r--ini_file.h53
4 files changed, 168 insertions, 16 deletions
diff --git a/Makefile b/Makefile
index 134d617..fdb13b7 100755
--- a/Makefile
+++ b/Makefile
@@ -5,13 +5,14 @@
# Name of the executables to be generated
EXEC := examples/ini_file_read \
examples/ini_file_search \
- examples/ini_file_create
+ examples/ini_file_create
# Library files
LIB_FILES := ini_file.c ini_file.h
# Flags for compiler
-CFLAGS := -W -Wall -Wextra -pedantic -Wconversion -Werror -flto -std=c89 -O2
+CFLAGS := -W -Wall -Wextra -pedantic -Wconversion \
+ -Werror -flto -std=c89 -O2
# ----------------------------------------
# Compilation and linking rules
diff --git a/examples/incorrect.ini b/examples/incorrect.ini
index 42dd5f2..5f8e285 100644
--- a/examples/incorrect.ini
+++ b/examples/incorrect.ini
@@ -19,8 +19,16 @@ correct = al bel gam
test2 = 65
a = ; 15
+= 2
+
+[ ]
+
alpha = beta
[section 3
-[section 4] dfg \ No newline at end of file
+[section 4] dfg
+
+[numbers]
+integer = 25
+real = 2.8e-3 \ No newline at end of file
diff --git a/ini_file.c b/ini_file.c
index 8d7126c..d512f1d 100644
--- a/ini_file.c
+++ b/ini_file.c
@@ -15,8 +15,18 @@
/* Most systems do not allow for a line greather than 4 kbytes */
#define MAX_LINE_SIZE 4096
-#define INITIAL_NUMBER_OF_SECTIONS 10
-#define INITIAL_NUMBER_OF_PROPERTIES 10
+#define INITIAL_NUMBER_OF_SECTIONS 32
+#define INITIAL_NUMBER_OF_PROPERTIES 32
+
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+static void string_buffer_free(struct String_Buffer *buffer) {
+ if (buffer == NULL) {
+ return;
+ }
+ string_buffer_free(buffer->next);
+ free(buffer);
+}
+#endif
size_t get_file_size(FILE *const file) {
long file_size;
@@ -40,7 +50,7 @@ char *get_content_from_file(const char *const filename) {
buffer[file_size] = '\0';
if (result != file_size) {
/* Reading file error, free dinamically allocated memory */
- free((void *)buffer);
+ free(buffer);
buffer = NULL;
}
}
@@ -52,10 +62,22 @@ char *get_content_from_file(const char *const filename) {
struct Ini_File *ini_file_new(void) {
struct Ini_File *ini_file = malloc(sizeof(struct Ini_File));
if (ini_file != NULL) {
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ ini_file->strings = malloc(sizeof(struct String_Buffer));
+ if (ini_file->strings == NULL) {
+ free(ini_file);
+ return NULL;
+ }
+ ini_file->strings->index = 0;
+ ini_file->strings->next = NULL;
+#endif
ini_file->sections_size = 0;
ini_file->sections_capacity = INITIAL_NUMBER_OF_SECTIONS;
ini_file->sections = malloc(ini_file->sections_capacity * sizeof(struct Ini_Section));
if (ini_file->sections == NULL) {
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ free(ini_file->strings);
+#endif
free(ini_file);
return NULL;
}
@@ -69,16 +91,22 @@ struct Ini_File *ini_file_new(void) {
}
void ini_file_free(struct Ini_File *const ini_file) {
- size_t i, j;
+ size_t i;
if (ini_file == NULL) {
return;
}
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ string_buffer_free(ini_file->strings);
+#endif
for (i = 0; i < ini_file->sections_size; i++) {
- free((void *)ini_file->sections[i].name);
+#ifndef USE_CUSTOM_STRING_ALLOCATOR
+ size_t j;
+ free(ini_file->sections[i].name);
for (j = 0; j < ini_file->sections[i].properties_size; j++) {
- free((void *)ini_file->sections[i].properties[j].key);
- free((void *)ini_file->sections[i].properties[j].value);
+ free(ini_file->sections[i].properties[j].key);
+ free(ini_file->sections[i].properties[j].value);
}
+#endif
free(ini_file->sections[i].properties);
ini_file->sections[i].properties_capacity = 0;
ini_file->sections[i].properties_size = 0;
@@ -86,7 +114,7 @@ void ini_file_free(struct Ini_File *const ini_file) {
free(ini_file->sections);
ini_file->sections_capacity = 0;
ini_file->sections_size = 0;
- free((void *)ini_file);
+ free(ini_file);
}
void ini_section_print_to(const struct Ini_Section *const ini_section, FILE *const sink) {
@@ -132,6 +160,7 @@ char *ini_file_error_to_string(const enum Ini_File_Errors error) {
"Didn't found the requested section",
"Didn't found the requested property",
"The requested property is not a valid integer number",
+ "The requested property is not a valid unsigned number",
"The requested property is not a valid floating point number",
};
#ifdef _Static_assert
@@ -153,7 +182,36 @@ static void advance_string_until(char **const str, const char *const chars) {
}
}
-/* TODO: We can have a large buffer of memory and store each new string in there */
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+static char *copy_sized_string(struct String_Buffer *strings, const char *const sized_str, const size_t len) {
+ char *str;
+ if (strings == NULL) {
+ return NULL;
+ }
+ while ((strings->index < 0) && (strings->next != NULL)) {
+ strings = strings->next;
+ }
+ if (strings->index < 0) {
+ return NULL;
+ }
+ if (((size_t)strings->index + len + 1) > sizeof(strings->buffer)) {
+ strings->index = -1;
+ strings->next = malloc(sizeof(struct String_Buffer));
+ if (strings->next == NULL) {
+ return NULL;
+ }
+ strings = strings->next;
+ strings->index = 0;
+ strings->next = NULL;
+ }
+ /* Allocates the memory to store the string */
+ str = &strings->buffer[strings->index];
+ strings->index += (int)len + 1;
+ strncpy(str, sized_str, len);
+ str[len] = '\0';
+ return str;
+}
+#else
static char *copy_sized_string(const char *const sized_str, const size_t len) {
char *str = malloc(len + 1);
if (str != NULL) {
@@ -162,6 +220,7 @@ static char *copy_sized_string(const char *const sized_str, const size_t len) {
}
return str;
}
+#endif
static int ini_file_parse_handle_error(Ini_File_Error_Callback callback, const char *const filename, const size_t line_number, const char *const line, const enum Ini_File_Errors error) {
/* This function is called when we found an error in the parsing.
@@ -284,7 +343,11 @@ enum Ini_File_Errors ini_file_add_section_sized(struct Ini_File *const ini_file,
ini_file->sections_capacity = new_cap;
}
section = &ini_file->sections[ini_file->sections_size];
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ section->name = copy_sized_string(ini_file->strings, name, name_len);
+#else
section->name = copy_sized_string(name, name_len);
+#endif
if (section->name == NULL) {
return ini_allocation;
}
@@ -333,11 +396,19 @@ enum Ini_File_Errors ini_file_add_property_sized(struct Ini_File *const ini_file
section->properties_capacity = new_cap;
}
property = &section->properties[section->properties_size];
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ property->key = copy_sized_string(ini_file->strings, key, key_len);
+#else
property->key = copy_sized_string(key, key_len);
+#endif
if (property->key == NULL) {
return ini_allocation;
}
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ property->value = copy_sized_string(ini_file->strings, value, value_len);
+#else
property->value = copy_sized_string(value, value_len);
+#endif
if (property->value == NULL) {
free(property->key);
return ini_allocation;
@@ -414,6 +485,7 @@ enum Ini_File_Errors ini_file_find_property(struct Ini_File *const ini_file, con
enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, const char *const section, const char *const key, long *integer) {
char *value, *end;
+ long i_value;
enum Ini_File_Errors error;
if (integer == NULL) {
return ini_invalid_parameters;
@@ -422,15 +494,36 @@ enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, cons
if (error != ini_no_error) {
return error;
}
- *integer = strtol(value, &end, 10);
+ i_value = strtol(value, &end, 10);
if (*end != '\0') {
return ini_not_integer;
}
+ *integer = i_value;
+ return ini_no_error;
+}
+
+enum Ini_File_Errors ini_file_find_unsigned(struct Ini_File *const ini_file, const char *const section, const char *const key, unsigned long *uint) {
+ char *value, *end;
+ unsigned long ui_value;
+ enum Ini_File_Errors error;
+ if (uint == NULL) {
+ return ini_invalid_parameters;
+ }
+ error = ini_file_find_property(ini_file, section, key, &value);
+ if (error != ini_no_error) {
+ return error;
+ }
+ ui_value = strtoul(value, &end, 10);
+ if (*end != '\0') {
+ return ini_not_unsigned;
+ }
+ *uint = ui_value;
return ini_no_error;
}
enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const char *const section, const char *const key, double *real) {
char *value, *end;
+ double d_value;
enum Ini_File_Errors error;
if (real == NULL) {
return ini_invalid_parameters;
@@ -439,10 +532,11 @@ enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const
if (error != ini_no_error) {
return error;
}
- *real = strtod(value, &end);
+ d_value = strtod(value, &end);
if (*end != '\0') {
return ini_not_float;
}
+ *real = d_value;
return ini_no_error;
}
diff --git a/ini_file.h b/ini_file.h
index 750f218..0a35d6b 100644
--- a/ini_file.h
+++ b/ini_file.h
@@ -29,6 +29,46 @@
#ifndef __INI_FILE
#define __INI_FILE
+/* Summary:
+ * INI files are not standardized, meaning that different implementations may have
+ * differences. This implementation uses the # and ; characters to define
+ * single-line comments. As a result, these characters cannot be used when defining
+ * section names, keys, and values. Special characters such as =, #, and ; are not
+ * allowed in key names. However, spaces and the = character can be used when
+ * defining values, as long as the characters # and ; are not used. Section names
+ * can have spaces, but cannot include the characters ], #, and ;. Nested sections
+ * are not currently implemented, and duplicate names are allowed (for now). Quoted
+ * strings and escaped characters are not supported in this implementation.
+ * If a key-value pair appears in the INI file before the first section is declared,
+ * it will be treated as belonging to the "global" section. This allows properties
+ * to be defined outside of any specific section and still be easily accessible in
+ * the program.
+ */
+
+
+/* This is a implementation of a custom string allocator to store the strings found
+ * inside the INI. If you don't want to use this approach, just comment the
+ * definition of the macro USE_CUSTOM_STRING_ALLOCATOR bellow. In this case, all the
+ * string allocations will be performed by expensive malloc calls.
+ *
+ * This custom string allocator consists of a linked-list of large buffers. It is
+ * designed to be memory-efficient and avoid memory fragmentation. Whenever a new
+ * string is found in the INI file, it is copied to an available buffer in the
+ * linked list, allocating new buffers as needed. This approach reduces the number
+ * of malloc and free calls, which can be expensive in terms of performance.
+ */
+#define USE_CUSTOM_STRING_ALLOCATOR
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+struct String_Buffer {
+ char buffer[4096];
+ struct String_Buffer *next;
+ /* This index points to the next valid location to store the string.
+ * If the buffer is full, the index will equal -1. In this case we shall
+ * use one of the next buffers. */
+ int index;
+};
+#endif
+
struct Key_Value_Pair {
char *key;
char *value;
@@ -36,12 +76,17 @@ struct Key_Value_Pair {
struct Ini_Section {
char *name;
+ /* The properties of the section are stored in a dynamic array */
size_t properties_size;
size_t properties_capacity;
struct Key_Value_Pair *properties;
};
struct Ini_File {
+#ifdef USE_CUSTOM_STRING_ALLOCATOR
+ struct String_Buffer *strings;
+#endif
+ /* The sections of the ini file are stored in a dynamic array */
size_t sections_size;
size_t sections_capacity;
struct Ini_Section *sections;
@@ -60,6 +105,7 @@ enum Ini_File_Errors {
ini_no_such_section,
ini_no_such_property,
ini_not_integer,
+ ini_not_unsigned,
ini_not_float,
NUMBER_OF_INI_FILE_ERRORS
@@ -90,11 +136,14 @@ enum Ini_File_Errors ini_file_add_property_sized(struct Ini_File *const ini_file
enum Ini_File_Errors ini_file_add_property(struct Ini_File *const ini_file, const char *const key, const char *const value);
enum Ini_File_Errors ini_file_save(const struct Ini_File *const ini_file, const char *const filename);
-/* These functions use sequential search algorithm to find the requested section and properties */
-/* These functions returns ini_no_error = 0 if everything worked correctly */
+/* These functions use sequential search algorithm to find the requested section and properties.
+ * They return ini_no_error = 0 if everything worked correctly.
+ * The found value will be stores at the memory address provided by the caller.
+ * If no value is found, the function will not modify the value stored at the address provided. */
enum Ini_File_Errors ini_file_find_section(struct Ini_File *const ini_file, const char *const section, struct Ini_Section **ini_section);
enum Ini_File_Errors ini_file_find_property(struct Ini_File *const ini_file, const char *const section, const char *const key, char **value);
enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, const char *const section, const char *const key, long *integer);
+enum Ini_File_Errors ini_file_find_unsigned(struct Ini_File *const ini_file, const char *const section, const char *const key, unsigned long *uint);
enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const char *const section, const char *const key, double *real);
#endif /* __INI_FILE */