diff options
| author | Clecio Jung | 2023-03-19 09:15:26 -0300 |
|---|---|---|
| committer | Clecio Jung | 2023-03-19 09:15:26 -0300 |
| commit | 852e58a3e6d82362b66ace7f35056ba6693795c3 (patch) | |
| tree | 55793c979586a3d38e6f5e085c6d7c9ebd899d8d | |
| parent | 0113ec2c4358a5a6f4ad473372a63d2aa3c249c0 (diff) | |
| download | libini-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-x | Makefile | 5 | ||||
| -rw-r--r-- | examples/incorrect.ini | 10 | ||||
| -rw-r--r-- | ini_file.c | 116 | ||||
| -rw-r--r-- | ini_file.h | 53 |
4 files changed, 168 insertions, 16 deletions
@@ -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 @@ -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 = §ion->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; } @@ -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 */ |
