From 6ae682aff66d736e9e92184b3c5dfcc544c65715 Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Mon, 14 Jul 2025 05:11:52 -0400 Subject: [PATCH] - Removed a bug with attempt to include pure c headers - Added some more information about the license - fennec::file implementation --- CMakeLists.txt | 22 +- PLANNING.md | 67 +- README.md | 4 +- include/fennec/fproc/io/common.h | 33 +- include/fennec/fproc/io/detail/__stdio.h | 12 - include/fennec/fproc/io/file.h | 242 ++++- include/fennec/fproc/strings/detail/__ctype.h | 13 +- .../fennec/fproc/strings/detail/__locale.h | 12 - include/fennec/fproc/strings/string.h | 73 +- include/fennec/fproc/strings/wcstring.h | 309 +++++++ include/fennec/fproc/strings/wstring.h | 426 +++++++++ include/fennec/lang/detail/__int.h | 7 - include/fennec/lang/detail/__stdlib.h | 14 - include/fennec/math/detail/__math.h | 9 - include/fennec/math/ext/constants.h | 4 - include/fennec/math/vector_storage.h | 4 - include/fennec/memory/allocator.h | 7 +- include/fennec/memory/detail/__string.h | 12 - include/fennec/memory/new.h | 33 - metaprogramming/CMakeLists.txt | 1 + source/debug/assert_impl.cpp | 5 +- source/fproc/io/common.cpp | 247 ++++++ source/fproc/io/file.cpp | 825 +++++++++++++++++- source/memory/new.cpp | 25 +- test/CMakeLists.txt | 8 +- test/tests/fproc/test_io.h | 60 +- 26 files changed, 2229 insertions(+), 245 deletions(-) create mode 100644 include/fennec/fproc/strings/wcstring.h create mode 100644 include/fennec/fproc/strings/wstring.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 230c653..1fd44b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,16 +9,14 @@ add_subdirectory(external/sdl) # CppTrace is a dependency of the project, added as a git submodule add_subdirectory(external/cpptrace) -set(CMAKE_CXX_STANDARD 26) -set(CMAKE_C_STANDARD 26) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_C_STANDARD 23) # find dependencies find_package(Doxygen) # any necessary include directories -include_directories(include) -include_directories(external/cpptrace/include) -include_directories(external/sdl/include) +include_directories(${PROJECT_SOURCE_DIR}/include) # Metaprogramming is a dependency for generating various type info before compilation of the engine. add_subdirectory(metaprogramming) @@ -32,6 +30,9 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib/${FENNEC_BUILD_NAME set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib/${FENNEC_BUILD_NAME}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}) +# add the test suite as a sub-project +add_subdirectory(test) + add_library(fennec STATIC # CORE ================================================================================================================= @@ -133,9 +134,9 @@ add_library(fennec STATIC # add metaprogramming templates as a dependency and also force documentation to be generated when fennec is compiled if(DOXYGEN_FOUND) - add_dependencies(fennec fennecdocs metaprogramming SDL3-shared cpptrace::cpptrace) + add_dependencies(fennec fennecdocs metaprogramming) # SDL3-shared cpptrace::cpptrace) else() - add_dependencies(fennec metaprogramming SDL3-shared cpptrace::cpptrace) + add_dependencies(fennec metaprogramming) # SDL3-shared cpptrace::cpptrace) endif() # Compiler Warning Flags @@ -143,10 +144,13 @@ if(MSVC) add_compile_options("/W4" "/WX") # All MSVC Warnings throw as Errors else() add_compile_options("-Wall" "-Wextra" "-pedantic" "-Werror") # All gcc/etc. Warnings throw as errors + + target_compile_definitions(fennec PRIVATE _GLIBCXX_INCLUDE_NEXT_C_HEADERS __USE_FILE_OFFSET64) endif() target_compile_options(fennec PUBLIC "-mavx" "-mavx2" "-mavx512f") # SIMD Instructions, automatic vectorization will occur + target_link_options(fennec PRIVATE "-nostdlib" "-fno-exceptions" "-fno-rtti" "-fdiagnostics-all-candidates") # Do not compile base fennec library with c++ stdlib # fennec does not use the C++ stdlib because it is bloated, difficult to read, and implementation defined. # This implementation is designed to be as readable as possible, and expose information that would otherwise be obfuscated @@ -154,10 +158,6 @@ target_link_options(fennec PRIVATE "-nostdlib" "-fno-exceptions" "-fno-rtti" "-f target_link_libraries(fennec PRIVATE SDL3-shared cpptrace::cpptrace) -# add the test suite as a sub-project -add_subdirectory(test) - - # DOXYGEN ============================================================================================================== # https://vicrucann.github.io/tutorials/quick-cmake-doxygen/ diff --git a/PLANNING.md b/PLANNING.md index 0abf3c0..d5a6ea9 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -6,7 +6,7 @@ 1. [Introduction](#introduction) 2. [TODO](#todo) - 1. [Security Ramblings](#security-ramblings) + 1. [Security Ramblings](#file-security-ramblings) 3. [C++ Language](#c-language-library-lang) 4. [Math Library](#math-library-math) 5. [Memory Library](#memory-library-memory) @@ -63,6 +63,7 @@ This however can be achieved using events at different stages of those engines t - 2D Physics (`physics2d`) - 2D & 3D Audio (`audio`) + ### File Security Ramblings: Windows is starting to piss me off, so I am considering dropping official support for MSVC. MinGW and Cygwin @@ -78,41 +79,41 @@ The crux of this issue falls at the following specific behaviour: - Application interface confirms overwrite action - Application writes to the file after confirmation -A threat actor can introduce a malicious file or symlink to the file that was attempted access between the check and +A threat actor can introduce a malicious file or symlink to the file that was attempted access between the check and usage of the file. This is called TOCTOU (time of check, time of use). -This issue can be solved using `fopen("", "w+")`, however this specific behaviour is not intuitive to those first -learning how to work with file systems. We can attempt to abstract this away with another wrapper, or simply write -the file structure to handle this behaviour properly. The downside to this method overall is that it will break -common conventions of how humans interpret filesystems and the related control flow logic. What we can do is force the -`'+'` flag to always be present for write operations, and raise an error when desired, if the file is not empty. This +This issue can be solved using `fopen("", "a+")` and `ftell`, however this specific behaviour is not intuitive to +those first learning how to work with file systems. We can attempt to abstract this away with another wrapper, or simply +write the file structure to handle this behaviour properly. The downside to this method overall is that it will break +common conventions of how humans interpret filesystems and the related control flow logic. What we can do is force the +`'+'` flag to always be present for write operations, and raise an error when desired, if the file is not empty. This unfortunately would have the downside of being unable to open a file as write only. -Using `"wx"` in this instance would not be sufficient since it would require a second call to fopen, which would +Using `"wx"` in this instance would not be sufficient since it would require a second call to fopen, which would create the conditions for the TOCTOU error described above. -Another issue arises when we are parsing a directory tree. The best we can do is take ownership of the directory that -is opened as the root. However, this requires `dirent.h` which is not implemented in MSVC. A custom implementation of -`dirent.h` may be written for MSVC, however this is one of the few things I am not willing to outsource to another -library. Developing our own implementation would take a non-insignificant amount of time, between writing the library, -debugging it, and testing for vulnerabilities. As stated above, this implementation is native to MinGW and Cygwin, -so we would not have to entirely drop support for Windows. However, MSVC is the most widely used compiler for Windows +Another issue arises when we are parsing a directory tree. The best we can do is take ownership of the directory that +is opened as the root. However, this requires `dirent.h` which is not implemented in MSVC. A custom implementation of +`dirent.h` may be written for MSVC, however this is one of the few things I am not willing to outsource to another +library. Developing our own implementation would take a non-insignificant amount of time, between writing the library, +debugging it, and testing for vulnerabilities. As stated above, this implementation is native to MinGW and Cygwin, +so we would not have to entirely drop support for Windows. However, MSVC is the most widely used compiler for Windows applications and is native to Visual Studio and VSCode. -What is probably the best solution is to wrap everything in a file interface that does not allow the direct setting of +What is probably the best solution is to wrap everything in a file interface that does not allow the direct setting of these flags. Then we set our own usage type for the file that informs which flags should be used. We need to be able to handle the following types of files: - Assets, such as scenes, audio, textures, metadata, meshes, etc. - Save files, setting files, etc. -One of the nice things about the assets is that they are guaranteed to be read-only once an application is installed +One of the nice things about the assets is that they are guaranteed to be read-only once an application is installed on the computer of the end-user. Therefore, this issue only arises with save files and custom file formats. When the editor is run, all these files should be opened in read/write mode. -Naming conventions should exist for the types of files and how they are read. For example, in release mode, -most assets should be opened once, and then closed immediately. However, this does not make sense for formats +Naming conventions should exist for the types of files and how they are read. For example, in release mode, +most assets should be opened once, and then closed immediately. However, this does not make sense for formats that are continuous and too large to be kept around in memory, such as video formats. Perhaps the following conventions: @@ -120,23 +121,41 @@ Perhaps the following conventions: - Stream Asset - Resource -We can turn this into an object-oriented approach by having different formats inherit these base types. We may still +We can turn this into an object-oriented approach by having different formats inherit these base types. We may still have a base file type that wraps C functionality, but discourage developers from using the interface. -We could also declare the file interface extern so that only internal files know the implementation. However, I would +We could also declare the file interface extern so that only internal files know the implementation. However, I would not be satisfied by doing this since it would prevent developers from implementing custom file type implementations. -Conserving memory is not really an issue here as long as we are smart about our implementation. Files should only be -open when necessary and be closed when it is no longer necessary to have them open. +Conserving memory is not really an issue here as long as we are smart about our implementation. Files should only be +open when necessary and be closed when it is no longer necessary to have them open. Data should be streamed unless the +all the data in the file is required. -When built in release mode, we also need to pack static assets into some sort of archive that is mountable to reduce -disk space consumption of a program. I am considering encryption for archives, but there likely is not much of a point. +When built in release mode, we also need to pack static assets into some sort of archive that is mountable to reduce +disk space consumption of a program. +I was considering encryption for archives, however it does not make much sense. Assuming someone intends to pirate the +game, there is not much stopping them from running the files. I will add Steam support at some point which would allow +you to use Steam's DRM to prevent the executable from being run. Otherwise, there is no point in attempting to encrypt +game files. Even Unreal PAK files can be cracked in seconds, and even if I managed to write something that cannot be +trivially cracked locally, you can scrape most assets from the GPU and Audio Card. + +I have managed to solve the specific case provided at the top of this section, which was done by wrapping C I/O calls +into a file wrapper. This wrapper can handle a few different mode flags and has specific conditions for the flags. See +the documentation for `fennec/fproc/io/file.h` for more info. + +One question remains unanswered on this front; should a read/write file open as `r+` or `a+`. `rewind` is slightly faster +than `fseek(SEEK_END)`, however for the case of save files and editor assets, `r+` makes more sense from a usage perspective + +Directories remain an issue, with `dirent.h` being the only sensible option at time of writing. The issue with using +`dirent.h` boils back down to security issues on Windows. However, the only option is to write a custom implementation +for MSVC. ## C++ Language Library (`lang`) Implement header files for standard functions relating to the C++ Language. + So far this is implemented on an as-needed basis. A full implementation should be worked on continuously. diff --git a/README.md b/README.md index c4e5752..43730c7 100644 --- a/README.md +++ b/README.md @@ -279,8 +279,8 @@ require legal council to consult with them which may dissuade permission to use I hold a Bachelor's Degree in Computer Science and Real-Time Interactive Simulation from DigiPen Institute of Technology, so I am familiar with their copyright policy. Ask your professor about usage of my engine, and they or someone with -appropriate standing will reach out to me. Eventually I may reach out on my own terms to negotiate usage of fennec for -educative purposes at DigiPen while retaining their license on student work. +appropriate standing will reach out to me. Eventually, with interest, I may reach out on my own terms to negotiate usage +of fennec for educative purposes at DigiPen while retaining their license on student work. If your University is not listed here, reach out to your professor for permission. Ask them to reach out to me, I am willing to work with educational institutes to protect both fennec and student work in accordance to university policy. diff --git a/include/fennec/fproc/io/common.h b/include/fennec/fproc/io/common.h index 2c1a513..d4fe02b 100644 --- a/include/fennec/fproc/io/common.h +++ b/include/fennec/fproc/io/common.h @@ -21,40 +21,13 @@ #include - -#ifdef _WIN32 - namespace fennec { -inline string getcwd() { - char cstr[MAX_PATH]; - if (GetCurrentDirectory(sizeof(str), str) == 0) { - return string(""); - } - return string(cstr); -} +string getcwd(); +string absolute(const cstring& path); +string absolute(const string& path); } -#else - -#include -#include - -namespace fennec -{ - -inline string getcwd() { - char cstr[PATH_MAX]; - if (::getcwd(cstr, sizeof(cstr)) == NULL) { - return string(""); - } - return string(cstr); -} - -} - -#endif - #endif // FENNEC_FPROC_IO_COMMON_H diff --git a/include/fennec/fproc/io/detail/__stdio.h b/include/fennec/fproc/io/detail/__stdio.h index 0591ce7..59180c5 100644 --- a/include/fennec/fproc/io/detail/__stdio.h +++ b/include/fennec/fproc/io/detail/__stdio.h @@ -19,18 +19,6 @@ #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #define FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - -#pragma push_macro("__cplusplus") -#undef __cplusplus #include -#pragma pop_macro("__cplusplus") - -#if _MSC_VER -#pragma warning(pop) -#endif #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H diff --git a/include/fennec/fproc/io/file.h b/include/fennec/fproc/io/file.h index d9ab2c7..43e25a3 100644 --- a/include/fennec/fproc/io/file.h +++ b/include/fennec/fproc/io/file.h @@ -19,77 +19,259 @@ #ifndef FENNEC_FPROC_IO_FILE_H #define FENNEC_FPROC_IO_FILE_H +#include + #include #include - -struct FILE; +#include namespace fennec { /// /// \brief Mode flags for opening a file -enum fmode : uint8_t +/// +/// fmode_binary and fmode_wide are independent of the other modes +/// +/// \details Valid Flag Combinations +/// +///
Flags +/// Description +/// +///
`read` +/// Opens file as read-only, reading from start +/// +///
`write` +/// Opens file as write-only, writing to end +/// +///
`read | write` +/// Opens file as read-write, reading from start +/// +///
`write | trunc` +/// Opens file as write-only, destroying contents +/// +///
`read | write | trunc` +/// Opens file as read-write, destroying contents +///
+enum fmode_ : uint8_t { - read = 0b00000001 -, write = 0b00000010 -, append = 0b00000100 -, noexists = 0b00001000 + fmode_read = 0b00000001 ///< Opens file for reading +, fmode_write = 0b00000010 ///< Opens file for writing +, fmode_trunc = 0b00000100 ///< Contents of the file will be destroyed, only compatible with write enabled modes +, fmode_exclusive = 0b00001000 ///< Generates an error if the opened file is not empty +, fmode_binary = 0b00010000 ///< Open in binary mode +, fmode_wide = 0b00100000 ///< Opens a file in wide mode }; +/// +/// \brief Structure for handling streams of data +/// +/// \details operations, when errored, will return a corresponding error. +/// Use file::get_error() to check if an error is present and return a corresponding string. +/// Use file::clear_error() to clear the errored state. +/// Some operations, specifically file::rename() and file::copy(). class file { public: + /// \brief value of an invalid position + static constexpr size_t npos = -1; + + /// + /// \brief Check if the provided mode bitflags are a valid combination + /// \param mode the bitfield + /// \returns true if the combination of flags is valid, false otherwise + static constexpr bool is_valid(uint8_t mode) { + bool t = mode & fmode_trunc; + bool x = mode & fmode_exclusive; + bool w = mode & fmode_write; + + // when x is true, t must be true + // when t is true, w must be true + return (t && x && w) + || (t && w) + || !(t || x); + } + + /// + /// \returns the c stdout + static file& cout(); + + /// + /// \returns the c stdin + static file& cin(); + + /// + /// \returns the c stderr + static file& cerr(); + + /// + /// \brief default constructor, initializes an empty stream file(); + + /// + /// \brief default destructor, cleans up an open stream ~file(); /// - /// \brief Opens a file at the provided path using the provided mode - /// \param path the path to the provided file - /// \param mode the flags for opening the file - file(const cstring& path, fmode mode); - file(const string& path, fmode mode); + /// \brief move constructor + /// \param file the stream to take ownership of + file(file&& file) noexcept; - file(file&& file); + file& operator=(file&& file) noexcept; + // don't allow copying streams file(const file&) = delete; +// Properties ========================================================================================================== + + /// + /// \returns the path the stream + const string& path() const { + return _path; + } + + /// + /// \returns the mode of the stream + uint8_t mode() const { + return _mode; + } + + /// + /// \returns true if there is a valid, open stream. + bool is_open() const { + return _handle != nullptr; + } + + // File Access ========================================================================================================= - void open(const cstring& filename, fmode mode); - void open(const string& filename, fmode mode); - void close(); - void commit(); + /// + /// \brief open a file + /// \param path the path to the file + /// \param mode the mode flags to open the file with + /// \returns false on success, true on error + bool open(const cstring& path, uint8_t mode); + + /// + /// \brief open a file + /// \param path the path to the file + /// \param mode the mode flags to open the file with + /// \returns false on success, true on error + bool open(const string& path, uint8_t mode); + + /// + /// \brief close a stream + /// \returns false on success, true on error + bool close(); + + /// + /// \brief commit the streams buffer to the file + /// \returns false on success, true on error + bool commit(); // File Operations ===================================================================================================== - void erase(); - void rename(const cstring& str); - void rename(const string& str); + /// + /// \brief closes the stream and erases the file + /// \returns false on success, true on error + bool erase(); + + /// + /// \brief rebinds the stream, copying contents to path, and erasing the old file + /// \param path the new path + /// \returns false on success, true on error + /// + /// \details attempts to open a write-only stream at path, + /// attempts to reopen this file as read-only, + /// copies the contents of this file to the new stream, + /// reopen the new stream with the flags of this file and binds to it, + /// closes the old file. + bool rename(const cstring& path); + + /// + /// \brief rebinds the stream, copying contents to path, and erasing the old file + /// \param path the new path + /// \returns false on success, true on error + /// + /// \details attempts to open a write-only stream at path, + /// attempts to reopen this file as read-only, + /// copies the contents of this file to the new stream, + /// reopen the new stream with the flags of this file and binds to it, + /// closes the old file. + bool rename(const string& path); + + /// + /// \brief copies the contents of this file to path. + /// \param path the path to copy to + /// \returns a file at the new path with the copied contents + file copy(const cstring& path); + + /// + /// \brief copies the contents of this file to path. + /// \param path the path to copy to + /// \returns a file at the new path with the copied contents + file copy(const string& path); // File Positioning ==================================================================================================== - size_t get_pos(); - void set_pos(size_t i); - void rewind(); + size_t get_pos() const; + bool set_pos(size_t i); + bool rewind(); + bool eof() const; + + +// Read Operations ===================================================================================================== + + char getc(); + wchar_t getwc(); + + size_t read(void* data, size_t size, size_t n); + + template + size_t read(T* data, size_t n) { + return read(static_cast(data), sizeof(T), n); + } + + template + size_t read(T (&data)[n]) { + return read(static_cast(data), sizeof(T), n); + } + + string getline(); + wstring getwline(); + + +// Write Operations ==================================================================================================== + + bool putc(char c); + bool putwc(wchar_t c); + + size_t write(const void* data, size_t size, size_t n); + + template + size_t write(const T* data, size_t n) { + return write(static_cast(data), sizeof(T), n); + } + + template + size_t write(const T (&data)[n]) { + return write(static_cast(data), sizeof(T), n); + } // Error Handling ====================================================================================================== - bool check_error(); - string get_error(); - void clear_error(); - - -// + const char* get_error() const { return _error; } + void clear_error() { _error = nullptr; } private: FILE* _handle; string _path; - fmode _mode; + uint8_t _mode; + char* _error; }; } diff --git a/include/fennec/fproc/strings/detail/__ctype.h b/include/fennec/fproc/strings/detail/__ctype.h index 624bd91..e5ee76e 100644 --- a/include/fennec/fproc/strings/detail/__ctype.h +++ b/include/fennec/fproc/strings/detail/__ctype.h @@ -19,18 +19,7 @@ #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #define FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - -#pragma push_macro("__cplusplus") -#undef __cplusplus #include -#pragma pop_macro("__cplusplus") - -#if _MSC_VER -#pragma warning(pop) -#endif +#include #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H diff --git a/include/fennec/fproc/strings/detail/__locale.h b/include/fennec/fproc/strings/detail/__locale.h index 3d1d973..e13dcb7 100644 --- a/include/fennec/fproc/strings/detail/__locale.h +++ b/include/fennec/fproc/strings/detail/__locale.h @@ -19,18 +19,6 @@ #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #define FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - -#pragma push_macro("__cplusplus") -#undef __cplusplus #include -#pragma pop_macro("__cplusplus") - -#if _MSC_VER -#pragma warning(pop) -#endif #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H diff --git a/include/fennec/fproc/strings/string.h b/include/fennec/fproc/strings/string.h index 69fa240..ed538e7 100644 --- a/include/fennec/fproc/strings/string.h +++ b/include/fennec/fproc/strings/string.h @@ -75,7 +75,33 @@ public: /// \details adds additional character for null termination. constexpr _string(char c, size_t n) : _str(n + 1) { - fennec::memset(_str, c, n); _str[n] = '\0'; + fennec::memset(_str.data(), c, n); _str[n] = '\0'; + } + /// + /// \brief Buffer Copy Constructor + /// \param str the buffer to copy + /// \param len number of characters in the buffer + /// + /// \details adds additional character for null termination. Ignores whether str is null-terminated. + /// This constructor makes the assumption that `len` is the intended number of characters. + template + constexpr _string(const char str[n]) + : _str(str, n + 1) { + ::strncpy(_str.data(), str, n); + _str[n] = '\0'; + } + + /// + /// \brief Buffer Copy Constructor + /// \param str the buffer to copy + /// \param len number of characters in the buffer + /// + /// \details adds additional character for null termination. Ignores whether str is null-terminated. + /// This constructor makes the assumption that `len` is the intended number of characters. + constexpr _string(const char* str, size_t n) + : _str(str, n + 1) { + ::strncpy(_str.data(), str, n); + _str[n] = '\0'; } /// @@ -94,7 +120,7 @@ public: /// \brief String Copy Constructor /// \param str the string to copy constexpr _string(const string& str) - : _string(str, str.size() - 1) { + : _string(str, str.size()) { } constexpr _string(_string&& str) noexcept @@ -112,6 +138,10 @@ public: return _str.capacity() - 1; } + constexpr bool empty() const { + return size() == 0; + } + // Access ============================================================================================================== @@ -187,16 +217,16 @@ public: } /// - /// \brief Finds the index of the first occurrence of `x` in the string - /// \param x the character to find - /// \returns The index of `x` if it occurs in the string, otherwise returns `size()` + /// \brief Finds the index of the first occurrence of `c` in the string + /// \param c the character to find + /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` constexpr size_t find(char c, size_t i = 0) const { if (i >= size()) { // bounds check return size(); } - const char* loc = ::strchr(_str + i, c); // get location using strchr - return loc ? loc - _str : size(); // return size if not found + const char* loc = ::strchr(_str.data() + i, c); // get location using strchr + return loc ? loc - _str.data() : size(); // return size if not found } /// @@ -293,7 +323,18 @@ public: constexpr void resize(size_t n) { size_t i = size(); _str.reallocate(n + 1); - if (n > i) fennec::memset(*_str + i, '\0', n - i); + if (n > i) fennec::memset(_str.data() + i, '\0', n - i); + } + + /// + /// \brief Copy Assignment Operator + /// \param str the string to copy + /// \returns a reference to `this` + constexpr string& operator=(const cstring& str) { + resize(str.size()); + fennec::memcpy(_str.data(), str, str.size()); + _str[str.size()] = '\0'; + return *this; } /// @@ -301,8 +342,8 @@ public: /// \param str the string to copy /// \returns a reference to `this` constexpr string& operator=(const string& str) { - if (str.size() > size()) resize(str.size()); - fennec::memcpy(_str, str, str.size()); + resize(str.size()); + fennec::memcpy(_str.data(), str, str.size()); _str[str.size()] = '\0'; return *this; } @@ -321,10 +362,12 @@ public: /// \param i the start index /// \param n the number of characters /// \return - constexpr string substring(size_t i, size_t n = size()) const { - assert(i < size(), "Array Out of Bounds"); + constexpr string substring(size_t i, size_t n = npos) const { + if (i >= size()) { + return string(""); + } n = min(n, size() - i); - return string(&_str[i], n); + return string(_str.data() + i, n); } /// @@ -340,14 +383,14 @@ public: constexpr string operator+(const cstring& str) const { string res(size() + str.size()); // Make a new string with the size of this + str - fennec::memcpy(res, _str, size()); // Copy the contents of this + fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str return res; } constexpr string operator+(const string& str) const { string res(size() + str.size()); // Make a new string with the size of this + str - fennec::memcpy(res, _str, size()); // Copy the contents of this + fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str return res; } diff --git a/include/fennec/fproc/strings/wcstring.h b/include/fennec/fproc/strings/wcstring.h new file mode 100644 index 0000000..e40603e --- /dev/null +++ b/include/fennec/fproc/strings/wcstring.h @@ -0,0 +1,309 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef FENNEC_FPROC_STRINGS_wcstring_H +#define FENNEC_FPROC_STRINGS_wcstring_H + +#include +#include + +#include + +#include + +namespace fennec +{ + +// TODO: Document + +using ::iswalnum; +using ::iswalpha; +using ::iswlower; +using ::iswupper; +using ::iswdigit; +using ::iswxdigit; +using ::iswcntrl; +using ::iswgraph; +using ::iswspace; +using ::iswblank; +using ::iswprint; +using ::iswpunct; + +using ::towlower; +using ::towupper; + +using ::towctrans; +using ::wctrans; + +/// +/// \brief This struct wraps c-style strings +/// +/// \details Requires that the string is null-terminated. +/// Prevents const qualified memory blocks from being manipulated. +/// This struct should be used when fennec::string would make unnecessary dynamic buffers. +struct wcstring +{ +public: + static constexpr size_t npos = -1; + +// Constructors ======================================================================================================== + + /// + /// \brief Default Constructor, initializes with nullptr + constexpr wcstring() + : _str(nullptr), _size(0), _const(true) { + } + + /// + /// \brief Buffer Constructor, wraps the provided C-Style string + /// \param str the buffer to wrap + /// \param n the number of characters in the buffer plus the null terminator + constexpr wcstring(wchar_t* str, size_t n) + : _str(str) + , _size(n - 1) + , _const(false) { + assert(_str[n - 1] == '\0', "Invalid NTBS."); + } + + /// + /// \brief Buffer Constructor, wraps the provided C-Style string + /// \param str the buffer to wrap + /// \tparam n the number of characters in the buffer plus the null terminator + template + constexpr wcstring(wchar_t(&str)[n]) + : _str(str) + , _size(n - 1) + , _const(false) { + assert(_str[n - 1] == '\0', "Invalid NTBS."); + } + + /// + /// \brief Const Buffer Constructor, wraps the provided C-Style string + /// \param str the buffer to wrap + /// \param n the number of characters in the buffer plus the null terminator + constexpr wcstring(const wchar_t* str, size_t n) + : _cstr(str) + , _size(n - 1) + , _const(true) { + assert(_cstr[n - 1] == '\0', "Invalid NTBS."); + } + + /// + /// \brief Buffer Constructor, wraps the provided C-Style string + /// \param str the buffer to wrap + /// \tparam n the number of characters in the buffer plus the null terminator + template + constexpr wcstring(const wchar_t(&str)[n]) + : _cstr(str) + , _size(n - 1) + , _const(true) { + assert(_cstr[n - 1] == '\0', "Invalid NTBS."); + } + + /// + /// \brief Move Constructor + /// \param str object to move + constexpr wcstring(wcstring&& str) noexcept + : _cstr(str._cstr) + , _size(str._size) + , _const(str._const) { + str._cstr = nullptr; + str._size = 0; + str._const = true; + } + + // TODO: Document + constexpr wcstring(const wcstring&) = delete; + constexpr ~wcstring() = default; + + // TODO: Document + template + constexpr wcstring& operator=(wchar_t(&str)[n]) { + assert(_str[n - 1] == '\0', "Invalid NTBS."); + _str = str; _size = n - 1; _const = false; + return *this; + } + + // TODO: Document + template + constexpr wcstring& operator=(const wchar_t(&str)[n]) { + assert(str[n - 1] == '\0', "Invalid NTBS."); + _cstr = str; _size = n - 1; _const = true; + return *this; + } + + // TODO: Document + constexpr wcstring& operator=(wcstring&& str) noexcept { + _cstr = str._cstr; str._cstr = nullptr; + _size = str._size; str._size = 0; + _const = str._const; str._const = true; + return *this; + } + + +// Properties ========================================================================================================== + + /// + /// \returns the size of the string excluding its null terminator, i.e. `(*str)[size()] == '\0'` + constexpr size_t size() const { return _size; } + + +// Access ============================================================================================================== + + /// + /// \brief Array Access Operator + /// \param i the index to access + /// \returns a reference to the character + constexpr wchar_t& operator[](size_t i) { + assertd(not _const, "Attempted to Access Const-Qualified Memory as Non-Const"); + assertd(i < size(), "Array Out of Bounds"); + return _str[i]; + } + + /// + /// \brief Const-Array Access Operator + /// \param i the index to access + /// \returns a copy of the character + constexpr wchar_t operator[](size_t i) const { + assertd(i < size(), "Array Out of Bounds"); + return _cstr[i]; + } + + /// + /// \brief Dereference Operator + /// \returns A const qualified pointer to the underlying allocation + constexpr const wchar_t* operator*() const { + return _cstr; + } + + /// + /// \brief Implicit Dereference Cast + constexpr operator const wchar_t*() const { + return _cstr; + } + + +// Examination ========================================================================================================= + + /// + /// \returns The length of the string to the first null-terminator + constexpr size_t length() const { + return find(L'\0'); + } + + /// + /// \brief String Comparison + /// \param str the string to compare against + /// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the + /// current locale, otherwise a positive value. + constexpr int compare(const wcstring& str, size_t i = 0, size_t n = npos) const { + if (i >= _size) { + return -1; + } + + n = min(n, max(_size, str._size) + 1); + return ::wcsncmp(_cstr + i, str, n); + } + + /// + /// \brief String Equality + /// \param str the string to compare against + /// \returns True if all characters are equal, false otherwise + constexpr bool operator==(const wcstring& str) const { + return compare(str) == 0; + } + + /// + /// \brief Finds the index of the first occurrence of `c` in the string + /// \param c the character to find + /// \param i the index to start at + /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` + constexpr size_t find(wchar_t c, size_t i = 0) const { + if (i >= _size) { // bounds check + return _size; + } + + const wchar_t* loc = ::wcschr(_cstr + i, c); // get location using strchr + return loc ? loc - _cstr : _size; // return size if not found + } + + /// + /// \brief Finds the index of the first occurrence of `str` in the string. + /// \param str the string to find + /// \param i the index to start at + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t find(const wcstring& str, size_t i = 0) const { + if (i + str._size > _size) { // bounds check + return _size; + } + + const wchar_t* loc = ::wcsstr(_cstr + i, str); // get location using strstr + return loc ? loc - _cstr : _size; // return size if not found + } + + /// + /// \brief Finds the index of the last occurrence of `c` in the string. + /// \param c the string to find + /// \param i the index to start at + /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` + constexpr size_t rfind(wchar_t c, size_t i = npos) const { + if (_size == 0) { + return _size; + } + + i = min(i, _size - 1); // clamp i to bounds + do { + if (_cstr[i] == c) return i; // loop backwards looking for c + } while (i--); + return _size; // base case + } + + /// + /// \brief Finds the index of the last occurrence of `str` in the string. + /// \param str the string to find + /// \param i the index to start at + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t rfind(const wcstring& str, size_t i = npos) const { + if (_size == 0) { + return _size; + } + + const wchar_t first = str[0]; + i = min(i, _size - str._size); + do { + if(_cstr[i] == first) { + if (compare(str, i, str._size - 1) == 0) { + return i; + } + } // loop backwards looking for str + } while (i--); + return _size; // base case + } + +private: + union { // hack to allow both const qualified and non-const strings + wchar_t* _str; + const wchar_t* _cstr; + }; + size_t _size; + bool _const; +}; + +} + +#endif // FENNEC_FPROC_STRINGS_wcstring_H diff --git a/include/fennec/fproc/strings/wstring.h b/include/fennec/fproc/strings/wstring.h new file mode 100644 index 0000000..2826fda --- /dev/null +++ b/include/fennec/fproc/strings/wstring.h @@ -0,0 +1,426 @@ +// ===================================================================================================================== +// fennec, a free and open source game engine +// Copyright © 2025 Medusa Slockbower +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ===================================================================================================================== + +#ifndef FENNEC_FPROC_wstringS_wstring_H +#define FENNEC_FPROC_wstringS_wstring_H + +#include +#include + +#include + +#include +#include + +#include + +namespace fennec +{ + +// Forward def +template> struct _wstring; + +// Alias for default allocator +using wstring = _wstring<>; + +/// +/// \brief Struct for wrapping c-style strings +/// +/// \details behaviour guarantees that the underlying string is null-terminated +template +struct _wstring +{ +public: + static constexpr size_t npos = -1; + using alloc_t = allocation; + + +// Constructors ======================================================================================================== + + /// + /// \brief Default Constructor, initializes empty string + constexpr _wstring() + : _str() { + } + + /// + /// \brief Sized Constructor, initializes a null-terminated string of size `n` with null wchar_tacters + /// \param n the number of wchar_tacters + /// + /// \details adds additional wchar_tacter for null termination. + constexpr _wstring(size_t n) + : _wstring('\0', n) { + } + + /// + /// \brief Sized Constructor, initializes a null-terminated string of size `n` filled with the wchar_tacter `c` + /// \param c the wchar_tacter to fill with + /// \param n the number of wchar_tacters + /// + /// \details adds additional wchar_tacter for null termination. + constexpr _wstring(wchar_t c, size_t n) + : _str(n + 1) { + fennec::wmemset(_str.data(), c, n); _str[n] = '\0'; + } + /// + /// \brief Buffer Copy Constructor + /// \param str the buffer to copy + /// \param len number of wchar_tacters in the buffer + /// + /// \details adds additional wchar_tacter for null termination. Ignores whether str is null-terminated. + /// This constructor makes the assumption that `len` is the intended number of wchar_tacters. + template + constexpr _wstring(const wchar_t str[n]) + : _str(str, n + 1) { + ::wcsncpy(_str.data(), str, n); + _str[n] = '\0'; + } + + /// + /// \brief Buffer Copy Constructor + /// \param str the buffer to copy + /// \param len number of wchar_tacters in the buffer + /// + /// \details adds additional wchar_tacter for null termination. Ignores whether str is null-terminated. + /// This constructor makes the assumption that `len` is the intended number of wchar_tacters. + constexpr _wstring(const wchar_t* str, size_t n) + : _str(str, n + 1) { + ::wcsncpy(_str.data(), str, n); + _str[n] = '\0'; + } + + /// + /// \brief Buffer Copy Constructor + /// \param str the buffer to copy + /// \param len number of wchar_tacters in the buffer + /// + /// \details adds additional wchar_tacter for null termination. Ignores whether str is null-terminated. + /// This constructor makes the assumption that `len` is the intended number of wchar_tacters. + constexpr _wstring(const wcstring& str) + : _str(str, str.size() + 1) { + _str[str.size()] = '\0'; + } + + /// + /// \brief String Copy Constructor + /// \param str the string to copy + constexpr _wstring(const wstring& str) + : _wstring(str, str.size() - 1) { + } + + constexpr _wstring(_wstring&& str) noexcept + : _str(fennec::move(str._str)) { } + + /// + /// \brief String Destructor, cleans up the underlying allocation + constexpr ~_wstring() = default; // allocation cleans up itself + +// Properties ========================================================================================================== + + /// + /// \returns The size of the string excluding null terminator + constexpr size_t size() const { + return _str.capacity() - 1; + } + + constexpr bool empty() const { + return size() == 0; + } + + +// Access ============================================================================================================== + + /// + /// \brief Array Access Operator + /// \param i the index to access + /// \returns a reference to the wchar_tacter + constexpr wchar_t& operator[](int i) { + return _str[i]; + } + + /// + /// \brief Const-Array Access Operator + /// \param i the index to access + /// \returns a copy of the wchar_tacter + constexpr wchar_t operator[](int i) const { + assertd(i >= 0 && i < size(), "Array Out of Bounds"); + return _str[i]; + } + + /// + /// \brief Dereference Operator + /// \returns A const qualified pointer to the underlying allocation + constexpr const wchar_t* operator*() const { + return _str.data(); + } + + /// + /// \brief Implicit Dereference Cast + constexpr operator const wchar_t*() const { + return _str.data(); + } + + +// Examination ========================================================================================================= + + /// + /// \returns The length of the string to the first null-terminator + constexpr size_t length() const { + return find('\0'); + } + + /// + /// \brief String Comparison + /// \param ostr the string to compare against + /// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the + /// current locale, otherwise a positive value. + constexpr int compare(const wcstring& str, size_t i = 0, size_t n = npos) const { + if (i >= size()) { // bounds check + return -1; + } + n = fennec::min(n, fennec::max(_str, str.size()) + 1); + + return ::wcsncmp(_str.data() + i, str, n); + } + + /// + /// \brief String Comparison + /// \param ostr the string to compare against + /// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the + /// current locale, otherwise a positive value. + constexpr int compare(const wstring& str, size_t i = 0, size_t n = npos) const { + if (i >= size()) { // bounds check + return -1; + } + n = min(n, max(size(), str.size()) + 1); + + return ::wcsncmp(_str.data() + i, str, n); + } + + constexpr bool operator==(const _wstring& str) const { + return compare(str) == 0; + } + + /// + /// \brief Finds the index of the first occurrence of `c` in the string + /// \param c the wchar_tacter to find + /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` + constexpr size_t find(wchar_t c, size_t i = 0) const { + if (i >= size()) { // bounds check + return size(); + } + + const wchar_t* loc = ::wcschr(_str.data() + i, c); // get location using strchr + return loc ? loc - _str.data() : size(); // return size if not found + } + + /// + /// \brief Finds the index of the first occurrence of `str` in the string. + /// \param str the string to find + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t find(const wstring& str, size_t i = 0) const { // bounds check + if (i >= size()) { // bounds check + return size(); + } + + const wchar_t* loc = ::wcsstr(_str, str); + return loc ? loc - _str : size(); + } + + /// + /// \brief Finds the index of the first occurrence of `str` in the string. + /// \param str the string to find + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t find(const wcstring& str, size_t i = 0) const { + if (i + str.size() > size()) { // bounds check + return size(); + } + + const wchar_t* loc = ::wcsstr(_str + i, str); // get location using strstr + return loc ? loc - _str : size(); // return size if not found + } + + /// + /// \brief Finds the index of the last occurrence of `c` in the string. + /// \param c the string to find + /// \param i the index to start at + /// \returns The index of `c` if it occurs in the string, otherwise returns `size()` + constexpr size_t rfind(wchar_t c, size_t i = npos) const { + if (size() == 0) { + return size(); + } + i = min(i, size() - 1); // clamp i to bounds + do { + if (_str[i] == c) { // loop backwards looking for c + return i; + } + } while (i--); + return size(); // base case + } + + /// + /// \brief Finds the index of the last occurrence of `str` in the string. + /// \param str the string to find + /// \param i the index to start at + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t rfind(const wcstring& str, size_t i = npos) const { + if (size() == 0) { + return size(); + } + const wchar_t first = str[0]; + i = min(i, size() - str.size()); + do { + if(_str[i] == first) { + if (compare(str, i) == 0) { + return i; + } + } + } while (i--); + return size(); // base case + } + + /// + /// \brief Finds the index of the last occurrence of `str` in the string. + /// \param str the string to find + /// \param i the index to start at + /// \returns The index of `str` if it occurs in the string, otherwise returns `size()` + constexpr size_t rfind(const wstring& str, size_t i = npos) const { + if (size() == 0) { + return size(); + } + const wchar_t first = str[0]; + i = min(i, size() - str.size()); + do { + if(_str[i] == first) { + if (compare(str, i) == 0) { // loop backwards looking for str + return i; + } + } + } while (i--); + return size(); // base case + } + +// Manipulation ======================================================================================================== + + /// + /// \brief Resize the string, filling additional bytes with `'\0'` + /// \param n the new size of the string + constexpr void resize(size_t n) { + size_t i = size(); + _str.reallocate(n + 1); + if (n > i) fennec::wmemset(_str.data() + i, L'\0', n - i); + } + + /// + /// \brief Copy Assignment Operator + /// \param str the string to copy + /// \returns a reference to `this` + constexpr wstring& operator=(const wcstring& str) { + if (str.size() > size()) resize(str.size()); + fennec::wmemcpy(_str.data(), str, str.size()); + _str[str.size()] = L'\0'; + return *this; + } + + /// + /// \brief Copy Assignment Operator + /// \param str the string to copy + /// \returns a reference to `this` + constexpr wstring& operator=(const wstring& str) { + if (str.size() > size()) resize(str.size()); + fennec::wmemcpy(_str.data(), str, str.size()); + _str[str.size()] = '\0'; + return *this; + } + + /// + /// \brief Move Assignment Operator + /// \param str the string to move + /// \returns a reference to `this` + constexpr wstring& operator=(wstring&& str) noexcept { + _str = fennec::move(str._str); + return *this; + } + + /// + /// \brief Retrieve a substring of a string + /// \param i the start index + /// \param n the number of wchar_tacters + /// \return + constexpr wstring substring(size_t i, size_t n = npos) const { + if (i >= size()) { + return wstring(""); + } + n = min(n, size() - i); + return wstring(_str.data() + i, n); + } + + /// + /// \brief Returns a string with `c` appended to it + /// \param c + /// \returns + constexpr wstring operator+(wchar_t c) const { + // Copy contents with one additional byte. + wstring res(_str, _str.size()); + res[size()] = c; // Set the last wchar_tacter to c + return res; + } + + constexpr wstring operator+(const wcstring& str) const { + wstring res(size() + str.size()); // Make a new string with the size of this + str + fennec::wmemcpy(&res[0], _str.data(), size()); // Copy the contents of this + fennec::wmemcpy(&res[size()], str, str.size()); // Append the contents of str + return res; + } + + constexpr wstring operator+(const wstring& str) const { + wstring res(size() + str.size()); // Make a new string with the size of this + str + fennec::wmemcpy(&res[0], _str.data(), size()); // Copy the contents of this + fennec::wmemcpy(&res[size()], str, str.size()); // Append the contents of str + return res; + } + + constexpr wstring& operator+=(wchar_t c) { + size_t x = size(); + resize(x + 1); + _str[x] = c; + return *this; + } + + constexpr wstring& operator+=(const wcstring& str) { + size_t x = size(); + resize(x + str.size()); + fennec::wmemcpy(&_str[x], str, str.size()); + return *this; + } + + constexpr wstring& operator+=(const wstring& str) { + size_t x = size(); + resize(x + str.size()); + fennec::wmemcpy(&_str[x], str, str.size()); + return *this; + } + +private: + alloc_t _str; +}; + +} + + +#endif // FENNEC_FPROC_wstringS_wstring_H diff --git a/include/fennec/lang/detail/__int.h b/include/fennec/lang/detail/__int.h index 3caed33..601ea4a 100644 --- a/include/fennec/lang/detail/__int.h +++ b/include/fennec/lang/detail/__int.h @@ -26,14 +26,7 @@ #define __PTRDIFF_TYPE__ ptrdiff_t #endif -#pragma push_macro("__cplusplus") -#undef __cplusplus #include #include -#pragma pop_macro("__cplusplus") - -#if _MSC_VER -#pragma warning(pop) -#endif #endif // FENNEC_LANG_DETAIL_INT_H diff --git a/include/fennec/lang/detail/__stdlib.h b/include/fennec/lang/detail/__stdlib.h index a3c9e9b..4614f96 100644 --- a/include/fennec/lang/detail/__stdlib.h +++ b/include/fennec/lang/detail/__stdlib.h @@ -19,20 +19,6 @@ #ifndef FENNEC_LANG_DETAIL_STDLIB_H #define FENNEC_LANG_DETAIL_STDLIB_H -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - -#pragma push_macro("__cplusplus") -#undef __cplusplus - extern "C" { #include - } -#pragma pop_macro("__cplusplus") - -#if _MSC_VER -#pragma warning(pop) -#endif #endif // FENNEC_LANG_DETAIL_STDLIB_H diff --git a/include/fennec/math/detail/__math.h b/include/fennec/math/detail/__math.h index d4b6307..88349e0 100644 --- a/include/fennec/math/detail/__math.h +++ b/include/fennec/math/detail/__math.h @@ -19,20 +19,11 @@ #ifndef FENNEC_MATH_DETAIL_MATH_H #define FENNEC_MATH_DETAIL_MATH_H -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - #pragma push_macro("__cplusplus") #undef __cplusplus #include #pragma pop_macro("__cplusplus") -#if _MSC_VER -#pragma warning(pop) -#endif - #undef div #undef acos #undef asin diff --git a/include/fennec/math/ext/constants.h b/include/fennec/math/ext/constants.h index 8394a34..9ae1a32 100644 --- a/include/fennec/math/ext/constants.h +++ b/include/fennec/math/ext/constants.h @@ -691,8 +691,4 @@ template constexpr genType log_phi() { return } -#if _MSC_VER -#pragma warning(pop) -#endif - #endif // FENNEC_MATH_EXT_CONSTANTS_H diff --git a/include/fennec/math/vector_storage.h b/include/fennec/math/vector_storage.h index 209e1bd..e34cc2a 100644 --- a/include/fennec/math/vector_storage.h +++ b/include/fennec/math/vector_storage.h @@ -615,8 +615,4 @@ struct vector_storage<4, SwizzleGenT, DataT> #pragma GCC diagnostic pop #endif -#if _MSC_VER -#pragma warning(pop) -#endif - #endif // FENNEC_MATH_VECTOR_STORAGE_H diff --git a/include/fennec/memory/allocator.h b/include/fennec/memory/allocator.h index cac1831..a1c93e7 100644 --- a/include/fennec/memory/allocator.h +++ b/include/fennec/memory/allocator.h @@ -508,12 +508,7 @@ public: return _data[i]; } - constexpr const value_t operator[](size_t i) const requires requires { is_fundamental_v == true; } { - assertd(i < size(), "Array Out of Bounds"); - return _data[i]; - } - - constexpr const value_t& operator[](size_t i) const requires requires { is_fundamental_v == false; } { + constexpr const value_t operator[](size_t i) const { assertd(i < size(), "Array Out of Bounds"); return _data[i]; } diff --git a/include/fennec/memory/detail/__string.h b/include/fennec/memory/detail/__string.h index 95dd55a..3e829ff 100644 --- a/include/fennec/memory/detail/__string.h +++ b/include/fennec/memory/detail/__string.h @@ -23,27 +23,15 @@ // see https://git.mslockbo.org/mslockbo/fennec/src/commit/0eeb7ae3cff9d78e98dc5d9fc09bcb98b10986b9 for previous // implementation -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable:4117) -#endif - #if __GNUC__ #define __OPTIMIZE__ #endif -#pragma push_macro("__cplusplus") -#undef __cplusplus #include #include -#pragma pop_macro("__cplusplus") #if __GNUC__ #undef __OPTIMIZE__ #endif -#if _MSC_VER -#pragma warning(pop) -#endif - #endif // FENNEC_MEMORY_DETAIL_MEMORY_H diff --git a/include/fennec/memory/new.h b/include/fennec/memory/new.h index 88d831f..8b67bd9 100644 --- a/include/fennec/memory/new.h +++ b/include/fennec/memory/new.h @@ -102,37 +102,4 @@ void operator delete (void* ptr, fennec::align_t, const fennec::nothrow_t&) noe void operator delete[](void* ptr, fennec::align_t, const fennec::nothrow_t&) noexcept; -// Platform specific code -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN -#include - -namespace fennec -{ - -inline size_T pagesize() { - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - return sysInfo.dwPageSize; -} - -} - -#else - -#include - -namespace fennec -{ - -inline size_t pagesize() { - return sysconf(_SC_PAGESIZE); -} - -} - -#endif - - #endif // FENNEC_MEMORY_NEW_H diff --git a/metaprogramming/CMakeLists.txt b/metaprogramming/CMakeLists.txt index d1ff56b..b2324a5 100644 --- a/metaprogramming/CMakeLists.txt +++ b/metaprogramming/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD 23) add_executable(fennec-metaprogramming main.cpp float.h integer.h) + add_custom_command( OUTPUT .metaprogramming COMMAND fennec-metaprogramming diff --git a/source/debug/assert_impl.cpp b/source/debug/assert_impl.cpp index 7d5d647..2d14aed 100644 --- a/source/debug/assert_impl.cpp +++ b/source/debug/assert_impl.cpp @@ -16,7 +16,8 @@ // along with this program. If not, see . // ===================================================================================================================== -#include +#include +//#include void __assert_callback(const char* expression, const char* file, int line, const char* function, const char* description) { @@ -27,5 +28,5 @@ void __assert_callback(const char* expression, const char* file, int line, const "At %s:%d in %s \n" "Description: %s \n", expression, file, line, function, description); - cpptrace::generate_trace(2).print(); +// cpptrace::generate_trace(2).print(); } \ No newline at end of file diff --git a/source/fproc/io/common.cpp b/source/fproc/io/common.cpp index 7a5b71f..ccf99ef 100644 --- a/source/fproc/io/common.cpp +++ b/source/fproc/io/common.cpp @@ -18,3 +18,250 @@ #include +#ifdef _WIN32 + +namespace fennec +{ + +string getcwd() { + char cstr[MAX_PATH]; + if (GetCurrentDirectory(sizeof(str), str) == 0) { + return string(""); + } + string result(cstr); + return result; +} + +} + +#else + +#include +#include + +namespace fennec +{ + +string getcwd() { + char cstr[PATH_MAX]; + if (::getcwd(cstr, sizeof(cstr)) == NULL) { + return string(""); + } + return string(cstr, strlen(cstr)); +} + +string absolute(const cstring& path) { + + // first determine if this is already an absolute path +#ifdef _WIN32 + if (path[1] == ':') { + return path; + } +#else + if (path[0] == '/') { + return path; + } +#endif + + string parse = path; + string res = getcwd(); + + size_t len = res.length(); +#ifdef _WIN32 + if (res[len - 1] == '/' || res[len - 1] == '\\') { +#else + if (res[len - 1] == '/') { +#endif + res = res.substring(0, len); + } + + while (parse.size() > 1) { + + // Parse for relative symbols + while (parse[0] == '.') { + +#ifdef _WIN32 + // up + if (parse[1] == '.' && (parse[2] == '/' || parse[2] == '\\')) { + size_t r = res.rfind('/'); + size_t l = res.rfind('\\'); + size_t size = res.size(); + if (r == size) { + size = l; + } + else if (l == size) { + size = r; + } + else { + size = max(r, l); + } + size = max(size, 1); + res = res.substring(0, size); + } + // rel + else if (parse[1] == '/' || parse[1] == '\\') { + parse = parse.substring(2); + } +#else + // up + if (parse[1] == '.' && parse[2] == '/') { + size_t r = res.rfind('/'); + res = res.substring(0, max(res.rfind('/'), static_cast(1))); + parse = parse.substring(2); + } + // rel + else if (parse[1] == '/') { + parse = parse.substring(1); + } +#endif + else { + break; + } + } + + // get the pos of the first directory separator +#ifdef _WIN32 + size_t pos = min(parse.find('\\', 1), parse.find('/', 1)); +#else + size_t pos = parse.find('/', 1); + size_t off = 0; +#endif + if (pos != parse.size()) { + if (parse[pos + 1] == '.') { + if (parse[pos + 2] == '/') { + off = 1; + } + else if (parse[pos + 2] == '.' && parse[pos + 3] == '/') { + off = 1; + } + } + else if (parse[pos + 1] == '/') { + off = 1; + } + } + + // add this part + string sub = parse.substring(0, pos); + parse = parse.substring(pos + off); + res += sub; + } + + return res; +} + +string absolute(const string& path) { + + // first determine if this is already an absolute path +#ifdef _WIN32 + if (path[1] == ':') { + return path; + } +#else + if (path[0] == '/') { + return path; + } +#endif + + string parse = path; + string res = getcwd(); + + size_t len = res.length(); +#ifdef _WIN32 + if (res[len - 1] == '/' || res[len - 1] == '\\') { +#else + if (res[len - 1] == '/') { +#endif + res = res.substring(0, len); + } + + while (parse.size() > 1) { + + // Parse for relative symbols + while (parse[0] == '.') { + +#ifdef _WIN32 + // up + if (parse[1] == '.' && (parse[2] == '/' || parse[2] == '\\')) { + size_t r = res.rfind('/'); + size_t l = res.rfind('\\'); + size_t size = res.size(); + if (r == size) { + size = l; + } + else if (l == size) { + size = r; + } + else { + size = max(r, l); + } + size = max(size, 1); + res = res.substring(0, size); + } + // rel + else if (parse[1] == '/' || parse[1] == '\\') { + parse = parse.substring(2); + } +#else + // up + if (parse[1] == '.' && parse[2] == '/') { + size_t r = res.rfind('/'); + res = res.substring(0, max(res.rfind('/'), static_cast(1))); + parse = parse.substring(2); + } + // rel + else if (parse[1] == '/') { + parse = parse.substring(1); + } +#endif + else { + break; + } + } + + // get the pos of the first directory separator +#ifdef _WIN32 + size_t pos = min(parse.find('\\', 1), parse.find('/', 1)); + size_t off = 0; + if (pos != parse.size()) { + if (parse[pos + 1] == '.') { + if (parse[pos + 2] == '/' || parse[pos + 2] == '\\') { + off = 1; + } + else if (parse[pos + 2] == '.' && (parse[pos + 3] == '/' || parse[pos + 3] == '\\')) { + off = 1; + } + } + else if (parse[pos + 1] == '/' || parse[pos + 1] == '\\') { + off = 1; + } + } +#else + size_t pos = parse.find('/', 1); + size_t off = 0; + if (pos != parse.size()) { + if (parse[pos + 1] == '.') { + if (parse[pos + 2] == '/') { + off = 1; + } + else if (parse[pos + 2] == '.' && parse[pos + 3] == '/') { + off = 1; + } + } + else if (parse[pos + 1] == '/') { + off = 1; + } + } +#endif + + // add this part + string sub = parse.substring(0, pos); + parse = parse.substring(pos + off); + res += sub; + } + + return res; +} + +#endif + +} diff --git a/source/fproc/io/file.cpp b/source/fproc/io/file.cpp index fcd29a9..b68ed8a 100644 --- a/source/fproc/io/file.cpp +++ b/source/fproc/io/file.cpp @@ -14,4 +14,827 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// ===================================================================================================================== \ No newline at end of file +// ===================================================================================================================== + +#include +#include +#include +#include + +#include + +#ifdef _WIN32 + + + +#else + +#include + +#endif + +namespace fennec +{ + +constexpr const cstring fmode_translate(uint8_t mode) { + if (not file::is_valid(mode)) { + return ""; + } + + mode &= ~fmode_wide; // Ignore wide bit + + switch (mode) { + case fmode_read: return "r"; + case fmode_write: return "a"; + +// I chose r+ because for most read/write assets in the context of the game engine will be setting files, save files, +// and editor assets, which require a complete read of the file before any writing is done + case fmode_read | fmode_write: return "r+"; + + case fmode_write | fmode_trunc: return "w"; + case fmode_read | fmode_write | fmode_trunc: return "w+"; + + case fmode_write | fmode_trunc | fmode_exclusive: return "wx"; + case fmode_read | fmode_write | fmode_trunc | fmode_exclusive: return "wx+"; + +// Binary Files + case fmode_binary | fmode_read: return "rb"; + case fmode_binary | fmode_write: return "ab"; + + case fmode_binary | fmode_read | fmode_write: return "rb+"; + + case fmode_binary | fmode_write | fmode_trunc: return "wb"; + case fmode_binary | fmode_read | fmode_write | fmode_trunc: return "wb+"; + + case fmode_binary | fmode_write | fmode_trunc | fmode_exclusive: return "wxb"; + case fmode_binary | fmode_read | fmode_write | fmode_trunc | fmode_exclusive: return "wxb+"; + + default: return ""; + } +} + +file& file::cout() { + static constexpr char path[] = "stdout"; + static file out = []() -> file { + file res; + res._mode = fmode_write; + res._handle = stdout; + res._path = string(path); + return res; + }(); + return out; +} + +file& file::cin() { + static constexpr char path[] = "stdin"; + static file out = []() -> file { + file res; + res._mode = fmode_read; + res._handle = stdin; + res._path = string(path); + return res; + }(); + return out; +} + +file& file::cerr() { + static constexpr char path[] = "stderr"; + static file out = []() -> file { + file res; + res._mode = fmode_write; + res._handle = stderr; + res._path = string(path); + return res; + }(); + return out; +} + +file::file() +: _handle(nullptr) +, _path("") +, _mode(0) +, _error(nullptr) { + +} + +file::~file() { + close(); +} + +file::file(file&& file) noexcept +: _handle(file._handle), _path(file._path), _mode(file._mode), _error(file._error) { + file._handle = nullptr; + file._error = nullptr; +} + +file& file::operator=(file&& file) noexcept { + assert(_error == nullptr, "Attempted Operation on a File in an Errored State"); + + close(); + _handle = file._handle; + _path = file._path; + _mode = file._mode; + _error = file._error; + + file._handle = nullptr; + file._path = ""; + file._error = nullptr; + + return *this; +} + +bool file::open(const cstring& filename, uint8_t mode) { + assert(_error == nullptr, "Attempted Operation on a File in an Errored State"); + + // Ensure validity of the mode + if (not is_valid(mode)) { + return true; + } + + // Close the file if already open + if (_handle) { + close(); + } + + // Attempt to open the file + _handle = fopen(filename, fmode_translate(mode)); + + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Attempt to lock the file + if (flock(fileno(_handle), LOCK_EX)) { + _error = strerror(errno); + fclose(_handle); + _handle = nullptr; + return true; + } + + _mode = mode; + _path = absolute(filename); + return false; +} + +bool file::open(const string& filename, uint8_t mode) { + assert(_error == nullptr, "Attempted Operation on a File in an Errored State"); + + // Ensure validity of the mode + if (not is_valid(mode)) { + return true; + } + + // Close the file if already open + if (_handle) { + close(); + } + + // Attempt to open the file + _handle = fopen(filename, fmode_translate(mode)); + + // Validate the file + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Attempt to lock the file + if (flock(fileno(_handle), LOCK_EX)) { + _error = strerror(errno); + fclose(_handle); + _handle = nullptr; + return true; + } + + // Set the orientation + fwide(_handle, mode & fmode_wide ? 1 : -1); + + _mode = mode; + _path = absolute(filename); + + return false; +} + +bool file::close() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + // Attempt to unlock the file + if (flock(fileno(_handle), LOCK_UN)) { + _error = strerror(errno); + return true; + } + + // Close the file and reset variables + fclose(_handle); + _mode = 0; + _handle = nullptr; + _path = ""; + + return false; +} + +bool file::commit() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + // Attempt to flush + if (fflush(_handle)) { + _error = strerror(errno); + return true; + } + + return false; +} + +bool file::erase() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + // Close the file + string path = move(_path); + if (close()) { + return true; + } + + // Erase the file + remove(path); + + return false; +} + +bool file::rename(const cstring& str) { + static const size_t page_size = pagesize(); + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + // Validate path + string fpath = absolute(str); + if (_path == fpath) { + return false; + } + + // Attempt to open the new file + FILE* fnew = fopen(fpath, "wx"); + + // Check for open failure + if (fnew == nullptr) { + _error = strerror(errno); + return true; + } + + // Reopen this file as read + _handle = freopen(nullptr, "r", _handle); + + // Check if it failed to reopen + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Initialize buffer + void* buffer = operator new(page_size, static_cast(page_size)); + + // Copy contents + size_t read = 0; + while ((read = fread(buffer, 1, page_size, _handle)) == page_size) { + if (fwrite(buffer, 1, page_size, fnew) != page_size) { + break; + } + } + + // Handle eof + if (feof(_handle)) { + fwrite(buffer, 1, read, fnew); + } + + + // Check new file for errors + if (ferror(fnew)) { + _error = strerror(errno); + fclose(fnew); + _mode = fmode_read; + return true; + } + + // Check the original file for errors + if (ferror(_handle)) { + _error = strerror(errno); + fclose(fnew); + fclose(_handle); + _handle = nullptr; + _mode = 0; + _path = ""; + return true; + } + + // Cleanup the buffer + operator delete(buffer, page_size, static_cast(page_size)); + + // Close old file + fclose(_handle); + + // Reopen the new file + _handle = freopen(&fpath[0], fmode_translate(_mode), fnew); + + // Check for open failure + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Erase the old file + remove(_path); + + // Set the new path + _path = fpath; + + return false; +} + +bool file::rename(const string& str) { + static const size_t page_size = pagesize(); + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + // Validate path + string fpath = absolute(str); + if (_path == fpath) { + return false; + } + + // Attempt to open the new file + FILE* fnew = fopen(fpath, "wx"); + + // Check for open failure + if (fnew == nullptr) { + _error = strerror(errno); + return true; + } + + // Reopen this file as read + _handle = freopen(nullptr, "r", _handle); + + // Check if it failed to reopen + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Initialize buffer + void* buffer = operator new(page_size, static_cast(page_size)); + + // Copy contents + size_t read = 0; + while ((read = fread(buffer, 1, page_size, _handle)) == page_size) { + if (fwrite(buffer, 1, page_size, fnew) != page_size) { + break; + } + } + + // Handle eof + if (feof(_handle)) { + fwrite(buffer, 1, read, fnew); + } + + // Check new file for errors + if (ferror(fnew)) { + _error = strerror(errno); + fclose(fnew); + _mode = fmode_read; + return true; + } + + // Check the original file for errors + if (ferror(_handle)) { + _error = strerror(errno); + fclose(fnew); + fclose(_handle); + _handle = nullptr; + _mode = 0; + _path = ""; + return true; + } + + // Cleanup the buffer + operator delete(buffer, page_size, static_cast(page_size)); + + // Close old file + fclose(_handle); + + // Reopen the new file + _handle = freopen(&_path[0], fmode_translate(_mode), fnew); + + // Check for open failure + if (_handle == nullptr) { + _error = strerror(errno); + return true; + } + + // Set the new path + _path = fpath; + + return false; +} + +file file::copy(const cstring& str) { + static const size_t page_size = pagesize(); + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return file(); + } + + // Validate path + string fpath = absolute(str); + if (_path == fpath) { + return file(); + } + + // Attempt to open the new file + FILE* fnew = fopen(fpath, "wx"); + + // Check for open failure + if (fnew == nullptr) { + _error = strerror(errno); + return file(); + } + + // Reopen this file as read + _handle = freopen(nullptr, "r", _handle); + + // Check if it failed to reopen + if (_handle == nullptr) { + _error = strerror(errno); + return file(); + } + + // Initialize buffer + void* buffer = operator new(page_size, static_cast(page_size)); + + // Copy contents + size_t read = 0; + while ((read = fread(buffer, 1, page_size, _handle)) == page_size) { + if (fwrite(buffer, 1, page_size, fnew) != page_size) { + break; + } + } + + // Handle eof + if (feof(_handle)) { + fwrite(buffer, 1, read, fnew); + } + + // Check new file for errors + if (ferror(fnew)) { + _error = strerror(errno); + fclose(fnew); + _mode = fmode_read; + return file(); + } + + // Check the original file for errors + if (ferror(_handle)) { + _error = strerror(errno); + fclose(fnew); + fclose(_handle); + _handle = nullptr; + _mode = 0; + _path = ""; + return file(); + } + + // Cleanup the buffer + operator delete(buffer, page_size, static_cast(page_size)); + + // Reopen the new file + _handle = freopen(nullptr, fmode_translate(_mode), _handle); + + // Check for open failure + if (_handle == nullptr) { + _error = strerror(errno); + return file(); + } + + // Set the new path + file res; + res._handle = fnew; + res._path = fpath; + res._mode = fmode_write; + + return res; +} + +file file::copy(const string& str) { + static const size_t page_size = pagesize(); + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return file(); + } + + // Validate path + string fpath = absolute(str); + if (_path == fpath) { + return file(); + } + + // Attempt to open the new file + FILE* fnew = fopen(fpath, "wx"); + + // Check for open failure + if (fnew == nullptr) { + _error = strerror(errno); + return file(); + } + + // Reopen this file as read + _handle = freopen(nullptr, "r", _handle); + + // Check if it failed to reopen + if (_handle == nullptr) { + _error = strerror(errno); + return file(); + } + + // Initialize buffer + void* buffer = operator new(page_size, static_cast(page_size)); + + // Copy contents + size_t read = 0; + while ((read = fread(buffer, 1, page_size, _handle)) == page_size) { + if (fwrite(buffer, 1, page_size, fnew) != page_size) { + break; + } + } + + // Handle eof + if (feof(_handle)) { + fwrite(buffer, 1, read, fnew); + } + + // Check new file for errors + if (ferror(fnew)) { + operator delete(buffer, page_size, static_cast(page_size)); + _error = strerror(errno); + fclose(fnew); + _mode = fmode_read; + return file(); + } + + // Check the original file for errors + if (ferror(_handle)) { + operator delete(buffer, page_size, static_cast(page_size)); + _error = strerror(errno); + fclose(fnew); + fclose(_handle); + _handle = nullptr; + _mode = 0; + _path = ""; + return file(); + } + + // Cleanup the buffer + operator delete(buffer, page_size, static_cast(page_size)); + + // Reopen the new file + _handle = freopen(nullptr, fmode_translate(_mode), _handle); + + // Check for open failure + if (_handle == nullptr) { + _error = strerror(errno); + return file(); + } + + // Set the new path + file res; + res._handle = fnew; + res._path = fpath; + res._mode = fmode_write; + + return res; +} + +size_t file::get_pos() const { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return npos; + } + + return ftell(_handle); +} + +bool file::set_pos(size_t i) { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + if (fseek(_handle, i, SEEK_SET)) { + _error = strerror(errno); + return true; + } + + return false; +} + +bool file::rewind() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + ::rewind(_handle); + if (ferror(_handle)) { + _error = strerror(errno); + return true; + } + + return false; +} + +bool file::eof() const { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + return feof(_handle); +} + +char file::getc() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(not _mode & fmode_wide, "Attempted Byte Operation on Wide File"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + return fgetc(_handle); +} + +wchar_t file::getwc() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(_mode & fmode_wide, "Attempted Wide Operation on Byte File"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + return fgetwc(_handle); +} + +size_t file::read(void* data, size_t size, size_t n) { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + size_t read = fread(data, size, n, _handle); + if (read != size && ferror(_handle)) { + _error = strerror(errno); + } + + return read; +} + +string file::getline() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(not _mode & fmode_wide, "Attempted Byte Operation on Wide File"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + // Read the next line; + char* line = nullptr; + size_t size = 0; + size_t read = ::getline(&line, &size, _handle); + if (read == npos && ferror(_handle)) { + _error = strerror(errno); + if (line) free(line); + return string(""); + } + + string res = string(line, read); + free(line); + return res; +} + +wstring file::getwline() { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(_mode & fmode_wide, "Attempted Wide Operation on Byte File"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + // Read the next line; + wchar_t arr[257] = { L'\0' }; + wcstring buff = arr; + wstring res{L""}; + + // read until first newline or end of file + while (fgetws(arr, 257, _handle)) { + res += wcstring(arr, buff.length()); + if (buff.length() < 256 || buff[256] == L'\n') { + return res; + } + } + + _error = strerror(errno); + return wstring(L""); +} + +bool file::putc(char c) { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(not _mode & fmode_wide, "Attempted Byte Operation on Wide File"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + int res; + if ((res = fputc(c, _handle)) != c && res != EOF) { + _error = strerror(errno); + return true; + } + + return false; +} + +bool file::putwc(wchar_t c) { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + assert(_mode & fmode_wide, "Attempted Wide Operation on Byte File"); + + // Check if there is a file + if (_handle == nullptr) { + return false; + } + + int res; + if ((res = fputc(c, _handle)) != c && res != EOF) { + _error = strerror(errno); + return true; + } + + return false; +} + +size_t file::write(const void* data, size_t size, size_t n) { + assert(_error == nullptr, "Attempted an Operation on a File in an Errored State"); + + // Check if there is a file + if (_handle == nullptr) { + return 0; + } + + size_t r = fwrite(data, size, n, _handle); + + if (r != size && ferror(_handle)) { + _error = strerror(errno); + return 0; + } + + return r; +} + +} diff --git a/source/memory/new.cpp b/source/memory/new.cpp index 3e9b936..4e4dcfd 100644 --- a/source/memory/new.cpp +++ b/source/memory/new.cpp @@ -44,6 +44,19 @@ void* operator new[](fennec::size_t size, fennec::align_t align) void* operator new (fennec::size_t size, fennec::align_t align, const fennec::nothrow_t&) { return _aligned_malloc(static_cast(align), size); } void* operator new[](fennec::size_t size, fennec::align_t align, const fennec::nothrow_t&) { return _aligned_malloc(static_cast(align), size); } +#define WIN32_LEAN_AND_MEAN +#include + +namespace fennec +{ + + inline size_T pagesize() { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; + } + +} #else void operator delete (void* ptr) noexcept { ::free(ptr); } @@ -76,4 +89,14 @@ void operator delete[](void* ptr, const fennec::nothrow_t&) noe void operator delete (void* ptr, fennec::size_t, const fennec::nothrow_t&) noexcept { ::free(ptr); } void operator delete[](void* ptr, fennec::size_t, const fennec::nothrow_t&) noexcept { ::free(ptr); } -#endif +#include + +namespace fennec +{ + +size_t pagesize() { + return sysconf(_SC_PAGESIZE); +} + +} +#endif \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5bbaddf..1702d3d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.30) project(fennec-test) -set(CMAKE_CXX_STANDARD 26) -set(CMAKE_C_STANDARD 26) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_C_STANDARD 23) add_executable(fennec-test main.cpp test.h @@ -26,7 +26,9 @@ add_executable(fennec-test main.cpp tests/fproc/test_io.h ) -target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}" + FENNEC_BUILD_NAME="${FENNEC_BUILD_NAME}" +) target_link_libraries(fennec-test PRIVATE fennec diff --git a/test/tests/fproc/test_io.h b/test/tests/fproc/test_io.h index badc0a4..1e7b7c0 100644 --- a/test/tests/fproc/test_io.h +++ b/test/tests/fproc/test_io.h @@ -22,6 +22,7 @@ #include "../../test.h" #include +#include namespace fennec { @@ -30,7 +31,64 @@ namespace test { inline void fennec_test_fproc_io() { - fennec_test_run(getcwd(), string(FENNEC_TEST_CWD)); + + fennec_test_section("utilities"); + + fennec_test_spacer(1); + + fennec_test_run(getcwd(), string(FENNEC_TEST_CWD)); + fennec_test_run(absolute("../" FENNEC_BUILD_NAME "/./test.sh"), string(FENNEC_TEST_CWD) + "/test.sh"); + fennec_test_run(absolute("./test/../test.sh"), string(FENNEC_TEST_CWD) + "/test.sh"); + + fennec_test_spacer(2); + + + fennec_test_section("file streams"); + + fennec_test_spacer(1); + + file stream; + char data[] = "Hello World!"; + fennec_test_run(stream.open("./test.txt", fmode_write | fmode_trunc), false); + fennec_test_run(stream.write(data), sizeof(data)); + fennec_test_run(stream.close(), false); + + fennec_test_spacer(1); + + fennec_test_run(stream.open("./test.txt", fmode_read), false); + fennec_test_run(stream.read(data), sizeof(data)); + fennec_test_run(cstring(data), cstring("Hello World!")); + + fennec_test_spacer(1); + + fennec_test_run(stream.open("./test.txt", fmode_read), false); + fennec_test_run((stream = stream.copy("./copy.txt")).is_open(), true); + fennec_test_run(stream.open("./copy.txt", fmode_read), false); + fennec_test_run(stream.read(data), sizeof(data)); + fennec_test_run(cstring(data), cstring("Hello World!")); + fennec_test_run(stream.erase(), false); + + fennec_test_spacer(1); + + fennec_test_run(stream.open("./test.txt", fmode_read), false); + fennec_test_run(stream.rename("./rename.txt"), false); + fennec_test_run(stream.open("./rename.txt", fmode_read), false); + fennec_test_run(stream.read(data), sizeof(data)); + fennec_test_run(cstring(data), cstring("Hello World!")); + fennec_test_run(stream.erase(), false); + + fennec_test_spacer(2); + + + fennec_test_section("std streams"); + + fennec_test_spacer(1); + + fennec_test_run(file::cout().write("Hello World!"), sizeof("Hello World!")); + fennec_test_run(file::cerr().write("Hello World!"), sizeof("Hello World!")); + + fennec_test_spacer(2); + } }