From 9916e720fc6d7ff73fd1bd4c52ed1c1eb2fda2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 25 Jul 2026 12:23:58 -0700 Subject: [PATCH] Added scanner. (#410) --- 3rdparty/ini/README.md | 1 - 3rdparty/ini/ini.h | 1067 --------------------------------- 3rdparty/ini/ini.md | 333 ---------- include/bx/inline/scanner.inl | 228 +++++++ include/bx/inline/string.inl | 149 +++-- include/bx/scanner.h | 341 +++++++++++ include/bx/settings.h | 4 +- include/bx/string.h | 65 +- scripts/bx.lua | 1 + src/amalgamated.cpp | 1 + src/debug.cpp | 29 +- src/filepath.cpp | 135 ++--- src/scanner.cpp | 167 ++++++ src/settings.cpp | 655 ++++++++++++++------ src/string.cpp | 93 +-- src/url.cpp | 149 +++-- tests/filepath_test.cpp | 28 + tests/scanner_test.cpp | 351 +++++++++++ tests/settings_test.cpp | 177 ++++++ tests/string_test.cpp | 49 ++ tests/url_test.cpp | 33 + 21 files changed, 2157 insertions(+), 1899 deletions(-) delete mode 100644 3rdparty/ini/README.md delete mode 100644 3rdparty/ini/ini.h delete mode 100644 3rdparty/ini/ini.md create mode 100644 include/bx/inline/scanner.inl create mode 100644 include/bx/scanner.h create mode 100644 src/scanner.cpp create mode 100644 tests/scanner_test.cpp diff --git a/3rdparty/ini/README.md b/3rdparty/ini/README.md deleted file mode 100644 index bc4d7c7..0000000 --- a/3rdparty/ini/README.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/mattiasgustavsson/libs/ diff --git a/3rdparty/ini/ini.h b/3rdparty/ini/ini.h deleted file mode 100644 index ba19d79..0000000 --- a/3rdparty/ini/ini.h +++ /dev/null @@ -1,1067 +0,0 @@ -/* ------------------------------------------------------------------------------- - Licensing information can be found at the end of the file. ------------------------------------------------------------------------------- - -ini.h - v1.2 - Simple ini-file reader for C/C++. - -Do this: - #define INI_IMPLEMENTATION -before you include this file in *one* C/C++ file to create the implementation. -*/ - -#ifndef ini_h -#define ini_h - -#define INI_GLOBAL_SECTION ( 0 ) -#define INI_NOT_FOUND ( -1 ) - -typedef struct ini_t ini_t; - -ini_t* ini_create( void* memctx ); -ini_t* ini_load( char const* data, unsigned int len, void* memctx ); - -int ini_save( ini_t const* ini, char* data, int size ); -void ini_destroy( ini_t* ini ); - -int ini_section_count( ini_t const* ini ); -char const* ini_section_name( ini_t const* ini, int section ); - -int ini_property_count( ini_t const* ini, int section ); -char const* ini_property_name( ini_t const* ini, int section, int property ); -char const* ini_property_value( ini_t const* ini, int section, int property ); - -int ini_find_section( ini_t const* ini, char const* name, int name_length ); -int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ); - -int ini_section_add( ini_t* ini, char const* name, int length ); -void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ); -void ini_section_remove( ini_t* ini, int section ); -void ini_property_remove( ini_t* ini, int section, int property ); - -void ini_section_name_set( ini_t* ini, int section, char const* name, int length ); -void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ); -void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ); - -#endif /* ini_h */ - - -/** - -ini.h -===== - -Simple ini-file reader for C/C++. - - -Examples --------- - -#### Loading an ini file and retrieving values - - #define INI_IMPLEMENTATION - #include "ini.h" - - #include - #include - - int main() - { - FILE* fp = fopen( "test.ini", "r" ); - fseek( fp, 0, SEEK_END ); - int size = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - char* data = (char*) malloc( size + 1 ); - fread( data, 1, size, fp ); - data[ size ] = '\0'; - fclose( fp ); - - ini_t* ini = ini_load( data, NULL ); - free( data ); - int second_index = ini_find_property( ini, INI_GLOBAL_SECTION, "SecondSetting", 0 ); - char const* second = ini_property_value( ini, INI_GLOBAL_SECTION, second_index ); - printf( "%s=%s\n", "SecondSetting", second ); - int section = ini_find_section( ini, "MySection", 0 ); - int third_index = ini_find_property( ini, section, "ThirdSetting", 0 ); - char const* third = ini_property_value( ini, section, third_index ); - printf( "%s=%s\n", "ThirdSetting", third ); - ini_destroy( ini ); - - return 0; - } - ------------------------------------------------------------------------------------------------ - -#### Creating a new ini file - - #define INI_IMPLEMENTATION - #include "ini.h" - - #include - #include - - int main() - { - ini_t* ini = ini_create( NULL ); - ini_property_add( ini, INI_GLOBAL_SECTION, "FirstSetting", 0, "Test", 0 ); - ini_property_add( ini, INI_GLOBAL_SECTION, "SecondSetting", 0, "2", 0 ); - int section = ini_section_add( ini, "MySection", 0 ); - ini_property_add( ini, section, "ThirdSetting", 0, "Three", 0 ); - - int size = ini_save( ini, NULL, 0 ); // Find the size needed - char* data = (char*) malloc( size ); - size = ini_save( ini, data, size ); // Actually save the file - ini_destroy( ini ); - - FILE* fp = fopen( "test.ini", "w" ); - fwrite( data, 1, size - 1, fp ); - fclose( fp ); - free( data ); - - return 0; - } - - - -API Documentation ------------------ - -ini.h is a small library for reading classic .ini files. It is a single-header library, and does not need any .lib files -or other binaries, or any build scripts. To use it, you just include ini.h to get the API declarations. To get the -definitions, you must include ini.h from *one* single C or C++ file, and #define the symbol `INI_IMPLEMENTATION` before -you do. - - -### Customization - -There are a few different things in ini.h which are configurable by #defines. The customizations only affect the -implementation, so will only need to be defined in the file where you have the #define INI_IMPLEMENTATION. - -Note that if all customizations are utilized, ini.h will include no external files whatsoever, which might be useful -if you need full control over what code is being built. - - -#### Custom memory allocators - -To store the internal data structures, ini.h needs to do dynamic allocation by calling `malloc`. Programs might want to -keep track of allocations done, or use custom defined pools to allocate memory from. ini.h allows for specifying custom -memory allocation functions for `malloc` and `free`. -This is done with the following code: - - #define INI_IMPLEMENTATION - #define INI_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) ) - #define INI_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) ) - #include "ini.h" - -where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter -is an optional parameter of type `void*`. When `ini_create` or `ini_load` is called, you can pass in a `memctx` -parameter, which can be a pointer to anything you like, and which will be passed through as the `ctx` parameter to every -`INI_MALLOC`/`INI_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your tracking -data as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the -right type, and access the tracking data. - -If no custom allocator is defined, ini.h will default to `malloc` and `free` from the C runtime library. - - -#### Custom C runtime function - -The library makes use of three additional functions from the C runtime library, and for full flexibility, it allows you -to substitute them for your own. Here's an example: - - #define INI_IMPLEMENTATION - #define INI_MEMCPY( dst, src, cnt ) ( my_memcpy_func( dst, src, cnt ) ) - #define INI_STRLEN( s ) ( my_strlen_func( s ) ) - #define INI_STRNICMP( s1, s2, cnt ) ( my_strnicmp_func( s1, s2, cnt ) ) - #include "ini.h" - -If no custom function is defined, ini.h will default to the C runtime library equivalent. - - -ini_create ----------- - - ini_t* ini_create( void* memctx ) - -Instantiates a new, empty ini structure, which can be manipulated with other API calls, to fill it with data. To save it -out to an ini-file string, use `ini_save`. When no longer needed, it can be destroyed by calling `ini_destroy`. -`memctx` is a pointer to user defined data which will be passed through to the custom INI_MALLOC/INI_FREE calls. It can -be NULL if no user defined data is needed. - - -ini_load --------- - - ini_t* ini_load( char const* data, void* memctx ) - -Parse the zero-terminated string `data` containing an ini-file, and create a new ini_t instance containing the data. -The instance can be manipulated with other API calls to enumerate sections/properties and retrieve values. When no -longer needed, it can be destroyed by calling `ini_destroy`. `memctx` is a pointer to user defined data which will be -passed through to the custom INI_MALLOC/INI_FREE calls. It can be NULL if no user defined data is needed. - - -ini_save --------- - - int ini_save( ini_t const* ini, char* data, int size ) - -Saves an ini structure as a zero-terminated ini-file string, into the specified buffer. Returns the number of bytes -written, including the zero terminator. If `data` is NULL, nothing is written, but `ini_save` still returns the number -of bytes it would have written. If the size of `data`, as specified in the `size` parameter, is smaller than that -required, only part of the ini-file string will be written. `ini_save` still returns the number of bytes it would have -written had the buffer been large enough. - - -ini_destroy ------------ - - void ini_destroy( ini_t* ini ) - -Destroy an `ini_t` instance created by calling `ini_load` or `ini_create`, releasing the memory allocated by it. No -further API calls are valid on an `ini_t` instance after calling `ini_destroy` on it. - - -ini_section_count ------------------ - - int ini_section_count( ini_t const* ini ) - -Returns the number of sections in an ini file. There's at least one section in an ini file (the global section), but -there can be many more, each specified in the file by the section name wrapped in square brackets [ ]. - - -ini_section_name ----------------- - - char const* ini_section_name( ini_t const* ini, int section ) - -Returns the name of the section with the specified index. `section` must be non-negative and less than the value -returned by `ini_section_count`, or `ini_section_name` will return NULL. The defined constant `INI_GLOBAL_SECTION` can -be used to indicate the global section. - - -ini_property_count ------------------- - - int ini_property_count( ini_t const* ini, int section ) - -Returns the number of properties belonging to the section with the specified index. `section` must be non-negative and -less than the value returned by `ini_section_count`, or `ini_section_name` will return 0. The defined constant -`INI_GLOBAL_SECTION` can be used to indicate the global section. Properties are declared in the ini-file on he format -`name=value`. - - -ini_property_name ------------------ - - char const* ini_property_name( ini_t const* ini, int section, int property ) - -Returns the name of the property with the specified index `property` in the section with the specified index `section`. -`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be -non-negative and less than the value returned by `ini_property_count`, or `ini_property_name` will return NULL. The -defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_property_value ------------------- - - char const* ini_property_value( ini_t const* ini, int section, int property ) - -Returns the value of the property with the specified index `property` in the section with the specified index `section`. -`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be -non-negative and less than the value returned by `ini_property_count`, or `ini_property_value` will return NULL. The -defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_find_section ----------------- - - int ini_find_section( ini_t const* ini, char const* name, int name_length ) - -Finds the section with the specified name, and returns its index. `name_length` specifies the number of characters in -`name`, which does not have to be zero-terminated. If `name_length` is zero, the length is determined automatically, but -in this case `name` has to be zero-terminated. If no section with the specified name could be found, the value -`INI_NOT_FOUND` is returned. - - -ini_find_property ------------------ - - int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ) - -Finds the property with the specified name, within the section with the specified index, and returns the index of the -property. `name_length` specifies the number of characters in `name`, which does not have to be zero-terminated. If -`name_length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. If no -property with the specified name could be found within the specified section, the value `INI_NOT_FOUND` is returned. -`section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_find_property` will -return `INI_NOT_FOUND`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_section_add ---------------- - - int ini_section_add( ini_t* ini, char const* name, int length ) - -Adds a section with the specified name, and returns the index it was added at. There is no check done to see if a -section with the specified name already exists - multiple sections of the same name are allowed. `length` specifies the -number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined -automatically, but in this case `name` has to be zero-terminated. - - -ini_property_add ----------------- - - void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ) - -Adds a property with the specified name and value to the specified section, and returns the index it was added at. There -is no check done to see if a property with the specified name already exists - multiple properties of the same name are -allowed. `name_length` and `value_length` specifies the number of characters in `name` and `value`, which does not have -to be zero-terminated. If `name_length` or `value_length` is zero, the length is determined automatically, but in this -case `name`/`value` has to be zero-terminated. `section` must be non-negative and less than the value returned by -`ini_section_count`, or the property will not be added. The defined constant `INI_GLOBAL_SECTION` can be used to -indicate the global section. - - -ini_section_remove ------------------- - - void ini_section_remove( ini_t* ini, int section ) - -Removes the section with the specified index, and all properties within it. `section` must be non-negative and less than -the value returned by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global -section. Note that removing a section will shuffle section indices, so that section indices you may have stored will no -longer indicate the same section as it did before the remove. Use the find functions to update your indices. - - -ini_property_remove -------------------- - - void ini_property_remove( ini_t* ini, int section, int property ) - -Removes the property with the specified index from the specified section. `section` must be non-negative and less than -the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by -`ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Note that -removing a property will shuffle property indices within the specified section, so that property indices you may have -stored will no longer indicate the same property as it did before the remove. Use the find functions to update your -indices. - - -ini_section_name_set --------------------- - - void ini_section_name_set( ini_t* ini, int section, char const* name, int length ) - -Change the name of the section with the specified index. `section` must be non-negative and less than the value returned -by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` -specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length -is determined automatically, but in this case `name` has to be zero-terminated. - - -ini_property_name_set ---------------------- - - void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ) - -Change the name of the property with the specified index in the specified section. `section` must be non-negative and -less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value -returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. -`length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, -the length is determined automatically, but in this case `name` has to be zero-terminated. - - -ini_property_value_set ----------------------- - - void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ) - -Change the value of the property with the specified index in the specified section. `section` must be non-negative and -less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value -returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. -`length` specifies the number of characters in `value`, which does not have to be zero-terminated. If `length` is zero, -the length is determined automatically, but in this case `value` has to be zero-terminated. - -*/ - - -/* ----------------------- - IMPLEMENTATION ----------------------- -*/ - -#ifdef INI_IMPLEMENTATION -#undef INI_IMPLEMENTATION - -#define INITIAL_CAPACITY ( 256 ) - -#undef _CRT_NONSTDC_NO_DEPRECATE -#define _CRT_NONSTDC_NO_DEPRECATE -#undef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#include - -#ifndef INI_MALLOC - #include - #define INI_MALLOC( ctx, size ) ( malloc( size ) ) - #define INI_FREE( ctx, ptr ) ( free( ptr ) ) -#endif - -#ifndef INI_MEMCPY - #include - #define INI_MEMCPY( dst, src, cnt ) ( memcpy( dst, src, cnt ) ) -#endif - -#ifndef INI_STRLEN - #include - #define INI_STRLEN( s ) ( strlen( s ) ) -#endif - -#ifndef INI_STRNICMP - #ifdef _WIN32 - #include - #define INI_STRNICMP( s1, s2, cnt ) ( strnicmp( s1, s2, cnt ) ) - #else - #include - #define INI_STRNICMP( s1, s2, cnt ) ( strncasecmp( s1, s2, cnt ) ) - #endif -#endif - - -struct ini_internal_section_t - { - char name[ 32 ]; - char* name_large; - }; - - -struct ini_internal_property_t - { - int section; - char name[ 32 ]; - char* name_large; - char value[ 64 ]; - char* value_large; - }; - - -struct ini_t - { - struct ini_internal_section_t* sections; - int section_capacity; - int section_count; - - struct ini_internal_property_t* properties; - int property_capacity; - int property_count; - - void* memctx; - }; - - -static int ini_internal_property_index( ini_t const* ini, int section, int property ) - { - int i; - int p; - - if( ini && section >= 0 && section < ini->section_count ) - { - p = 0; - for( i = 0; i < ini->property_count; ++i ) - { - if( ini->properties[ i ].section == section ) - { - if( p == property ) return i; - ++p; - } - } - } - - return INI_NOT_FOUND; - } - - -ini_t* ini_create( void* memctx ) - { - ini_t* ini; - - ini = (ini_t*) INI_MALLOC( memctx, sizeof( ini_t ) ); - ini->memctx = memctx; - ini->sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->sections[ 0 ] ) ); - ini->section_capacity = INITIAL_CAPACITY; - ini->section_count = 1; /* global section */ - ini->sections[ 0 ].name[ 0 ] = '\0'; - ini->sections[ 0 ].name_large = 0; - ini->properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->properties[ 0 ] ) ); - ini->property_capacity = INITIAL_CAPACITY; - ini->property_count = 0; - return ini; - } - - -ini_t* ini_load( char const* data, unsigned int len, void* memctx ) - { - ini_t* ini; - char const* ptr; - int s; - char const* start; - char const* start2; - int l; - char const* end; - - ini = ini_create( memctx ); - - ptr = data; - end = ptr + len; - if( ptr ) - { - s = 0; - while( ptr < end && *ptr ) - { - /* trim leading whitespace */ - while( ptr < end && *ptr && *ptr <=' ' ) - ++ptr; - - /* done? */ - if( ptr == end || !*ptr ) break; - - /* comment */ - else if( *ptr == ';' ) - { - while( ptr < end && *ptr && *ptr !='\n' ) - ++ptr; - } - /* section */ - else if( *ptr == '[' ) - { - ++ptr; - start = ptr; - while( ptr < end && *ptr && *ptr !=']' && *ptr != '\n' ) - ++ptr; - - if( *ptr == ']' ) - { - s = ini_section_add( ini, start, (int)( ptr - start) ); - ++ptr; - } - } - /* property */ - else - { - start = ptr; - while( ptr < end && *ptr && *ptr !='=' && *ptr != '\n' ) - ++ptr; - - if( *ptr == '=' ) - { - l = (int)( ptr - start); - ++ptr; - while( ptr < end && *ptr && *ptr <= ' ' && *ptr != '\n' ) - ptr++; - start2 = ptr; - while( ptr < end && *ptr && *ptr != '\n' ) - ++ptr; - while( ptr >= start2 && *(--ptr) <= ' ' ) - (void)ptr; - ptr++; - if( ptr == start2 ) - ini_property_add( ini, s, start, l, "", 1 ); - else - ini_property_add( ini, s, start, l, start2, (int)( ptr - start2 ) ); - } - } - } - } - - return ini; - } - - -int ini_save( ini_t const* ini, char* data, int size ) - { - int s; - int p; - int i; - int l; - char* n; - int pos; - - if( ini ) - { - pos = 0; - for( s = 0; s < ini->section_count; ++s ) - { - n = ini->sections[ s ].name_large ? ini->sections[ s ].name_large : ini->sections[ s ].name; - l = (int) INI_STRLEN( n ); - if( l > 0 ) - { - if( data && pos < size ) data[ pos ] = '['; - ++pos; - for( i = 0; i < l; ++i ) - { - if( data && pos < size ) data[ pos ] = n[ i ]; - ++pos; - } - if( data && pos < size ) data[ pos ] = ']'; - ++pos; - if( data && pos < size ) data[ pos ] = '\n'; - ++pos; - } - - for( p = 0; p < ini->property_count; ++p ) - { - if( ini->properties[ p ].section == s ) - { - n = ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name; - l = (int) INI_STRLEN( n ); - for( i = 0; i < l; ++i ) - { - if( data && pos < size ) data[ pos ] = n[ i ]; - ++pos; - } - if( data && pos < size ) data[ pos ] = '='; - ++pos; - n = ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value; - l = (int) INI_STRLEN( n ); - for( i = 0; i < l; ++i ) - { - if( data && pos < size ) data[ pos ] = n[ i ]; - ++pos; - } - if( data && pos < size ) data[ pos ] = '\n'; - ++pos; - } - } - - if( pos > 0 ) - { - if( data && pos < size ) data[ pos ] = '\n'; - ++pos; - } - } - - if( data && pos < size ) data[ pos ] = '\0'; - ++pos; - - return pos; - } - - return 0; - } - - -void ini_destroy( ini_t* ini ) - { - int i; - - if( ini ) - { - for( i = 0; i < ini->property_count; ++i ) - { - if( ini->properties[ i ].value_large ) INI_FREE( ini->memctx, ini->properties[ i ].value_large ); - if( ini->properties[ i ].name_large ) INI_FREE( ini->memctx, ini->properties[ i ].name_large ); - } - for( i = 0; i < ini->section_count; ++i ) - if( ini->sections[ i ].name_large ) INI_FREE( ini->memctx, ini->sections[ i ].name_large ); - INI_FREE( ini->memctx, ini->properties ); - INI_FREE( ini->memctx, ini->sections ); - INI_FREE( ini->memctx, ini ); - } - } - - -int ini_section_count( ini_t const* ini ) - { - if( ini ) return ini->section_count; - return 0; - } - - -char const* ini_section_name( ini_t const* ini, int section ) - { - if( ini && section >= 0 && section < ini->section_count ) - return ini->sections[ section ].name_large ? ini->sections[ section ].name_large : ini->sections[ section ].name; - - return NULL; - } - - -int ini_property_count( ini_t const* ini, int section ) - { - int i; - int count; - - if( ini ) - { - count = 0; - for( i = 0; i < ini->property_count; ++i ) - { - if( ini->properties[ i ].section == section ) ++count; - } - return count; - } - - return 0; - } - - -char const* ini_property_name( ini_t const* ini, int section, int property ) - { - int p; - - if( ini && section >= 0 && section < ini->section_count ) - { - p = ini_internal_property_index( ini, section, property ); - if( p != INI_NOT_FOUND ) - return ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name; - } - - return NULL; - } - - -char const* ini_property_value( ini_t const* ini, int section, int property ) - { - int p; - - if( ini && section >= 0 && section < ini->section_count ) - { - p = ini_internal_property_index( ini, section, property ); - if( p != INI_NOT_FOUND ) - return ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value; - } - - return NULL; - } - - -int ini_find_section( ini_t const* ini, char const* name, int name_length ) - { - int i; - - if( ini && name ) - { - if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); - for( i = 0; i < ini->section_count; ++i ) - { - char const* const other = - ini->sections[ i ].name_large ? ini->sections[ i ].name_large : ini->sections[ i ].name; - if( INI_STRLEN( other ) == name_length && INI_STRNICMP( name, other, (size_t)name_length ) == 0 ) - return i; - } - } - - return INI_NOT_FOUND; - } - - -int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ) - { - int i; - int c; - - if( ini && name && section >= 0 && section < ini->section_count) - { - if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); - c = 0; - for( i = 0; i < ini->property_count; ++i ) - { - if( ini->properties[ i ].section == section ) - { - char const* const other = - ini->properties[ i ].name_large ? ini->properties[ i ].name_large : ini->properties[ i ].name; - if( INI_STRLEN( other ) == name_length && INI_STRNICMP( name, other, (size_t) name_length ) == 0 ) - return c; - ++c; - } - } - } - - return INI_NOT_FOUND; - } - - -int ini_section_add( ini_t* ini, char const* name, int length ) - { - struct ini_internal_section_t* new_sections; - - if( ini && name ) - { - if( length <= 0 ) length = (int) INI_STRLEN( name ); - if( ini->section_count >= ini->section_capacity ) - { - ini->section_capacity *= 2; - new_sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, - ini->section_capacity * sizeof( ini->sections[ 0 ] ) ); - INI_MEMCPY( new_sections, ini->sections, ini->section_count * sizeof( ini->sections[ 0 ] ) ); - INI_FREE( ini->memctx, ini->sections ); - ini->sections = new_sections; - } - - ini->sections[ ini->section_count ].name_large = 0; - if( (size_t) length + 1 >= sizeof( ini->sections[ 0 ].name ) ) - { - ini->sections[ ini->section_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); - INI_MEMCPY( ini->sections[ ini->section_count ].name_large, name, (size_t) length ); - ini->sections[ ini->section_count ].name_large[ length ] = '\0'; - } - else - { - INI_MEMCPY( ini->sections[ ini->section_count ].name, name, (size_t) length ); - ini->sections[ ini->section_count ].name[ length ] = '\0'; - } - - return ini->section_count++; - } - return INI_NOT_FOUND; - } - - -void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ) - { - struct ini_internal_property_t* new_properties; - - if( ini && name && section >= 0 && section < ini->section_count ) - { - if( name_length <= 0 ) name_length = (int) INI_STRLEN( name ); - if( value_length <= 0 ) value_length = (int) INI_STRLEN( value ); - - if( ini->property_count >= ini->property_capacity ) - { - - ini->property_capacity *= 2; - new_properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, - ini->property_capacity * sizeof( ini->properties[ 0 ] ) ); - INI_MEMCPY( new_properties, ini->properties, ini->property_count * sizeof( ini->properties[ 0 ] ) ); - INI_FREE( ini->memctx, ini->properties ); - ini->properties = new_properties; - } - - ini->properties[ ini->property_count ].section = section; - ini->properties[ ini->property_count ].name_large = 0; - ini->properties[ ini->property_count ].value_large = 0; - - if( (size_t) name_length + 1 >= sizeof( ini->properties[ 0 ].name ) ) - { - ini->properties[ ini->property_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) name_length + 1 ); - INI_MEMCPY( ini->properties[ ini->property_count ].name_large, name, (size_t) name_length ); - ini->properties[ ini->property_count ].name_large[ name_length ] = '\0'; - } - else - { - INI_MEMCPY( ini->properties[ ini->property_count ].name, name, (size_t) name_length ); - ini->properties[ ini->property_count ].name[ name_length ] = '\0'; - } - - if( (size_t) value_length + 1 >= sizeof( ini->properties[ 0 ].value ) ) - { - ini->properties[ ini->property_count ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) value_length + 1 ); - INI_MEMCPY( ini->properties[ ini->property_count ].value_large, value, (size_t) value_length ); - ini->properties[ ini->property_count ].value_large[ value_length ] = '\0'; - } - else - { - INI_MEMCPY( ini->properties[ ini->property_count ].value, value, (size_t) value_length ); - ini->properties[ ini->property_count ].value[ value_length ] = '\0'; - } - - ++ini->property_count; - } - } - - -void ini_section_remove( ini_t* ini, int section ) - { - int p; - - if( ini && section >= 0 && section < ini->section_count ) - { - if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large ); - for( p = ini->property_count - 1; p >= 0; --p ) - { - if( ini->properties[ p ].section == section ) - { - if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); - if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); - ini->properties[ p ] = ini->properties[ --ini->property_count ]; - } - } - - ini->sections[ section ] = ini->sections[ --ini->section_count ]; - - for( p = 0; p < ini->property_count; ++p ) - { - if( ini->properties[ p ].section == ini->section_count ) - ini->properties[ p ].section = section; - } - } - } - - -void ini_property_remove( ini_t* ini, int section, int property ) - { - int p; - - if( ini && section >= 0 && section < ini->section_count ) - { - p = ini_internal_property_index( ini, section, property ); - if( p != INI_NOT_FOUND ) - { - if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); - if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); - ini->properties[ p ] = ini->properties[ --ini->property_count ]; - return; - } - } - } - - -void ini_section_name_set( ini_t* ini, int section, char const* name, int length ) - { - if( ini && name && section >= 0 && section < ini->section_count ) - { - if( length <= 0 ) length = (int) INI_STRLEN( name ); - if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large ); - ini->sections[ section ].name_large = 0; - - if( (size_t) length + 1 >= sizeof( ini->sections[ 0 ].name ) ) - { - ini->sections[ section ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); - INI_MEMCPY( ini->sections[ section ].name_large, name, (size_t) length ); - ini->sections[ section ].name_large[ length ] = '\0'; - } - else - { - INI_MEMCPY( ini->sections[ section ].name, name, (size_t) length ); - ini->sections[ section ].name[ length ] = '\0'; - } - } - } - - -void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ) - { - int p; - - if( ini && name && section >= 0 && section < ini->section_count ) - { - if( length <= 0 ) length = (int) INI_STRLEN( name ); - p = ini_internal_property_index( ini, section, property ); - if( p != INI_NOT_FOUND ) - { - if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large ); - ini->properties[ ini->property_count ].name_large = 0; - - if( (size_t) length + 1 >= sizeof( ini->properties[ 0 ].name ) ) - { - ini->properties[ p ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); - INI_MEMCPY( ini->properties[ p ].name_large, name, (size_t) length ); - ini->properties[ p ].name_large[ length ] = '\0'; - } - else - { - INI_MEMCPY( ini->properties[ p ].name, name, (size_t) length ); - ini->properties[ p ].name[ length ] = '\0'; - } - } - } - } - - -void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ) - { - int p; - - if( ini && value && section >= 0 && section < ini->section_count ) - { - if( length <= 0 ) length = (int) INI_STRLEN( value ); - p = ini_internal_property_index( ini, section, property ); - if( p != INI_NOT_FOUND ) - { - if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large ); - ini->properties[ ini->property_count ].value_large = 0; - - if( (size_t) length + 1 >= sizeof( ini->properties[ 0 ].value ) ) - { - ini->properties[ p ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 ); - INI_MEMCPY( ini->properties[ p ].value_large, value, (size_t) length ); - ini->properties[ p ].value_large[ length ] = '\0'; - } - else - { - INI_MEMCPY( ini->properties[ p ].value, value, (size_t) length ); - ini->properties[ p ].value[ length ] = '\0'; - } - } - } - } - - -#endif /* INI_IMPLEMENTATION */ - -/* - -contributors: - Randy Gaul (copy-paste bug in ini_property_value_set) - Branimir Karadzic (INI_STRNICMP bugfix) - -revision history: - 1.2 using strnicmp for correct length compares, fixed copy-paste bug in ini_property_value_set - 1.1 customization, added documentation, cleanup - 1.0 first publicly released version - -*/ - -/* ------------------------------------------------------------------------------- - -This software is available under 2 licenses - you may choose the one you like. - ------------------------------------------------------------------------------- - -ALTERNATIVE A - MIT License - -Copyright (c) 2015 Mattias Gustavsson - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ------------------------------------------------------------------------------- - -ALTERNATIVE B - Public Domain (www.unlicense.org) - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. - -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ------------------------------------------------------------------------------- -*/ diff --git a/3rdparty/ini/ini.md b/3rdparty/ini/ini.md deleted file mode 100644 index 0b204c0..0000000 --- a/3rdparty/ini/ini.md +++ /dev/null @@ -1,333 +0,0 @@ -ini.h -===== - -Library: [ini.h](../ini.h) - - -Examples -======== - -Loading an ini file and retrieving values ------------------------------------------ - -```cpp -#define INI_IMPLEMENTATION -#include "ini.h" - -#include -#include - -int main() - { - FILE* fp = fopen( "test.ini", "r" ); - fseek( fp, 0, SEEK_END ); - int size = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - char* data = (char*) malloc( size + 1 ); - fread( data, 1, size, fp ); - data[ size ] = '\0'; - fclose( fp ); - - ini_t* ini = ini_load( data ); - free( data ); - int second_index = ini_find_property( ini, INI_GLOBAL_SECTION, "SecondSetting" ); - char const* second = ini_property_value( ini, INI_GLOBAL_SECTION, second_index ); - printf( "%s=%s\n", "SecondSetting", second ); - int section = ini_find_section( ini, "MySection" ); - int third_index = ini_find_property( ini, section, "ThirdSetting" ); - char const* third = ini_property_value( ini, section, third_index ); - printf( "%s=%s\n", "ThirdSetting", third ); - ini_destroy( ini ); - - return 0; - } -``` - -Creating a new ini file ------------------------ - -```cpp -#define INI_IMPLEMENTATION -#include "ini.h" - -#include -#include - -int main() - { - ini_t* ini = ini_create(); - ini_property_add( ini, INI_GLOBAL_SECTION, "FirstSetting", "Test" ); - ini_property_add( ini, INI_GLOBAL_SECTION, "SecondSetting", "2" ); - int section = ini_section_add( ini, "MySection" ); - ini_property_add( ini, section, "ThirdSetting", "Three" ); - - int size = ini_save( ini, NULL, 0 ); // Find the size needed - char* data = (char*) malloc( size ); - size = ini_save( ini, data, size ); // Actually save the file - ini_destroy( ini ); - - FILE* fp = fopen( "test.ini", "w" ); - fwrite( data, 1, size, fp ); - fclose( fp ); - free( data ); - - return 0; - } -``` - - -API Documentation -================= - -ini.h is a small library for reading classic .ini files. It is a single-header library, and does not need any .lib files -or other binaries, or any build scripts. To use it, you just include ini.h to get the API declarations. To get the -definitions, you must include ini.h from *one* single C or C++ file, and #define the symbol `INI_IMPLEMENTATION` before -you do. - - -Customization -------------- -There are a few different things in ini.h which are configurable by #defines. The customizations only affect the -implementation, so will only need to be defined in the file where you have the #define INI_IMPLEMENTATION. - -Note that if all customizations are utilized, ini.h will include no external files whatsoever, which might be useful -if you need full control over what code is being built. - - -### Custom memory allocators - -To store the internal data structures, ini.h needs to do dynamic allocation by calling `malloc`. Programs might want to -keep track of allocations done, or use custom defined pools to allocate memory from. ini.h allows for specifying custom -memory allocation functions for `malloc` and `free`. -This is done with the following code: - - #define INI_IMPLEMENTATION - #define INI_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) ) - #define INI_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) ) - #include "ini.h" - -where `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter -is an optional parameter of type `void*`. When `ini_create` or `ini_load` is called, you can pass in a `memctx` -parameter, which can be a pointer to anything you like, and which will be passed through as the `ctx` parameter to every -`INI_MALLOC`/`INI_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your tracking -data as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the -right type, and access the tracking data. - -If no custom allocator is defined, ini.h will default to `malloc` and `free` from the C runtime library. - - -### Custom C runtime function - -The library makes use of three additional functions from the C runtime library, and for full flexibility, it allows you -to substitute them for your own. Here's an example: - - #define INI_IMPLEMENTATION - #define INI_MEMCPY( dst, src, cnt ) ( my_memcpy_func( dst, src, cnt ) ) - #define INI_STRLEN( s ) ( my_strlen_func( s ) ) - #define INI_STRICMP( s1, s2 ) ( my_stricmp_func( s1, s2 ) ) - #include "ini.h" - -If no custom function is defined, ini.h will default to the C runtime library equivalent. - - -ini_create ----------- - - ini_t* ini_create( void* memctx ) - -Instantiates a new, empty ini structure, which can be manipulated with other API calls, to fill it with data. To save it -out to an ini-file string, use `ini_save`. When no longer needed, it can be destroyed by calling `ini_destroy`. -`memctx` is a pointer to user defined data which will be passed through to the custom INI_MALLOC/INI_FREE calls. It can -be NULL if no user defined data is needed. - - -ini_load --------- - - ini_t* ini_load( char const* data, void* memctx ) - -Parse the zero-terminated string `data` containing an ini-file, and create a new ini_t instance containing the data. -The instance can be manipulated with other API calls to enumerate sections/properties and retrieve values. When no -longer needed, it can be destroyed by calling `ini_destroy`. `memctx` is a pointer to user defined data which will be -passed through to the custom INI_MALLOC/INI_FREE calls. It can be NULL if no user defined data is needed. - - -ini_save --------- - - int ini_save( ini_t const* ini, char* data, int size ) - -Saves an ini structure as a zero-terminated ini-file string, into the specified buffer. Returns the number of bytes -written, including the zero terminator. If `data` is NULL, nothing is written, but `ini_save` still returns the number -of bytes it would have written. If the size of `data`, as specified in the `size` parameter, is smaller than that -required, only part of the ini-file string will be written. `ini_save` still returns the number of bytes it would have -written had the buffer been large enough. - - -ini_destroy ------------ - - void ini_destroy( ini_t* ini ) - -Destroy an `ini_t` instance created by calling `ini_load` or `ini_create`, releasing the memory allocated by it. No -further API calls are valid on an `ini_t` instance after calling `ini_destroy` on it. - - -ini_section_count ------------------ - - int ini_section_count( ini_t const* ini ) - -Returns the number of sections in an ini file. There's at least one section in an ini file (the global section), but -there can be many more, each specified in the file by the section name wrapped in square brackets [ ]. - - -ini_section_name ----------------- - - char const* ini_section_name( ini_t const* ini, int section ) - -Returns the name of the section with the specified index. `section` must be non-negative and less than the value -returned by `ini_section_count`, or `ini_section_name` will return NULL. The defined constant `INI_GLOBAL_SECTION` can -be used to indicate the global section. - - -ini_property_count ------------------- - - int ini_property_count( ini_t const* ini, int section ) - -Returns the number of properties belonging to the section with the specified index. `section` must be non-negative and -less than the value returned by `ini_section_count`, or `ini_section_name` will return 0. The defined constant -`INI_GLOBAL_SECTION` can be used to indicate the global section. Properties are declared in the ini-file on he format -`name=value`. - - -ini_property_name ------------------ - - char const* ini_property_name( ini_t const* ini, int section, int property ) - -Returns the name of the property with the specified index `property` in the section with the specified index `section`. -`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be -non-negative and less than the value returned by `ini_property_count`, or `ini_property_name` will return NULL. The -defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_property_value ------------------- - - char const* ini_property_value( ini_t const* ini, int section, int property ) - -Returns the value of the property with the specified index `property` in the section with the specified index `section`. -`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be -non-negative and less than the value returned by `ini_property_count`, or `ini_property_value` will return NULL. The -defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_find_section ----------------- - - int ini_find_section( ini_t const* ini, char const* name, int name_length ) - -Finds the section with the specified name, and returns its index. `name_length` specifies the number of characters in -`name`, which does not have to be zero-terminated. If `name_length` is zero, the length is determined automatically, but -in this case `name` has to be zero-terminated. If no section with the specified name could be found, the value -`INI_NOT_FOUND` is returned. - - -ini_find_property ------------------ - - int ini_find_property( ini_t const* ini, int section, char const* name, int name_length ) - -Finds the property with the specified name, within the section with the specified index, and returns the index of the -property. `name_length` specifies the number of characters in `name`, which does not have to be zero-terminated. If -`name_length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. If no -property with the specified name could be found within the specified section, the value `INI_NOT_FOUND` is returned. -`section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_find_property` will -return `INI_NOT_FOUND`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. - - -ini_section_add ---------------- - - int ini_section_add( ini_t* ini, char const* name, int length ) - -Adds a section with the specified name, and returns the index it was added at. There is no check done to see if a -section with the specified name already exists - multiple sections of the same name are allowed. `length` specifies the -number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined -automatically, but in this case `name` has to be zero-terminated. - - -ini_property_add ----------------- - - void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length ) - -Adds a property with the specified name and value to the specified section, and returns the index it was added at. There -is no check done to see if a property with the specified name already exists - multiple properties of the same name are -allowed. `name_length` and `value_length` specifies the number of characters in `name` and `value`, which does not have -to be zero-terminated. If `name_length` or `value_length` is zero, the length is determined automatically, but in this -case `name`/`value` has to be zero-terminated. `section` must be non-negative and less than the value returned by -`ini_section_count`, or the property will not be added. The defined constant `INI_GLOBAL_SECTION` can be used to -indicate the global section. - - -ini_section_remove ------------------- - - void ini_section_remove( ini_t* ini, int section ) - -Removes the section with the specified index, and all properties within it. `section` must be non-negative and less than -the value returned by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global -section. Note that removing a section will shuffle section indices, so that section indices you may have stored will no -longer indicate the same section as it did before the remove. Use the find functions to update your indices. - - -ini_property_remove -------------------- - - void ini_property_remove( ini_t* ini, int section, int property ) - -Removes the property with the specified index from the specified section. `section` must be non-negative and less than -the value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by -`ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Note that -removing a property will shuffle property indices within the specified section, so that property indices you may have -stored will no longer indicate the same property as it did before the remove. Use the find functions to update your -indices. - - -ini_section_name_set --------------------- - - void ini_section_name_set( ini_t* ini, int section, char const* name, int length ) - -Change the name of the section with the specified index. `section` must be non-negative and less than the value returned -by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` -specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length -is determined automatically, but in this case `name` has to be zero-terminated. - - -ini_property_name_set ---------------------- - - void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length ) - -Change the name of the property with the specified index in the specified section. `section` must be non-negative and -less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value -returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. -`length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, -the length is determined automatically, but in this case `name` has to be zero-terminated. - - -ini_property_value_set ----------------------- - - void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length ) - -Change the value of the property with the specified index in the specified section. `section` must be non-negative and -less than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value -returned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. -`length` specifies the number of characters in `value`, which does not have to be zero-terminated. If `length` is zero, -the length is determined automatically, but in this case `value` has to be zero-terminated. diff --git a/include/bx/inline/scanner.inl b/include/bx/inline/scanner.inl new file mode 100644 index 0000000..879e363 --- /dev/null +++ b/include/bx/inline/scanner.inl @@ -0,0 +1,228 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#ifndef BX_SCANNER_H_HEADER_GUARD +# error "Must be included from bx/scanner.h!" +#endif // BX_SCANNER_H_HEADER_GUARD + +namespace bx +{ + inline Scanner::Scanner(const StringView& _input) + : m_input(_input) + , m_tail(_input) + , m_line(0) + { + } + + inline void Scanner::reset() + { + m_tail = m_input; + m_line = 0; + } + + inline StringView Scanner::acceptAll() + { + StringView result = m_tail; + moveBy(result.getLength() ); + + return result; + } + + inline StringView Scanner::accept() + { + return moveBy(1); + } + + inline StringView Scanner::accept(char _ch) + { + if (!m_tail.isEmpty() + && _ch == *m_tail.getPtr() ) + { + return moveBy(1); + } + + return StringView(); + } + + inline StringView Scanner::accept(char _ch0, char _ch1) + { + if (!m_tail.isEmpty() ) + { + const char ch = *m_tail.getPtr(); + + if (ch == _ch0 + || ch == _ch1) + { + return moveBy(1); + } + } + + return StringView(); + } + + template + inline StringView Scanner::accept(char _ch0, char _ch1, Args... _args) + { + StringView result = accept(_ch0, _ch1); + + if (!result.isEmpty() ) + { + return result; + } + + return accept(_args...); + } + + inline StringView Scanner::accept(const StringView& _str) + { + if (hasPrefix(m_tail, _str) ) + { + return moveTo({ m_tail.getPtr(), _str.getLength() }); + } + + return StringView(); + } + + inline StringView Scanner::accept(Class _class) + { + return moveTo({ m_tail.getPtr(), strFunc(_class).getPtr() }); + } + + inline StringView Scanner::accept(CharTestFn _fn) + { + if (!m_tail.isEmpty() + && _fn(*m_tail.getPtr() ) ) + { + return moveBy(1); + } + + return StringView(); + } + + inline StringView Scanner::acceptWhile(const StringView& _any) + { + const StringView input = strLTrim(m_tail, _any); + + if (input.getPtr() != m_tail.getPtr() ) + { + return moveTo({ m_tail.getPtr(), input.getPtr() }); + } + + return StringView(); + } + + inline StringView Scanner::acceptWhile(CharTestFn _fn) + { + const char* ptr = m_tail.getPtr(); + const char* term = m_tail.getTerm(); + + while (ptr != term + && _fn(*ptr) ) + { + ++ptr; + } + + return moveTo({ m_tail.getPtr(), ptr }); + } + + inline StringView Scanner::acceptUntil(const StringView& _find) + { + StringView result = strFind(m_tail, _find); + + if (!result.isEmpty() ) + { + return moveTo({ m_tail.getPtr(), result.getPtr() }); + } + + return result; + } + + inline StringView Scanner::acceptUntil(Class _class) + { + StringView result = strFunc(_class); + return moveTo({ m_tail.getPtr(), result.getPtr() }); + } + + inline StringView Scanner::peek() const + { + return m_tail.isEmpty() + ? StringView() + : StringView(m_tail.getPtr(), m_tail.getPtr() + 1) + ; + } + + inline StringView Scanner::peek(char _ch) const + { + if (!m_tail.isEmpty() + && _ch == *m_tail.getPtr() ) + { + return StringView(m_tail.getPtr(), m_tail.getPtr() + 1); + } + + return StringView(); + } + + inline StringView Scanner::peek(const StringView& _str) const + { + if (hasPrefix(m_tail, _str) ) + { + return StringView(m_tail.getPtr(), _str.getLength() ); + } + + return StringView(); + } + + inline StringView Scanner::peek(Class _class) const + { + return StringView(m_tail.getPtr(), strFunc(_class).getPtr() ); + } + + inline bool Scanner::seek(int32_t _bytes) + { + moveBy(_bytes); + + return true; + } + + inline uint32_t Scanner::getLine() const + { + return m_line; + } + + inline StringView Scanner::between(const StringView& _from) const + { + const char* a = _from.getPtr(); + const char* b = m_tail.getPtr(); + return a <= b ? StringView(a, b) : StringView(b, a); + } + + inline bool Scanner::isDone() const + { + return m_tail.isEmpty(); + } + + inline LineReader::LineReader(const StringView& _str) + : m_scanner(_str) + , m_line(0) + { + } + + inline void LineReader::reset() + { + m_scanner.reset(); + m_line = 0; + } + + inline uint32_t LineReader::getLine() const + { + return m_line; + } + + inline bool LineReader::isDone() const + { + return m_scanner.isDone(); + } + +} // namespace bx diff --git a/include/bx/inline/string.inl b/include/bx/inline/string.inl index b34fe95..82028ad 100644 --- a/include/bx/inline/string.inl +++ b/include/bx/inline/string.inl @@ -461,50 +461,121 @@ namespace bx return StringView(m_ptr, m_len); } + inline BX_CONSTEXPR_FUNC bool isInRange(char _ch, char _from, char _to) + { + return unsigned(_ch - _from) <= unsigned(_to-_from); + } + + inline BX_CONSTEXPR_FUNC bool isSpace(char _ch) + { + return ' ' == _ch // Space. + || '\t' == _ch // Horizontal tab. + || '\n' == _ch // Line feed / new line. + || '\r' == _ch // Carriage return. + || '\v' == _ch // Vertical tab. + || '\f' == _ch // Form feed / new page. + ; + } + + inline BX_CONSTEXPR_FUNC bool isSpaceHoriz(char _ch) + { + return false + || ' ' == _ch + || '\t' == _ch + || '\r' == _ch + ; + } + + inline BX_CONSTEXPR_FUNC bool isUpper(char _ch) + { + return isInRange(_ch, 'A', 'Z'); + } + + inline BX_CONSTEXPR_FUNC char toUpper(char _ch) + { + return _ch - (isLower(_ch) ? 0x20 : 0); + } + + inline BX_CONSTEXPR_FUNC bool isLower(char _ch) + { + return isInRange(_ch, 'a', 'z'); + } + + inline BX_CONSTEXPR_FUNC char toLower(char _ch) + { + return _ch + (isUpper(_ch) ? 0x20 : 0); + } + + inline BX_CONSTEXPR_FUNC bool isAlpha(char _ch) + { + return isLower(_ch) || isUpper(_ch); + } + + inline BX_CONSTEXPR_FUNC bool isNumeric(char _ch) + { + return isInRange(_ch, '0', '9'); + } + + inline BX_CONSTEXPR_FUNC bool isAlphaNum(char _ch) + { + return false + || isAlpha(_ch) + || isNumeric(_ch) + ; + } + + inline BX_CONSTEXPR_FUNC bool isHexNum(char _ch) + { + return false + || isInRange(toLower(_ch), 'a', 'f') + || isNumeric(_ch) + ; + } + + inline BX_CONSTEXPR_FUNC bool isOctNum(char _ch) + { + return isInRange(_ch, '0', '7'); + } + + inline BX_CONSTEXPR_FUNC bool isBinNum(char _ch) + { + return isInRange(_ch, '0', '1'); + } + + inline BX_CONSTEXPR_FUNC bool isPrint(char _ch) + { + return isInRange(_ch, ' ', '~'); + } + + inline BX_CONSTEXPR_FUNC bool isIdentStart(char _ch) + { + return false + || isAlpha(_ch) + || '_' == _ch + ; + } + + inline BX_CONSTEXPR_FUNC bool isIdentChar(char _ch) + { + return false + || isAlphaNum(_ch) + || '_' == _ch + ; + } + + inline BX_CONSTEXPR_FUNC bool isPathSeparator(char _ch) + { + return false + || '/' == _ch + || '\\' == _ch + ; + } + inline StringView strSubstr(const StringView& _str, int32_t _start, int32_t _len) { return StringView(_str, _start, _len); } - inline LineReader::LineReader(const StringView& _str) - : m_str(_str) - { - reset(); - } - - inline void LineReader::reset() - { - m_curr = m_str; - m_line = 0; - } - - inline StringView LineReader::next() - { - if (m_curr.getPtr() != m_str.getTerm() ) - { - ++m_line; - - StringView curr(m_curr); - m_curr = strFindNl(m_curr); - - StringView line(curr.getPtr(), m_curr.getPtr() ); - - return strRTrim(strRTrim(line, "\n"), "\r"); - } - - return m_curr; - } - - inline bool LineReader::isDone() const - { - return m_curr.getPtr() == m_str.getTerm(); - } - - inline uint32_t LineReader::getLine() const - { - return m_line; - } - inline constexpr int32_t strLen(const StringView& _str, int32_t _max) { return min(_str.getLength(), _max); diff --git a/include/bx/scanner.h b/include/bx/scanner.h new file mode 100644 index 0000000..bbcbf63 --- /dev/null +++ b/include/bx/scanner.h @@ -0,0 +1,341 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#ifndef BX_SCANNER_H_HEADER_GUARD +#define BX_SCANNER_H_HEADER_GUARD + +#include + +namespace bx +{ + /// Forward-only cursor over a string view, used to tokenize text input. + /// + /// Scanner keeps a cursor inside immutable input string, and tracks line number as cursor + /// moves. `accept*` functions advance cursor when they match, `peek*` functions perform the + /// same test without moving cursor. + /// + /// All returned string views point into input string, and no copies are made. + /// + /// @attention Scanner doesn't own input string. Input string must outlive Scanner. + /// + class Scanner + { + public: + /// Character class matched by `accept`, `peek`, and `acceptUntil`. + /// + enum class Class : uint8_t + { + Space, //!< Run of whitespace characters. + NonSpace, //!< Run of non-whitespace characters. + Identifier, //!< Run of alphanumeric characters and `_`. + EndOfLine, //!< Everything up to, but not including, line terminator. + NewLine, //!< Everything up to, and including, line terminator. + }; + + /// Position inside input string returned by `getCursor`. + /// + enum class Cursor : uint8_t + { + Current, //!< Current cursor position. + LineStart, //!< First character of line cursor is on. + LineEnd, //!< Position of `\n` that terminates line cursor is on, or end of input. + }; + + /// Default constructor is not available. Scanner must be constructed with input string. + /// + Scanner() = delete; + + /// Constructor. + /// + /// @param[in] _input Input string to scan. It's not copied, and it must outlive Scanner. + /// + Scanner(const StringView& _input); + + /// Rewind cursor to the beginning of input string, and reset line counter. + /// + void reset(); + + /// Accept everything from cursor to the end of input string. + /// + /// @returns Accepted string view, or empty string view if input is exhausted. + /// + StringView acceptAll(); + + /// Accept single character. + /// + /// @returns Accepted character, or empty string view if input is exhausted. + /// + StringView accept(); + + /// Accept single character if it matches `_ch`. + /// + /// @param[in] _ch Character to match. + /// + /// @returns Accepted character, or empty string view if it doesn't match. + /// + StringView accept(char _ch); + + /// Accept single character if it matches any of the characters passed in. + /// + /// @param[in] _ch0 First character to match. + /// @param[in] _ch1 Second character to match. + /// + /// @returns Accepted character, or empty string view if it doesn't match. + /// + StringView accept(char _ch0, char _ch1); + + /// Accept single character if it matches any of the characters passed in. + /// + /// @param[in] _ch0 First character to match. + /// @param[in] _ch1 Second character to match. + /// @param[in] _args Additional characters to match. + /// + /// @returns Accepted character, or empty string view if it doesn't match. + /// + template + StringView accept(char _ch0, char _ch1, Args... _args); + + /// Accept `_str` if input at cursor starts with it. + /// + /// @param[in] _str String to match. + /// + /// @returns Accepted string view, or empty string view if it doesn't match. + /// + StringView accept(const StringView& _str); + + /// Accept leading run of characters belonging to `_class`. + /// + /// @param[in] _class Character class to match. + /// + /// @returns Accepted string view, or empty string view if it doesn't match. + /// + StringView accept(Class _class); + + /// Accept single character if `_fn` returns true for it. + /// + /// @param[in] _fn Character test function. + /// + /// @returns Accepted character, or empty string view if it doesn't match. + /// + StringView accept(CharTestFn _fn); + + /// Accept characters as long as they are contained in `_any`. + /// + /// @param[in] _any Set of characters to match. + /// + /// @returns Accepted string view, or empty string view if it doesn't match. + /// + StringView acceptWhile(const StringView& _any); + + /// Accept characters as long as `_fn` returns true for them. + /// + /// @param[in] _fn Character test function. + /// + /// @returns Accepted string view, or empty string view if it doesn't match. + /// + StringView acceptWhile(CharTestFn _fn); + + /// Accept characters until `_find` is found. Cursor stops at `_find`, and `_find` is not + /// accepted. + /// + /// @param[in] _find String to search for. + /// + /// @returns Accepted string view, or empty string view if `_find` is not found, or it's + /// already at cursor. + /// + /// @attention Returned string view is empty in both cases, and cursor doesn't move. + /// Use `getCursor` and `between` to capture text that can legitimately be empty. + /// + StringView acceptUntil(const StringView& _find); + + /// Accept leading run of characters belonging to `_class`. + /// + /// @param[in] _class Character class to match. + /// + /// @returns Accepted string view, or empty string view if it doesn't match. + /// + /// @remarks Same as `accept(Class)`. It's provided for readability when used with + /// `Class::EndOfLine` and `Class::NewLine`. + /// + StringView acceptUntil(Class _class); + + /// Peek at single character without moving cursor. + /// + /// @returns Next character, or empty string view if input is exhausted. + /// + StringView peek() const; + + /// Peek at single character without moving cursor, if it matches `_ch`. + /// + /// @param[in] _ch Character to match. + /// + /// @returns Next character, or empty string view if it doesn't match. + /// + StringView peek(char _ch) const; + + /// Peek at `_str` without moving cursor, if input at cursor starts with it. + /// + /// @param[in] _str String to match. + /// + /// @returns Matched string view, or empty string view if it doesn't match. + /// + StringView peek(const StringView& _str) const; + + /// Peek at leading run of characters belonging to `_class`, without moving cursor. + /// + /// @param[in] _class Character class to match. + /// + /// @returns Matched string view. It's zero length if it doesn't match. + /// + StringView peek(Class _class) const; + + /// Move cursor to `_to`. Line number is adjusted accordingly, and cursor can move backward. + /// + /// @param[in] _to Position to move cursor to. It must be a string view into input string. + /// + /// @returns True if cursor moved, or false if `_to` is not a string view into input string. + /// + bool seek(const StringView& _to); + + /// Move cursor by `_bytes`. Negative value moves cursor backward. Line number is adjusted + /// accordingly, and value is clamped to input string bounds. + /// + /// @param[in] _bytes Number of bytes to move cursor by. + /// + /// @returns Always true. + /// + bool seek(int32_t _bytes); + + /// Returns zero-based line number cursor is on. + /// + /// @returns Line number. + /// + uint32_t getLine() const; + + /// Returns one-based column number cursor is on. + /// + /// @returns Column number. + /// + uint32_t getColumn() const; + + /// Returns cursor position as zero length string view. + /// + /// @param[in] _which Position to return. + /// + /// @returns Zero length string view at requested position. + /// + /// @remarks Unlike default constructed `StringView`, returned string view keeps pointer + /// into input string even though it's empty. Use it with `between` to capture text that + /// can legitimately be empty. + /// + StringView getCursor(Cursor _which = Cursor::Current) const; + + /// Returns text between `_from` and current cursor position. + /// + /// @param[in] _from Position to measure from, usually obtained by `getCursor`. + /// + /// @returns String view between `_from` and cursor. Order of arguments doesn't matter, + /// `_from` can be either before or after cursor. + /// + StringView between(const StringView& _from) const; + + /// Returns true if input string is exhausted. + /// + /// @returns True if cursor reached end of input string, otherwise returns false. + /// + bool isDone() const; + + private: + /// Move cursor to `_to`, and update line number. + /// + /// @param[in] _to Position to move cursor to. + /// + /// @returns String view between old and new cursor position, or empty string view if `_to` + /// is empty. + /// + StringView moveTo(const StringView& _to); + + /// Move cursor by `_bytes` clamped to input string bounds, and update line number. + /// + /// @param[in] _bytes Number of bytes to move cursor by. + /// + /// @returns String view between old and new cursor position. + /// + StringView moveBy(int32_t _bytes); + + /// Returns end of leading run of characters belonging to `_class`. + /// + /// @param[in] _class Character class to match. + /// + /// @returns String view starting past the run, and ending at end of input string. + /// + StringView strFunc(Class _class) const; + + /// Count number of line terminators in `_str`. + /// + /// @param[in] _str String to count line terminators in. + /// + /// @returns Number of lines. + /// + static uint32_t countLines(const StringView& _str); + + const StringView m_input; + StringView m_tail; + uint32_t m_line; + }; + + /// Splits string into lines. + /// + /// Line terminator is `\n` or `\r\n`, and it's not part of returned line. Trailing `\r` left + /// by malformed input is trimmed. + /// + /// All returned string views point into input string, and no copies are made. + /// + /// @attention LineReader doesn't own input string. Input string must outlive LineReader. + /// + class LineReader + { + public: + /// Constructor. + /// + /// @param[in] _str Input string to read lines from. It's not copied, and it must outlive + /// LineReader. + /// + LineReader(const StringView& _str); + + /// Rewind to the beginning of input string, and reset line counter. + /// + void reset(); + + /// Read next line. + /// + /// @returns Next line without line terminator. Line can be empty, and returned string view + /// keeps pointer into input string. If input is exhausted, zero length string view at + /// end of input string is returned. + /// + StringView next(); + + /// Returns one-based line number of line returned by the last `next` call. + /// + /// @returns Line number, or 0 if `next` was not called yet. + /// + uint32_t getLine() const; + + /// Returns true if input string is exhausted. + /// + /// @returns True if all lines are read, otherwise returns false. + /// + bool isDone() const; + + private: + Scanner m_scanner; + uint32_t m_line; + }; + +} // namespace bx + +#include "inline/scanner.inl" + +#endif // BX_SCANNER_H_HEADER_GUARD diff --git a/include/bx/settings.h b/include/bx/settings.h index 7e846db..a2630e0 100644 --- a/include/bx/settings.h +++ b/include/bx/settings.h @@ -46,8 +46,10 @@ namespace bx private: Settings(); + struct Ini; + AllocatorI* m_allocator; - void* m_ini; + Ini* m_ini; }; /// diff --git a/include/bx/string.h b/include/bx/string.h index 3bfc41b..99fd2ff 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -279,6 +279,8 @@ namespace bx int32_t m_capacity; }; + typedef bool (*CharTestFn)(char _ch); + /// Returns true if character is part of white space set. /// /// White space set is: @@ -289,55 +291,73 @@ namespace bx /// '\v' - Vertical tab. /// '\f' - Form feed / new page. /// - bool isSpace(char _ch); + BX_CONSTEXPR_FUNC bool isSpace(char _ch); /// Returns true if string view contains only space characters. bool isSpace(const StringView& _str); + /// Returns true if character is horizontal white space. + BX_CONSTEXPR_FUNC bool isSpaceHoriz(char _ch); + /// Returns true if character is uppercase. - bool isUpper(char _ch); + BX_CONSTEXPR_FUNC bool isUpper(char _ch); /// Returns true if string view contains only uppercase characters. bool isUpper(const StringView& _str); /// Returns true if character is lowercase. - bool isLower(char _ch); + BX_CONSTEXPR_FUNC bool isLower(char _ch); /// Returns true if string view contains only lowercase characters. bool isLower(const StringView& _str); /// Returns true if character is part of alphabet set. - bool isAlpha(char _ch); + BX_CONSTEXPR_FUNC bool isAlpha(char _ch); /// Returns true if string view contains only alphabet characters. bool isAlpha(const StringView& _str); /// Returns true if character is part of numeric set. - bool isNumeric(char _ch); + BX_CONSTEXPR_FUNC bool isNumeric(char _ch); /// Returns true if string view contains only numeric characters. bool isNumeric(const StringView& _str); /// Returns true if character is part of alpha numeric set. - bool isAlphaNum(char _ch); + BX_CONSTEXPR_FUNC bool isAlphaNum(char _ch); /// Returns true if string view contains only alphanumeric characters. bool isAlphaNum(const StringView& _str); /// Returns true if character is part of hexadecimal set. - bool isHexNum(char _ch); + BX_CONSTEXPR_FUNC bool isHexNum(char _ch); /// Returns true if string view contains only hexadecimal characters. bool isHexNum(const StringView& _str); + /// + BX_CONSTEXPR_FUNC bool isOctNum(char _ch); + + /// + BX_CONSTEXPR_FUNC bool isBinNum(char _ch); + + /// + BX_CONSTEXPR_FUNC bool isIdentStart(char _ch); + + /// + BX_CONSTEXPR_FUNC bool isIdentChar(char _ch); + + /// + BX_CONSTEXPR_FUNC bool isPathSeparator(char _ch); + /// Returns true if character is printable. - bool isPrint(char _ch); + BX_CONSTEXPR_FUNC bool isPrint(char _ch); /// Returns true if string vieww contains only printable characters. bool isPrint(const StringView& _str); /// Returns lower case character representing _ch. - char toLower(char _ch); + BX_CONSTEXPR_FUNC char toLower(char _ch); /// Lower case string in place assuming length passed is valid. void toLowerUnsafe(char* _inOutStr, int32_t _len); @@ -346,7 +366,7 @@ namespace bx void toLower(char* _inOutStr, int32_t _max = INT32_MAX); /// Returns upper case character representing _ch. - char toUpper(char _ch); + BX_CONSTEXPR_FUNC char toUpper(char _ch); /// Upper case string in place assuming length passed is valid. void toUpperUnsafe(char* _inOutStr, int32_t _len); @@ -557,31 +577,6 @@ namespace bx /// Converts string to 64-bit unsigned long long value. bool fromString(unsigned long long* _out, const StringView& _str); - /// - class LineReader - { - public: - /// - LineReader(const StringView& _str); - - /// - void reset(); - - /// - StringView next(); - - /// - bool isDone() const; - - /// - uint32_t getLine() const; - - private: - const StringView m_str; - StringView m_curr; - uint32_t m_line; - }; - } // namespace bx #include "inline/string.inl" diff --git a/scripts/bx.lua b/scripts/bx.lua index 8b521e3..c4cbd95 100644 --- a/scripts/bx.lua +++ b/scripts/bx.lua @@ -78,6 +78,7 @@ project "bx" path.join(BX_DIR, "src/mutex.cpp"), path.join(BX_DIR, "src/os.cpp"), path.join(BX_DIR, "src/process.cpp"), + path.join(BX_DIR, "src/scanner.cpp"), path.join(BX_DIR, "src/semaphore.cpp"), path.join(BX_DIR, "src/settings.cpp"), path.join(BX_DIR, "src/sort.cpp"), diff --git a/src/amalgamated.cpp b/src/amalgamated.cpp index 1736413..1d679f8 100644 --- a/src/amalgamated.cpp +++ b/src/amalgamated.cpp @@ -18,6 +18,7 @@ #include "mutex.cpp" #include "os.cpp" #include "process.cpp" +#include "scanner.cpp" #include "semaphore.cpp" #include "settings.cpp" #include "sort.cpp" diff --git a/src/debug.cpp b/src/debug.cpp index 50aab73..76b9018 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -8,6 +8,7 @@ #include // WriterI #include // exit #include // ProcessReader +#include // Scanner #include // PRIx* @@ -947,22 +948,6 @@ namespace bx #elif BX_CONFIG_CALLSTACK_USE_EXECINFO - StringView strConsumeTo(StringView& _input, const StringView& _find) - { - const StringView to = strFind(_input, _find); - - if (!to.isEmpty() ) - { - const StringView result(_input.getPtr(), to.getPtr() ); - - _input.set(to.getTerm(), _input.getTerm() ); - - return result; - } - - return StringView(); - } - int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err) { int32_t total = write(_writer, _err, "Callstack (%d):\n", _num); @@ -1126,16 +1111,18 @@ namespace bx { for (LineReader lr({atosBuffer, bytes}); !lr.isDone();) { - StringView input = lr.next(); + Scanner scanner(lr.next() ); - atosFunctionName = strConsumeTo(input, " ("); + atosFunctionName = scanner.acceptUntil(" ("); if (atosFunctionName.isEmpty() ) { break; } - filePath = strConsumeTo(input, ":"); + scanner.accept(" ("); + + filePath = scanner.acceptUntil(":"); if (filePath.isEmpty() ) { @@ -1143,7 +1130,9 @@ namespace bx break; } - const StringView lineStr = strConsumeTo(input, ")"); + scanner.accept(':'); + + const StringView lineStr = scanner.acceptUntil(")"); if (!lineStr.isEmpty() ) { diff --git a/src/filepath.cpp b/src/filepath.cpp index da7a8ed..b2cb81a 100644 --- a/src/filepath.cpp +++ b/src/filepath.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #if BX_CRT_MSVC # include // _getcwd @@ -24,12 +25,9 @@ extern "C" int _NSGetExecutablePath(char* _buf, uint32_t* _bufSize); namespace bx { - static bool isPathSeparator(char _ch) + static bool isNotPathSeparator(char _ch) { - return false - || '/' == _ch - || '\\' == _ch - ; + return !isPathSeparator(_ch); } static int32_t normalizeFilePath(char* _dst, int32_t _dstSize, const char* _src, int32_t _num) @@ -38,9 +36,9 @@ namespace bx // - Lexical File Names in Plan 9 or Getting Dot-Dot Right // https://web.archive.org/web/20180629044444/https://9p.io/sys/doc/lexnames.html - const int32_t num = strLen(_src, _num); + const StringView src(_src, strLen(_src, _num) ); - if (0 == num) + if (src.isEmpty() ) { return strCopy(_dst, _dstSize, "."); } @@ -50,91 +48,80 @@ namespace bx StaticMemoryBlockWriter writer(_dst, _dstSize); Error err; - int32_t idx = 0; - int32_t dotdot = 0; + Scanner scanner(src); - if (2 <= num - && ':' == _src[1]) + // Everything below `dotdot` is a prefix `..` can't back out of. + int32_t dotdot = 0; + + if (2 <= src.getLength() + && ':' == src.getPtr()[1]) { - size += write(&writer, toUpper(_src[idx]), &err); + size += write(&writer, toUpper(src.getPtr()[0]), &err); size += write(&writer, ':', &err); - idx += 2; + scanner.seek(2); dotdot = size; } - const int32_t slashIdx = idx; + const bool rooted = !scanner.accept(isPathSeparator).isEmpty(); - bool rooted = isPathSeparator(_src[idx]); if (rooted) { size += write(&writer, '/', &err); - ++idx; dotdot = size; } + const int32_t rootSize = size; + bool trailingSlash = false; - while (idx < num && err.isOk() ) + while (!scanner.isDone() + && err.isOk() ) { - switch (_src[idx]) + if (!scanner.acceptWhile(isPathSeparator).isEmpty() ) { - case '/': - case '\\': - ++idx; - trailingSlash = idx == num; - break; - - case '.': - if (idx+1 == num - || isPathSeparator(_src[idx+1]) ) - { - ++idx; - break; - } - - if ('.' == _src[idx+1] - && (idx+2 == num || isPathSeparator(_src[idx+2]) ) ) - { - idx += 2; - - if (dotdot < size) - { - for (--size - ; dotdot < size && !isPathSeparator(_dst[size]) - ; --size) - { - } - seek(&writer, size, Whence::Begin); - } - else if (!rooted) - { - if (0 < size) - { - size += write(&writer, '/', &err); - } - - size += write(&writer, "..", &err); - dotdot = size; - } - - break; - } - [[fallthrough]]; - - default: - if ( ( rooted && slashIdx+1 != size) - || (!rooted && 0 != size) ) - { - size += write(&writer, '/', &err); - } - - for (; idx < num && !isPathSeparator(_src[idx]); ++idx) - { - size += write(&writer, _src[idx], &err); - } - - break; + trailingSlash = scanner.isDone(); + continue; } + + const StringView component = scanner.acceptWhile(isNotPathSeparator); + + if (component == ".") + { + continue; + } + + if (component == "..") + { + if (dotdot < size) + { + for (--size + ; dotdot < size && !isPathSeparator(_dst[size]) + ; --size) + { + } + + seek(&writer, size, Whence::Begin); + } + else if (!rooted) + { + if (0 < size) + { + size += write(&writer, '/', &err); + } + + size += write(&writer, "..", &err); + dotdot = size; + } + + continue; + } + + if (rooted ? rootSize != size : 0 != size) + { + size += write(&writer, '/', &err); + } + + size += write(&writer, component, &err); } if (0 == size) diff --git a/src/scanner.cpp b/src/scanner.cpp new file mode 100644 index 0000000..41e3206 --- /dev/null +++ b/src/scanner.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#include + +namespace bx +{ + bool Scanner::seek(const StringView& _to) + { + if (contain(m_input, _to) ) + { + moveTo(_to); + return true; + } + + BX_TRACE("StringView '%S' is not a view into input string!", &_to); + return false; + } + + uint32_t Scanner::getColumn() const + { + return uint32_t(between(getCursor(Cursor::LineStart) ).getLength() ) + 1; + } + + StringView Scanner::getCursor(Cursor _which) const + { + const char* ptr = m_tail.getPtr(); + + if (Cursor::LineStart == _which) + { + while (ptr > m_input.getPtr() + && '\n' != ptr[-1]) + { + --ptr; + } + } + else if (Cursor::LineEnd == _which) + { + const char* term = m_input.getTerm(); + while (ptr < term + && '\n' != *ptr) + { + ++ptr; + } + } + + return StringView(ptr, ptr); + } + + StringView Scanner::moveTo(const StringView& _to) + { + if (BX_LIKELY(!_to.isEmpty() ) ) + { + if (BX_LIKELY(overlap(m_tail, _to) ) ) + { + const StringView result(m_tail.getPtr(), _to.getTerm() ); + + m_tail.set(_to.getTerm(), m_tail.getTerm() ); + m_line += countLines(result); + + return result; + } + + const StringView result(_to.getPtr(), m_tail.getPtr() ); + + m_tail.set(_to.getPtr(), m_tail.getTerm() ); + m_line -= countLines(result); + + return result; + } + + return StringView(); + } + + StringView Scanner::strFunc(Class _class) const + { + switch (_class) + { + case Class::Space: + return strLTrimSpace(m_tail); + + case Class::NonSpace: + return strLTrimNonSpace(m_tail); + + case Class::Identifier: + { + const StringView word = strWord(m_tail); + return word.isEmpty() + ? m_tail + : StringView(word.getTerm(), m_tail.getTerm() ) + ; + } + + case Class::EndOfLine: + return strFindEol(m_tail); + + case Class::NewLine: + return strFindNl(m_tail); + + default: + BX_ASSERT(false, "Bug, _class can't be %d!", _class); + BX_UNREACHABLE; + } + } + + StringView Scanner::moveBy(int32_t _bytes) + { + const int32_t len = m_tail.getLength(); + _bytes = clamp(_bytes, len - m_input.getLength(), len); + + if (BX_LIKELY(0 < _bytes) ) + { + const StringView result(m_tail.getPtr(), m_tail.getPtr() + _bytes); + + m_tail.set(result.getTerm(), m_tail.getTerm() ); + m_line += countLines(result); + + return result; + } + + const StringView result(m_tail.getPtr() + _bytes, m_tail.getPtr() ); + + m_tail.set(result.getPtr(), m_tail.getTerm() ); + m_line -= countLines(result); + + return result; + } + + uint32_t Scanner::countLines(const StringView& _str) + { + uint32_t line = 0; + + StringView str = strFindEol(_str); + + while (!str.isEmpty() ) + { + ++line; + str = strFindEol(strFindNl(str) ); + } + + return line; + } + + StringView LineReader::next() + { + if (!m_scanner.isDone() ) + { + ++m_line; + + const StringView cursor = m_scanner.getCursor(); + m_scanner.acceptUntil(Scanner::Class::EndOfLine); + + const StringView line = m_scanner.between(cursor); + m_scanner.accept(Scanner::Class::NewLine); + + // A line terminator is `\n` or `\r\n`, and `Class::EndOfLine` already + // excludes it. Only malformed input (`\r` at the end of input, or + // `\r\r\n`) can leave a trailing `\r` behind. + return strRTrim(line, "\r"); + } + + return m_scanner.getCursor(); + } + +} // namespace bx diff --git a/src/settings.cpp b/src/settings.cpp index 575ccd8..b8fc821 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -4,209 +4,496 @@ */ #include - -namespace -{ -#define INI_MALLOC(_ctx, _size) (bx::alloc(reinterpret_cast(_ctx), _size) ) -#define INI_FREE(_ctx, _ptr) (bx::free(reinterpret_cast(_ctx), _ptr) ) -#define INI_MEMCPY(_dst, _src, _count) (bx::memCopy(_dst, _src, _count) ) -#define INI_STRLEN(_str) (bx::strLen(_str) ) -#define INI_STRNICMP(_s1, _s2, _len) (bx::strCmpI(_s1, _s2, _len) ) - -#define INI_IMPLEMENTATION - -BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wunused-function"); - -BX_PRAGMA_DIAGNOSTIC_PUSH(); -BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wsign-compare"); -#include -BX_PRAGMA_DIAGNOSTIC_POP(); -} +#include +#include namespace bx { - -Settings::Settings(AllocatorI* _allocator, const void* _data, uint32_t _len) - : m_allocator(_allocator) - , m_ini(NULL) -{ - load(_data, _len); -} - -#define INI_T(_ptr) reinterpret_cast(_ptr) - -Settings::~Settings() -{ - ini_destroy(INI_T(m_ini) ); -} - -void Settings::clear() -{ - load(NULL, 0); -} - -void Settings::load(const void* _data, uint32_t _len) -{ - if (NULL != m_ini) + static StringView settingsAlloc(AllocatorI* _allocator, const StringView& _str) { - ini_destroy(INI_T(m_ini) ); - } + const int32_t len = _str.getLength(); - if (NULL == _data) - { - m_ini = ini_create(m_allocator); - } - else - { - m_ini = ini_load( (const char*)_data, _len, m_allocator); - } -} - -StringView Settings::get(const StringView& _name) const -{ - ini_t* ini = INI_T(m_ini); - - FilePath uri(_name); - const StringView path(strTrim(uri.getPath(), "/") ); - const StringView& fileName(uri.getFileName() ); - int32_t section = INI_GLOBAL_SECTION; - - if (!path.isEmpty() ) - { - section = ini_find_section(ini, path.getPtr(), path.getLength() ); - if (INI_NOT_FOUND == section) + if (0 == len) { - section = INI_GLOBAL_SECTION; + return StringView(); + } + + char* ptr = (char*)alloc(_allocator, len); + memCopy(ptr, _str.getPtr(), len); + + return StringView(ptr, len); + } + + static void settingsFree(AllocatorI* _allocator, StringView& _str) + { + if (!_str.isEmpty() ) + { + free(_allocator, const_cast(_str.getPtr() ) ); + } + + _str.clear(); + } + + static void settingsAssign(AllocatorI* _allocator, StringView& _dst, const StringView& _src) + { + const StringView next = settingsAlloc(_allocator, _src); + settingsFree(_allocator, _dst); + _dst = next; + } + + struct Settings::Ini + { + static constexpr int32_t kGlobal = 0; + static constexpr int32_t kInvalid = -1; + + struct Property + { + StringView name; + StringView value; + }; + + struct Section + { + StringView name; + Property* props; + uint32_t count; + uint32_t capacity; + }; + + void init(AllocatorI* _allocator) + { + m_allocator = _allocator; + m_sections = NULL; + m_count = 0; + m_capacity = 0; + + clear(); + } + + void shutdown() + { + reset(); + + if (NULL != m_sections) + { + free(m_allocator, m_sections); + m_sections = NULL; + } + + m_capacity = 0; + } + + void clear() + { + reset(); + + addSection(StringView() ); + } + + void load(const StringView& _data) + { + clear(); + + Scanner scanner(_data); + int32_t section = kGlobal; + + while (!scanner.isDone() ) + { + scanner.accept(Scanner::Class::Space); + + if (scanner.isDone() ) + { + break; + } + + Scanner line(scanner.acceptUntil(Scanner::Class::EndOfLine) ); + + // Line comment. + if (!line.accept(';').isEmpty() ) + { + continue; + } + + // [section] header. + if (!line.accept('[').isEmpty() ) + { + line.accept(Scanner::Class::Space); + const StringView name = strRTrimSpace(line.acceptUntil("]") ); + + if (!line.accept(']').isEmpty() + && !name.isEmpty() ) + { + const int32_t existing = findSection(name); + section = kInvalid == existing ? addSection(name) : existing; + } + + continue; + } + + // name = value property. + const StringView name = strRTrimSpace(line.acceptUntil("=") ); + + if (line.accept('=').isEmpty() + || name.isEmpty() ) + { + continue; + } + + line.accept(Scanner::Class::Space); + setProperty(section, name, strRTrimSpace(line.acceptAll() ) ); + } + } + + int32_t find(const StringView& _name, StringView& _propertyName) const + { + const FilePath uri(_name); + const StringView path(strTrim(uri.getPath(), "/") ); + + _propertyName = uri.getFileName(); + + if (path.isEmpty() ) + { + return kGlobal; + } + + const int32_t section = findSection(path); + + return kInvalid == section ? kGlobal : section; + } + + int32_t findSection(const StringView& _name) const + { + for (uint32_t ss = 0; ss < m_count; ++ss) + { + const StringView& name = m_sections[ss].name; + + if (isEqual(name, _name, false) ) + { + return int32_t(ss); + } + } + + return kInvalid; + } + + int32_t addSection(const StringView& _name) + { + if (m_count == m_capacity) + { + m_capacity = 0 == m_capacity ? 8 : m_capacity * 2; + m_sections = (Section*)realloc(m_allocator, m_sections, m_capacity * sizeof(Section) ); + } + + Section& section = m_sections[m_count]; + section.name = settingsAlloc(m_allocator, _name); + section.props = NULL; + section.count = 0; + section.capacity = 0; + + return int32_t(m_count++); + } + + void removeSection(int32_t _section) + { + if (kGlobal >= _section + || uint32_t(_section) >= m_count) + { + return; + } + + freeSection(m_sections[_section]); + + for (uint32_t ss = uint32_t(_section); ss+1 < m_count; ++ss) + { + m_sections[ss] = m_sections[ss+1]; + } + + --m_count; + } + + int32_t findProperty(int32_t _section, const StringView& _name) const + { + if (0 > _section + || uint32_t(_section) >= m_count) + { + return kInvalid; + } + + const Section& section = m_sections[_section]; + + for (uint32_t pp = 0; pp < section.count; ++pp) + { + const StringView& name = section.props[pp].name; + + if (isEqual(name, _name, false) ) + { + return int32_t(pp); + } + } + + return kInvalid; + } + + void setProperty(int32_t _section, const StringView& _name, const StringView& _value) + { + if (0 > _section + || uint32_t(_section) >= m_count) + { + return; + } + + const int32_t property = findProperty(_section, _name); + + Section& section = m_sections[_section]; + + if (kInvalid != property) + { + settingsAssign(m_allocator, section.props[property].value, _value); + return; + } + + if (section.count == section.capacity) + { + section.capacity = 0 == section.capacity ? 8 : section.capacity * 2; + section.props = (Property*)realloc(m_allocator, section.props, section.capacity * sizeof(Property) ); + } + + Property& prop = section.props[section.count]; + prop.name = settingsAlloc(m_allocator, _name); + prop.value = settingsAlloc(m_allocator, _value); + + ++section.count; + } + + void removeProperty(int32_t _section, int32_t _property) + { + if (0 > _section + || uint32_t(_section) >= m_count) + { + return; + } + + Section& section = m_sections[_section]; + + if (0 > _property + || uint32_t(_property) >= section.count) + { + return; + } + + settingsFree(m_allocator, section.props[_property].name); + settingsFree(m_allocator, section.props[_property].value); + + for (uint32_t pp = uint32_t(_property); pp+1 < section.count; ++pp) + { + section.props[pp] = section.props[pp+1]; + } + + --section.count; + } + + StringView getPropertyValue(int32_t _section, int32_t _property) const + { + if (0 > _section + || uint32_t(_section) >= m_count) + { + return StringView(); + } + + const Section& section = m_sections[_section]; + + return (0 <= _property && uint32_t(_property) < section.count) + ? section.props[_property].value + : StringView() + ; + } + + uint32_t getPropertyCount(int32_t _section) const + { + return (0 <= _section && uint32_t(_section) < m_count) + ? m_sections[_section].count + : 0 + ; + } + + int32_t save(WriterI* _writer, Error* _err) const + { + int32_t total = 0; + + for (uint32_t ss = 0; ss < m_count; ++ss) + { + const Section& section = m_sections[ss]; + + if (!section.name.isEmpty() ) + { + total += bx::write(_writer, "[", _err); + total += bx::write(_writer, section.name, _err); + total += bx::write(_writer, "]\n", _err); + } + + for (uint32_t pp = 0; pp < section.count; ++pp) + { + total += bx::write(_writer, section.props[pp].name, _err); + total += bx::write(_writer, "=", _err); + total += bx::write(_writer, section.props[pp].value, _err); + total += bx::write(_writer, "\n", _err); + } + + if (0 != total) + { + total += bx::write(_writer, "\n", _err); + } + } + + return total; + } + + void freeSection(Section& _section) + { + for (uint32_t pp = 0; pp < _section.count; ++pp) + { + settingsFree(m_allocator, _section.props[pp].name); + settingsFree(m_allocator, _section.props[pp].value); + } + + if (NULL != _section.props) + { + free(m_allocator, _section.props); + } + + settingsFree(m_allocator, _section.name); + + _section.props = NULL; + _section.count = 0; + _section.capacity = 0; + } + + void reset() + { + for (uint32_t ss = 0; ss < m_count; ++ss) + { + freeSection(m_sections[ss]); + } + + m_count = 0; + } + + AllocatorI* m_allocator; + Section* m_sections; + uint32_t m_count; + uint32_t m_capacity; + }; + + Settings::Settings(AllocatorI* _allocator, const void* _data, uint32_t _len) + : m_allocator(_allocator) + , m_ini(BX_NEW(_allocator, Ini) ) + { + m_ini->init(_allocator); + load(_data, _len); + } + + Settings::~Settings() + { + m_ini->shutdown(); + deleteObject(m_allocator, m_ini); + } + + void Settings::clear() + { + m_ini->clear(); + } + + void Settings::load(const void* _data, uint32_t _len) + { + if (NULL == _data + || 0 == _len) + { + m_ini->clear(); + return; + } + + m_ini->load(StringView( (const char*)_data, int32_t(_len) ) ); + } + + StringView Settings::get(const StringView& _name) const + { + StringView propertyName; + const int32_t section = m_ini->find(_name, propertyName); + + return m_ini->getPropertyValue(section, m_ini->findProperty(section, propertyName) ); + } + + void Settings::set(const StringView& _name, const StringView& _value) + { + const FilePath uri(_name); + const StringView path(strTrim(uri.getPath(), "/") ); + const StringView& fileName(uri.getFileName() ); + + int32_t section = Ini::kGlobal; + + if (!path.isEmpty() ) + { + section = m_ini->findSection(path); + + if (Ini::kInvalid == section) + { + section = m_ini->addSection(path); + } + } + + m_ini->setProperty(section, fileName, _value); + } + + void Settings::remove(const StringView& _name) const + { + StringView propertyName; + const int32_t section = m_ini->find(_name, propertyName); + + const int32_t property = m_ini->findProperty(section, propertyName); + + if (Ini::kInvalid == property) + { + return; + } + + m_ini->removeProperty(section, property); + + if (Ini::kGlobal != section + && 0 == m_ini->getPropertyCount(section) ) + { + m_ini->removeSection(section); } } - int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() ); - if (INI_NOT_FOUND == property) + int32_t Settings::read(ReaderSeekerI* _reader, Error* _err) { - return StringView(); + int32_t size = int32_t(getRemain(_reader) ); + + void* data = bx::alloc(m_allocator, size); + + int32_t total = bx::read(_reader, data, size, _err); + load(data, size); + + bx::free(m_allocator, data); + + return total; } - return ini_property_value(ini, section, property); -} - -void Settings::set(const StringView& _name, const StringView& _value) -{ - ini_t* ini = INI_T(m_ini); - - FilePath uri(_name); - const StringView path(strTrim(uri.getPath(), "/") ); - const StringView& fileName(uri.getFileName() ); - - int32_t section = INI_GLOBAL_SECTION; - - if (!path.isEmpty() ) + int32_t Settings::write(WriterI* _writer, Error* _err) const { - section = ini_find_section(ini, path.getPtr(), path.getLength() ); - if (INI_NOT_FOUND == section) - { - section = ini_section_add(ini, path.getPtr(), path.getLength() ); - } + return m_ini->save(_writer, _err); } - int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() ); - if (INI_NOT_FOUND == property) + int32_t read(ReaderSeekerI* _reader, Settings& _settings, Error* _err) { - ini_property_add( - ini - , section - , fileName.getPtr() - , fileName.getLength() - , _value.getPtr() - , _value.getLength() - ); - } - else - { - ini_property_value_set( - ini - , section - , property - , _value.getPtr() - , _value.getLength() - ); - } -} - -void Settings::remove(const StringView& _name) const -{ - ini_t* ini = INI_T(m_ini); - - FilePath uri(_name); - const StringView path = strTrim(uri.getPath(), "/"); - const StringView& fileName = uri.getFileName(); - - int32_t section = INI_GLOBAL_SECTION; - - if (!path.isEmpty() ) - { - section = ini_find_section(ini, path.getPtr(), path.getLength() ); - if (INI_NOT_FOUND == section) - { - section = INI_GLOBAL_SECTION; - } + BX_ERROR_SCOPE(_err); + return _settings.read(_reader, _err); } - int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() ); - if (INI_NOT_FOUND == property) + int32_t write(WriterI* _writer, const Settings& _settings, Error* _err) { - return; + BX_ERROR_SCOPE(_err); + return _settings.write(_writer, _err); } - ini_property_remove(ini, section, property); - - if (INI_GLOBAL_SECTION != section - && 0 == ini_property_count(ini, section) ) - { - ini_section_remove(ini, section); - } -} - -int32_t Settings::read(ReaderSeekerI* _reader, Error* _err) -{ - int32_t size = int32_t(getRemain(_reader) ); - - void* data = bx::alloc(m_allocator, size); - - int32_t total = bx::read(_reader, data, size, _err); - load(data, size); - - bx::free(m_allocator, data); - - return total; -} - -int32_t Settings::write(WriterI* _writer, Error* _err) const -{ - ini_t* ini = INI_T(m_ini); - - int32_t size = ini_save(ini, NULL, 0); - void* data = bx::alloc(m_allocator, size); - - ini_save(ini, (char*)data, size); - int32_t total = bx::write(_writer, data, size-1, _err); - - bx::free(m_allocator, data); - - return total; -} - -#undef INI_T - -int32_t read(ReaderSeekerI* _reader, Settings& _settings, Error* _err) -{ - BX_ERROR_SCOPE(_err); - return _settings.read(_reader, _err); -} - -int32_t write(WriterI* _writer, const Settings& _settings, Error* _err) -{ - BX_ERROR_SCOPE(_err); - return _settings.write(_writer, _err); -} - } // namespace bx diff --git a/src/string.cpp b/src/string.cpp index e5b27d7..b27d845 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -10,65 +10,6 @@ namespace bx { - inline bool isInRange(char _ch, char _from, char _to) - { - return unsigned(_ch - _from) <= unsigned(_to-_from); - } - - bool isSpace(char _ch) - { - return ' ' == _ch // Space. - || '\t' == _ch // Horizontal tab. - || '\n' == _ch // Line feed / new line. - || '\r' == _ch // Carriage return. - || '\v' == _ch // Vertical tab. - || '\f' == _ch // Form feed / new page. - ; - } - - bool isUpper(char _ch) - { - return isInRange(_ch, 'A', 'Z'); - } - - bool isLower(char _ch) - { - return isInRange(_ch, 'a', 'z'); - } - - bool isAlpha(char _ch) - { - return isLower(_ch) || isUpper(_ch); - } - - bool isNumeric(char _ch) - { - return isInRange(_ch, '0', '9'); - } - - bool isAlphaNum(char _ch) - { - return false - || isAlpha(_ch) - || isNumeric(_ch) - ; - } - - bool isHexNum(char _ch) - { - return false - || isInRange(toLower(_ch), 'a', 'f') - || isNumeric(_ch) - ; - } - - bool isPrint(char _ch) - { - return isInRange(_ch, ' ', '~'); - } - - typedef bool (*CharTestFn)(char _ch); - template inline bool isCharTest(const StringView& _str) { @@ -125,11 +66,6 @@ namespace bx return isCharTest(_str); } - char toLower(char _ch) - { - return _ch + (isUpper(_ch) ? 0x20 : 0); - } - void toLowerUnsafe(char* _inOutStr, int32_t _len) { for (int32_t ii = 0; ii < _len; ++ii) @@ -144,11 +80,6 @@ namespace bx toLowerUnsafe(_inOutStr, len); } - char toUpper(char _ch) - { - return _ch - (isLower(_ch) ? 0x20 : 0); - } - void toUpperUnsafe(char* _inOutStr, int32_t _len) { for (int32_t ii = 0; ii < _len; ++ii) @@ -165,7 +96,7 @@ namespace bx typedef char (*CharFn)(char _ch); - inline constexpr char toNoop(char _ch) + inline BX_CONSTEXPR_FUNC char toNoop(char _ch) { return _ch; } @@ -560,8 +491,6 @@ namespace bx ); } - constexpr uint32_t kFindStep = 1024; - StringView strFindNl(const StringView& _str) { StringView str(_str); @@ -578,23 +507,19 @@ namespace bx StringView strFindEol(const StringView& _str) { - StringView str(_str); + const StringView eol = strFind(_str, '\n'); - for (; str.getPtr() != _str.getTerm() - ; str = StringView(min(str.getPtr() + kFindStep, _str.getTerm() ), min(str.getPtr() + kFindStep*2, _str.getTerm() ) ) - ) + if (!eol.isEmpty() ) { - StringView eol = strFind(str, "\r\n"); - if (!eol.isEmpty() ) + const char* ptr = eol.getPtr(); + + if (ptr != _str.getPtr() + && '\r' == ptr[-1]) { - return StringView(eol.getPtr(), _str.getTerm() ); + --ptr; } - eol = strFind(str, '\n'); - if (!eol.isEmpty() ) - { - return StringView(eol.getPtr(), _str.getTerm() ); - } + return StringView(ptr, _str.getTerm() ); } return StringView(_str.getTerm(), _str.getTerm() ); diff --git a/src/url.cpp b/src/url.cpp index cc333a4..a132aca 100644 --- a/src/url.cpp +++ b/src/url.cpp @@ -3,10 +3,36 @@ * License: https://github.com/bkaradzic/bnet#license-bsd-2-clause */ +#include #include namespace bx { + static bool isNotSlash(char _ch) + { + return '/' != _ch; + } + + static bool isNotColon(char _ch) + { + return ':' != _ch; + } + + static bool isNotQuery(char _ch) + { + return '?' != _ch; + } + + static bool isNotFragment(char _ch) + { + return '#' != _ch; + } + + static bool isNotQueryOrFragment(char _ch) + { + return isNotQuery(_ch) && isNotFragment(_ch); + } + UrlView::UrlView() { } @@ -23,22 +49,16 @@ namespace bx { clear(); - const char* term = _url.getTerm(); - StringView schemeEnd = strFind(_url, "://"); - const char* hostStart = !schemeEnd.isEmpty() ? schemeEnd.getTerm() : _url.getPtr(); - StringView path = strFind(StringView(hostStart, term), '/'); + Scanner scanner(_url); - if (schemeEnd.isEmpty() - && path.isEmpty() ) + const StringView schemeBegin = scanner.getCursor(); + scanner.acceptUntil("://"); + const StringView scheme = scanner.between(schemeBegin); + + const bool hasScheme = !scanner.accept("://").isEmpty(); + + if (hasScheme) { - return false; - } - - if (!schemeEnd.isEmpty() - && (path.isEmpty() || path.getPtr() > schemeEnd.getPtr() ) ) - { - const StringView scheme(_url.getPtr(), schemeEnd.getPtr() ); - if (!isAlpha(scheme) ) { return false; @@ -47,65 +67,72 @@ namespace bx m_tokens[Scheme].set(scheme); } - if (!path.isEmpty() ) - { - path.set(path.getPtr(), term); - const StringView query = strFind(path, '?'); - const StringView fragment = strFind(path, '#'); + const StringView authorityBegin = scanner.getCursor(); + scanner.acceptWhile(isNotSlash); + const StringView authority = scanner.between(authorityBegin); - if (!fragment.isEmpty() - && fragment.getPtr() < query.getPtr() ) + const bool hasPath = !scanner.peek('/').isEmpty(); + + if (!hasScheme + && !hasPath) + { + return false; + } + + if (hasPath) + { + const StringView pathBegin = scanner.getCursor(); + scanner.acceptWhile(isNotQueryOrFragment); + m_tokens[Path].set(scanner.between(pathBegin) ); + + if (!scanner.accept('?').isEmpty() ) + { + const StringView queryBegin = scanner.getCursor(); + scanner.acceptWhile(isNotFragment); + m_tokens[Query].set(scanner.between(queryBegin) ); + } + + if (!scanner.accept('#').isEmpty() ) + { + const StringView fragmentBegin = scanner.getCursor(); + scanner.acceptWhile(isNotQuery); + m_tokens[Fragment].set(scanner.between(fragmentBegin) ); + } + + // Anything left over is a query following a fragment. + if (!scanner.isDone() ) { return false; } - - m_tokens[Path].set(path.getPtr() - , !query.isEmpty() ? query.getPtr() - : !fragment.isEmpty() ? fragment.getPtr() - : term - ); - - if (!query.isEmpty() ) - { - m_tokens[Query].set(query.getPtr()+1 - , !fragment.isEmpty() ? fragment.getPtr() - : term - ); - } - - if (!fragment.isEmpty() ) - { - m_tokens[Fragment].set(fragment.getPtr()+1, term); - } - - term = path.getPtr(); } - const StringView userPassEnd = strFind(StringView(hostStart, term), '@'); - const char* userPassStart = !userPassEnd.isEmpty() ? hostStart : NULL; - hostStart = !userPassEnd.isEmpty() ? userPassEnd.getPtr()+1 : hostStart; - const StringView portStart = strFind(StringView(hostStart, term), ':'); + Scanner authorityScanner(authority); - m_tokens[Host].set(hostStart, !portStart.isEmpty() ? portStart.getPtr() : term); + const StringView userInfoBegin = authorityScanner.getCursor(); + authorityScanner.acceptUntil("@"); + const StringView userInfo = authorityScanner.between(userInfoBegin); - if (!portStart.isEmpty()) + if (!authorityScanner.accept('@').isEmpty() ) { - m_tokens[Port].set(portStart.getPtr()+1, term); + Scanner userInfoScanner(userInfo); + + const StringView userNameBegin = userInfoScanner.getCursor(); + userInfoScanner.acceptWhile(isNotColon); + m_tokens[UserName].set(userInfoScanner.between(userNameBegin) ); + + if (!userInfoScanner.accept(':').isEmpty() ) + { + m_tokens[Password].set(userInfoScanner.acceptAll() ); + } } - if (NULL != userPassStart) + const StringView hostBegin = authorityScanner.getCursor(); + authorityScanner.acceptWhile(isNotColon); + m_tokens[Host].set(authorityScanner.between(hostBegin) ); + + if (!authorityScanner.accept(':').isEmpty() ) { - StringView passStart = strFind(StringView(userPassStart, userPassEnd.getPtr() ), ':'); - - m_tokens[UserName].set(userPassStart - , !passStart.isEmpty() ? passStart.getPtr() - : userPassEnd.getPtr() - ); - - if (!passStart.isEmpty() ) - { - m_tokens[Password].set(passStart.getPtr()+1, userPassEnd.getPtr() ); - } + m_tokens[Port].set(authorityScanner.acceptAll() ); } return true; diff --git a/tests/filepath_test.cpp b/tests/filepath_test.cpp index b5873cb..a3fe88c 100644 --- a/tests/filepath_test.cpp +++ b/tests/filepath_test.cpp @@ -64,6 +64,15 @@ FilePathTest s_filePathTest[] = {"abc\\/../..\\/././../def", "../../def"}, {"\\abc/def\\../..\\..", "/"}, + + // Drive letter + {"c:", "C:"}, + {"c:/", "C:/"}, + {"c:abc", "C:/abc"}, + {"c:/abc/../def", "C:/def"}, + {"c:/..", "C:/"}, + {"c:/../..", "C:/"}, + {"c:\\abc\\..\\def", "C:/def"}, }; struct FilePathSplit @@ -118,6 +127,25 @@ TEST_CASE("FilePath", "[filepath][string]") }; } +TEST_CASE("FilePath sub-view", "[filepath]") +{ + const char buffer[] = "C:/abc"; + + bx::FilePath fp; + fp.set(bx::StringView(buffer, 2) ); + REQUIRE(0 == bx::strCmp("C:", fp) ); + + const char buffer2[] = "ab/cd"; + fp.set(bx::StringView(buffer2, 2) ); + REQUIRE(0 == bx::strCmp("ab", fp) ); + + fp.set(bx::StringView(buffer2, 3) ); + REQUIRE(0 == bx::strCmp("ab/", fp) ); + + fp.set(bx::StringView(buffer2, 0) ); + REQUIRE(0 == bx::strCmp(".", fp) ); +} + TEST_CASE("FilePath temp", "[filepath]") { bx::FilePath tmp(bx::Dir::Temp); diff --git a/tests/scanner_test.cpp b/tests/scanner_test.cpp new file mode 100644 index 0000000..2130099 --- /dev/null +++ b/tests/scanner_test.cpp @@ -0,0 +1,351 @@ +/* + * Copyright 2010-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#include "test.h" + +#include + +namespace bx +{ + uint32_t countLines(const StringView& _str) + { + Scanner scanner(_str); + scanner.acceptAll(); + + return scanner.getLine(); + } + + void printLines(const StringView& _str) + { + Scanner scanner(_str); + + while (!scanner.isDone() ) + { + StringView sv = scanner.acceptUntil(Scanner::Class::EndOfLine); + BX_TRACE("%d: '%S'", scanner.getLine(), &sv); + + scanner.acceptUntil(Scanner::Class::NewLine); + } + } + +} // namespace bx + +TEST_CASE("Scanner", "[scanner]") +{ + const bx::StringLiteral input = + "test (foo) bar [baz]xyz\n" + " // test\n" + "tset (foo) bar [baz]xyz\n" + " \n" + "void main()\n" + "{\n" + "\treturn;\n" + "}\n" + ; + const uint32_t numLines = bx::countLines(input); + + bx::printLines(input); + + bx::Scanner scanner(input); + + REQUIRE(!scanner.isDone() ); + + bx::StringView start = scanner.accept(); + + REQUIRE("t" == start); + REQUIRE("est " == scanner.acceptUntil("(") ); + REQUIRE("(" == scanner.accept('(') ); + + REQUIRE(!scanner.isDone() ); + REQUIRE("foo" == scanner.accept(bx::Scanner::Class::Identifier) ); + REQUIRE(")" == scanner.accept(')') ); + + REQUIRE(!scanner.isDone() ); + REQUIRE(" " == scanner.accept(' ') ); + + REQUIRE(!scanner.isDone() ); + REQUIRE("b" == scanner.accept('r', 'b', 'a') ); + REQUIRE("a" == scanner.accept('b', 'a', 'r') ); + REQUIRE("r" == scanner.accept('a', 'r', 'b') ); + + REQUIRE(!scanner.isDone() ); + REQUIRE(" " == scanner.accept(' ') ); + + REQUIRE(!scanner.isDone() ); + REQUIRE("[baz]" == scanner.acceptWhile("abz[]") ); + + REQUIRE(!scanner.isDone() ); + REQUIRE("xyz" == scanner.accept("xyz") ); + + REQUIRE(0 == scanner.getLine() ); + REQUIRE("\n " == scanner.acceptUntil(bx::Scanner::Class::Space) ); + REQUIRE(1 == scanner.getLine() ); + + scanner.acceptAll(); + REQUIRE(scanner.accept().isEmpty() ); + + REQUIRE(numLines == scanner.getLine() ); + + REQUIRE(scanner.isDone() ); + + REQUIRE(scanner.seek(start) ); + REQUIRE(0 == scanner.getLine() ); + REQUIRE(!scanner.isDone() ); + + REQUIRE(scanner.seek(INT32_MAX) ); + REQUIRE(numLines == scanner.getLine() ); + REQUIRE(scanner.isDone() ); + + REQUIRE(scanner.seek(INT32_MIN) ); + REQUIRE(0 == scanner.getLine() ); + REQUIRE(!scanner.isDone() ); +} + +TEST_CASE("Scanner.class accept", "[scanner]") +{ + { + bx::Scanner sc(bx::StringView("abc def") ); + REQUIRE("abc" == sc.accept(bx::Scanner::Class::Identifier) ); + REQUIRE(" def" == sc.acceptAll() ); + } + + { + bx::Scanner sc(bx::StringView(" abc def") ); + REQUIRE(" " == sc.accept(bx::Scanner::Class::Space) ); + REQUIRE(!sc.isDone() ); + REQUIRE("abc def" == sc.acceptAll() ); + } + + { + bx::Scanner sc(bx::StringView("abc") ); + REQUIRE(sc.accept(bx::Scanner::Class::Space).isEmpty() ); + REQUIRE("abc" == sc.acceptAll() ); + } + + { + bx::Scanner sc(bx::StringView("abc def") ); + REQUIRE("abc" == sc.accept(bx::Scanner::Class::NonSpace) ); + REQUIRE(" def" == sc.acceptAll() ); + } + + { + bx::Scanner sc(bx::StringView("abc\ndef") ); + REQUIRE("abc" == sc.accept(bx::Scanner::Class::EndOfLine) ); + REQUIRE("\ndef" == sc.acceptAll() ); + } + + { + bx::Scanner a(bx::StringView(" abc") ); + bx::Scanner b(bx::StringView(" abc") ); + REQUIRE(" " == a.accept(bx::Scanner::Class::Space) ); + REQUIRE(" " == b.acceptUntil(bx::Scanner::Class::Space) ); + } +} + +TEST_CASE("Scanner.peek", "[scanner]") +{ + bx::Scanner sc(bx::StringView("abc def") ); + + REQUIRE("a" == sc.peek() ); + REQUIRE("a" == sc.peek('a') ); + REQUIRE(sc.peek('x').isEmpty() ); + REQUIRE("abc" == sc.peek(bx::StringView("abc") ) ); + REQUIRE(sc.peek(bx::StringView("abx") ).isEmpty() ); + REQUIRE("abc" == sc.peek(bx::Scanner::Class::Identifier) ); + + REQUIRE("a" == sc.accept() ); + REQUIRE("bc" == sc.peek(bx::Scanner::Class::Identifier) ); + REQUIRE("bc" == sc.accept(bx::Scanner::Class::Identifier) ); + + sc.acceptAll(); + REQUIRE(sc.isDone() ); + REQUIRE(sc.peek().isEmpty() ); + REQUIRE(sc.peek(bx::Scanner::Class::Identifier).isEmpty() ); +} + +TEST_CASE("Scanner.getCursor between", "[scanner]") +{ + bx::Scanner sc(bx::StringView("abc def") ); + + const bx::StringView mark = sc.getCursor(); + REQUIRE(mark.isEmpty() ); + + sc.accept(bx::Scanner::Class::Identifier); + REQUIRE("abc" == sc.between(mark) ); + + const bx::StringView here = sc.getCursor(); + sc.acceptAll(); + REQUIRE(" def" == sc.between(here) ); + REQUIRE(sc.seek(INT32_MIN) ); + REQUIRE("abc" == sc.between(here) ); +} + +TEST_CASE("Scanner.getCursor lines", "[scanner]") +{ + bx::Scanner sc(bx::StringView("abc\ndefgh\nij") ); + + sc.accept(bx::Scanner::Class::Identifier); + sc.accept('\n'); + sc.accept('d'); + sc.accept('e'); + + REQUIRE("de" == sc.between(sc.getCursor(bx::Scanner::Cursor::LineStart) ) ); + REQUIRE("fgh" == sc.between(sc.getCursor(bx::Scanner::Cursor::LineEnd) ) ); +} + +TEST_CASE("Scanner.getColumn", "[scanner]") +{ + bx::Scanner sc(bx::StringView("abc\ndef") ); + + REQUIRE(1 == sc.getColumn() ); + sc.accept(); + REQUIRE(2 == sc.getColumn() ); + sc.accept(bx::Scanner::Class::Identifier); + REQUIRE(4 == sc.getColumn() ); + + sc.accept('\n'); + REQUIRE(1 == sc.getLine() ); + REQUIRE(1 == sc.getColumn() ); + sc.accept(); + REQUIRE(2 == sc.getColumn() ); +} + +TEST_CASE("Scanner.predicate accept", "[scanner]") +{ + bx::Scanner sc(bx::StringView("123abc xyz") ); + + REQUIRE("1" == sc.accept(bx::isNumeric) ); + REQUIRE(sc.accept(bx::isAlpha).isEmpty() ); + REQUIRE("2" == sc.accept(bx::isNumeric) ); + + REQUIRE("3" == sc.acceptWhile(bx::isNumeric) ); + REQUIRE("abc" == sc.acceptWhile(bx::isAlpha) ); + + REQUIRE(sc.acceptWhile(bx::isNumeric).isEmpty() ); + REQUIRE(!sc.isDone() ); + + REQUIRE(" " == sc.acceptWhile(bx::isSpace) ); + REQUIRE("xyz" == sc.acceptWhile(bx::isAlphaNum) ); + REQUIRE(sc.isDone() ); +} + +TEST_CASE("Scanner.reset", "[scanner]") +{ + bx::Scanner sc(bx::StringView("abc\ndef\n") ); + + sc.acceptAll(); + REQUIRE(sc.isDone() ); + REQUIRE(2 == sc.getLine() ); + + sc.reset(); + REQUIRE(!sc.isDone() ); + REQUIRE(0 == sc.getLine() ); + REQUIRE(1 == sc.getColumn() ); + REQUIRE("abc" == sc.accept(bx::Scanner::Class::Identifier) ); + + // Reset from a partially consumed state. + sc.reset(); + REQUIRE("abc\ndef\n" == sc.acceptAll() ); +} + +TEST_CASE("Scanner.EndOfLine mixed", "[scanner]") +{ + // End of line must be the *first* terminator, even when a later line + // uses \r\n and an earlier one uses a lone \n. + bx::Scanner sc(bx::StringView("a\nb\r\n") ); + + REQUIRE("a" == sc.acceptUntil(bx::Scanner::Class::EndOfLine) ); + REQUIRE("\n" == sc.accept(bx::Scanner::Class::NewLine) ); + REQUIRE("b" == sc.acceptUntil(bx::Scanner::Class::EndOfLine) ); + REQUIRE("\r\n" == sc.accept(bx::Scanner::Class::NewLine) ); + REQUIRE(sc.isDone() ); + + REQUIRE(2 == bx::countLines(bx::StringView("a\nb\r\n") ) ); + REQUIRE(4 == bx::countLines(bx::StringView("a\nb\r\nc\rd\ne\r\n") ) ); +} + +TEST_CASE("LineReader", "[scanner][string]") +{ + { + bx::LineReader lr(bx::StringView("") ); + REQUIRE(lr.isDone() ); + REQUIRE(lr.next().isEmpty() ); + REQUIRE(0 == lr.getLine() ); + } + + { + bx::LineReader lr(bx::StringView("abc") ); + REQUIRE(!lr.isDone() ); + REQUIRE("abc" == lr.next() ); + REQUIRE(1 == lr.getLine() ); + REQUIRE(lr.isDone() ); + } + + { + // \n, \r\n and empty lines interleaved. + bx::LineReader lr(bx::StringView("a\r\nb\n\nc\r\n\r\nd") ); + + REQUIRE("a" == lr.next() ); + REQUIRE("b" == lr.next() ); + REQUIRE(lr.next().isEmpty() ); + REQUIRE("c" == lr.next() ); + REQUIRE(lr.next().isEmpty() ); + REQUIRE("d" == lr.next() ); + REQUIRE(6 == lr.getLine() ); + REQUIRE(lr.isDone() ); + + // next() past the end is a no-op. + REQUIRE(lr.next().isEmpty() ); + REQUIRE(6 == lr.getLine() ); + } + + { + // Trailing \r without \n is trimmed, interior \r is not a line break. + bx::LineReader lr(bx::StringView("a\rb\nc\r") ); + + REQUIRE("a\rb" == lr.next() ); + REQUIRE("c" == lr.next() ); + REQUIRE(lr.isDone() ); + } + + { + // Only \r and \n are trimmed, other trailing whitespace is kept. + bx::LineReader lr(bx::StringView("a \r\n\tb\t\n") ); + + REQUIRE("a " == lr.next() ); + REQUIRE("\tb\t" == lr.next() ); + REQUIRE(lr.isDone() ); + } + + { + bx::LineReader lr(bx::StringView("a\nb\n") ); + + REQUIRE("a" == lr.next() ); + REQUIRE("b" == lr.next() ); + REQUIRE(lr.isDone() ); + REQUIRE(2 == lr.getLine() ); + + lr.reset(); + + REQUIRE(!lr.isDone() ); + REQUIRE(0 == lr.getLine() ); + REQUIRE("a" == lr.next() ); + REQUIRE("b" == lr.next() ); + REQUIRE(2 == lr.getLine() ); + } + + { + // Returned views must point into the input, not at a temporary. + const bx::StringView input("abc\ndef\n"); + + bx::LineReader lr(input); + + const bx::StringView first = lr.next(); + REQUIRE(first.getPtr() == input.getPtr() ); + + const bx::StringView second = lr.next(); + REQUIRE(second.getPtr() == input.getPtr() + 4); + } +} diff --git a/tests/settings_test.cpp b/tests/settings_test.cpp index 8989201..be964e6 100644 --- a/tests/settings_test.cpp +++ b/tests/settings_test.cpp @@ -7,6 +7,40 @@ #include #include +class CountingAllocator : public bx::AllocatorI +{ +public: + CountingAllocator() + : m_live(0) + { + } + + virtual ~CountingAllocator() + { + } + + virtual void* realloc(void* _ptr, size_t _size, size_t _align, const char* _filePath, uint32_t _line) override + { + if (NULL == _ptr + && 0 != _size) + { + ++m_live; + } + else if (NULL != _ptr + && 0 == _size) + { + --m_live; + } + + return m_allocator.realloc(_ptr, _size, _align, _filePath, _line); + } + + int32_t m_live; + +private: + bx::DefaultAllocator m_allocator; +}; + TEST_CASE("Settings", "") { bx::FilePath filePath; @@ -47,3 +81,146 @@ TEST_CASE("Settings", "") REQUIRE(0 == bx::strCmp(settings.get("meh/podmac"), "true") ); REQUIRE(0 == bx::strCmp(settings.get("test/foo/bar/abvgd"), "1389") ); } + +TEST_CASE("Settings parse", "") +{ + bx::DefaultAllocator allocator; + + const bx::StringView ini( + "; leading comment\n" + "globalKey=globalValue\n" + "\n" + "[section]\n" + "\tkey = value \n" + "spaces = a b c\n" + "; comment inside section\n" + "empty=\n" + "\n" + "[ padded ]\n" + "k=v\n" + "lineWithoutEquals\n" + "=valueWithoutName\n" + "\n" + "[dup]\n" + "x=1\n" + "x=2" + ); + + bx::Settings settings(&allocator, ini.getPtr(), ini.getLength() ); + + REQUIRE(0 == bx::strCmp(settings.get("globalKey"), "globalValue") ); + + REQUIRE(0 == bx::strCmp(settings.get("section/key"), "value") ); + REQUIRE(0 == bx::strCmp(settings.get("section/spaces"), "a b c") ); + + REQUIRE(settings.get("section/empty").isEmpty() ); + + REQUIRE(0 == bx::strCmp(settings.get("padded/k"), "v") ); + + REQUIRE(settings.get("padded/lineWithoutEquals").isEmpty() ); + + REQUIRE(0 == bx::strCmp(settings.get("dup/x"), "2") ); + + REQUIRE(settings.get("section/; comment inside section").isEmpty() ); + + REQUIRE(0 == bx::strCmp(settings.get("SECTION/KEY"), "value") ); + + REQUIRE(settings.get("nosuch/key").isEmpty() ); + REQUIRE(settings.get("nosuch").isEmpty() ); +} + +TEST_CASE("Settings parse CRLF", "") +{ + bx::DefaultAllocator allocator; + + const bx::StringView ini( + "[a]\r\n" + "key = value\r\n" + "\r\n" + "[b]\r\n" + "key=other\r\n" + ); + + bx::Settings settings(&allocator, ini.getPtr(), ini.getLength() ); + + REQUIRE(0 == bx::strCmp(settings.get("a/key"), "value") ); + REQUIRE(0 == bx::strCmp(settings.get("b/key"), "other") ); +} + +TEST_CASE("Settings empty", "") +{ + bx::DefaultAllocator allocator; + + bx::Settings settings(&allocator); + REQUIRE(settings.get("anything").isEmpty() ); + + const bx::StringView comments("; just a comment\n\n\n \n"); + settings.load(comments.getPtr(), comments.getLength() ); + REQUIRE(settings.get("anything").isEmpty() ); + + settings.load(NULL, 0); + REQUIRE(settings.get("anything").isEmpty() ); +} + +TEST_CASE("Settings roundtrip", "") +{ + bx::DefaultAllocator allocator; + + bx::Settings settings(&allocator); + settings.set("global", "0"); + settings.set("a/one", "1"); + settings.set("a/two", "2"); + settings.set("b/nested/three", "3"); + + char tmp[512]; + bx::StaticMemoryBlockWriter writer(tmp, sizeof(tmp) ); + const int32_t size = bx::write(&writer, settings, bx::ErrorIgnore{}); + REQUIRE(0 < size); + + bx::Settings reloaded(&allocator, tmp, uint32_t(size) ); + REQUIRE(0 == bx::strCmp(reloaded.get("global"), "0") ); + REQUIRE(0 == bx::strCmp(reloaded.get("a/one"), "1") ); + REQUIRE(0 == bx::strCmp(reloaded.get("a/two"), "2") ); + REQUIRE(0 == bx::strCmp(reloaded.get("b/nested/three"), "3") ); + + reloaded.set("a/one", "one-updated"); + REQUIRE(0 == bx::strCmp(reloaded.get("a/one"), "one-updated") ); + + reloaded.remove("a/one"); + reloaded.remove("a/two"); + REQUIRE(reloaded.get("a/one").isEmpty() ); + REQUIRE(reloaded.get("a/two").isEmpty() ); + REQUIRE(0 == bx::strCmp(reloaded.get("b/nested/three"), "3") ); +} + +TEST_CASE("Settings allocations balanced", "") +{ + CountingAllocator allocator; + + { + const bx::StringView ini( + "global=0\n" + "[a]\n" + "one=1\n" + "two=2\n" + "[b]\n" + "three=3\n" + ); + + bx::Settings settings(&allocator, ini.getPtr(), ini.getLength() ); + + REQUIRE(0 < allocator.m_live); + + settings.set("c/four", "4"); + settings.set("a/one", "overwritten"); + settings.remove("b/three"); + + settings.load(ini.getPtr(), ini.getLength() ); + + settings.clear(); + + settings.set("d/five", "5"); + } + + REQUIRE(0 == allocator.m_live); +} diff --git a/tests/string_test.cpp b/tests/string_test.cpp index d155760..3881c6d 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -691,6 +691,55 @@ TEST_CASE("strWord", "[string]") REQUIRE(0 == bx::strCmp(bx::strWord("abvgd-1389.0"), "abvgd") ); } +TEST_CASE("strFindEol strFindNl", "[string]") +{ + { + const bx::StringView test("abc"); + REQUIRE(test.getTerm() == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getTerm() == bx::strFindNl(test).getPtr() ); + } + + { + const bx::StringView test("abc\ndef"); + REQUIRE(test.getPtr() + 3 == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getPtr() + 4 == bx::strFindNl(test).getPtr() ); + } + + { + // End of line is the \r of a \r\n pair, new line is past the \n. + const bx::StringView test("abc\r\ndef"); + REQUIRE(test.getPtr() + 3 == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getPtr() + 5 == bx::strFindNl(test).getPtr() ); + } + + { + // A lone \r is not a line terminator. + const bx::StringView test("abc\rdef\n"); + REQUIRE(test.getPtr() + 7 == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getPtr() + 8 == bx::strFindNl(test).getPtr() ); + } + + { + // Must return the *first* terminator, not the first \r\n. + const bx::StringView test("abc\ndef\r\n"); + REQUIRE(test.getPtr() + 3 == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getPtr() + 4 == bx::strFindNl(test).getPtr() ); + } + + { + const bx::StringView test("\r\n"); + REQUIRE(test.getPtr() == bx::strFindEol(test).getPtr() ); + REQUIRE(test.getTerm() == bx::strFindNl(test).getPtr() ); + } + + { + // Leading \n whose preceding \r is outside the view. + const bx::StringView test("a\r\nb"); + const bx::StringView tail(test, 2, INT32_MAX); + REQUIRE(tail.getPtr() == bx::strFindEol(tail).getPtr() ); + } +} + TEST_CASE("strFindBlock", "[string]") { const bx::StringView test0("{ { {} {} abvgd; {} } }"); diff --git a/tests/url_test.cpp b/tests/url_test.cpp index 45d76c6..15c6c79 100644 --- a/tests/url_test.cpp +++ b/tests/url_test.cpp @@ -41,6 +41,39 @@ static const UrlTest s_urlTest[] = , "file:///d:/tmp/archive.tar.gz", { "file", "", "", "", "", "/d:/tmp/archive.tar.gz", "", "" }, }, + + { true + , "scheme://host.rs/path#fragment", + { "scheme", "", "", "host.rs", "", "/path", "", "fragment" }, + }, + { true + , "scheme://host.rs/path?query#fragment", + { "scheme", "", "", "host.rs", "", "/path", "query", "fragment" }, + }, + { true + , "scheme://host.rs/path?", + { "scheme", "", "", "host.rs", "", "/path", "", "" }, + }, + { true + , "scheme://username:password@host.rs/", + { "scheme", "username", "password", "host.rs", "", "/", "", "" }, + }, + { true + , "scheme://host.rs", + { "scheme", "", "", "host.rs", "", "", "", "" }, + }, + { false // Fragment must not precede query. + , "scheme://host.rs/path#fragment?query", + { "", "", "", "", "", "", "", "" }, + }, + { false // Neither scheme nor path. + , "host.rs", + { "", "", "", "", "", "", "", "" }, + }, + { false // Scheme must be alpha. + , "1scheme://host.rs/", + { "", "", "", "", "", "", "", "" }, + }, }; TEST_CASE("tokenizeUrl", "[url][string]")