- Removed a bug with attempt to include pure c headers

- Added some more information about the license
 - fennec::file implementation
This commit is contained in:
2025-07-14 05:11:52 -04:00
parent 5e0dc78210
commit 6ae682aff6
26 changed files with 2229 additions and 245 deletions

View File

@@ -9,16 +9,14 @@ add_subdirectory(external/sdl)
# CppTrace is a dependency of the project, added as a git submodule # CppTrace is a dependency of the project, added as a git submodule
add_subdirectory(external/cpptrace) add_subdirectory(external/cpptrace)
set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 26) set(CMAKE_C_STANDARD 23)
# find dependencies # find dependencies
find_package(Doxygen) find_package(Doxygen)
# any necessary include directories # any necessary include directories
include_directories(include) include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(external/cpptrace/include)
include_directories(external/sdl/include)
# Metaprogramming is a dependency for generating various type info before compilation of the engine. # Metaprogramming is a dependency for generating various type info before compilation of the engine.
add_subdirectory(metaprogramming) 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_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib/${FENNEC_BUILD_NAME})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/${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 add_library(fennec STATIC
# CORE ================================================================================================================= # 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 # add metaprogramming templates as a dependency and also force documentation to be generated when fennec is compiled
if(DOXYGEN_FOUND) if(DOXYGEN_FOUND)
add_dependencies(fennec fennecdocs metaprogramming SDL3-shared cpptrace::cpptrace) add_dependencies(fennec fennecdocs metaprogramming) # SDL3-shared cpptrace::cpptrace)
else() else()
add_dependencies(fennec metaprogramming SDL3-shared cpptrace::cpptrace) add_dependencies(fennec metaprogramming) # SDL3-shared cpptrace::cpptrace)
endif() endif()
# Compiler Warning Flags # Compiler Warning Flags
@@ -143,10 +144,13 @@ if(MSVC)
add_compile_options("/W4" "/WX") # All MSVC Warnings throw as Errors add_compile_options("/W4" "/WX") # All MSVC Warnings throw as Errors
else() else()
add_compile_options("-Wall" "-Wextra" "-pedantic" "-Werror") # All gcc/etc. Warnings throw as errors 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() endif()
target_compile_options(fennec PUBLIC "-mavx" "-mavx2" "-mavx512f") # SIMD Instructions, automatic vectorization will occur 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 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. # 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 # 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) target_link_libraries(fennec PRIVATE SDL3-shared cpptrace::cpptrace)
# add the test suite as a sub-project
add_subdirectory(test)
# DOXYGEN ============================================================================================================== # DOXYGEN ==============================================================================================================
# https://vicrucann.github.io/tutorials/quick-cmake-doxygen/ # https://vicrucann.github.io/tutorials/quick-cmake-doxygen/

View File

@@ -6,7 +6,7 @@
1. [Introduction](#introduction) 1. [Introduction](#introduction)
2. [TODO](#todo) 2. [TODO](#todo)
1. [Security Ramblings](#security-ramblings) 1. [Security Ramblings](#file-security-ramblings)
3. [C++ Language](#c-language-library-lang) 3. [C++ Language](#c-language-library-lang)
4. [Math Library](#math-library-math) 4. [Math Library](#math-library-math)
5. [Memory Library](#memory-library-memory) 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 Physics (`physics2d`)
- 2D & 3D Audio (`audio`) - 2D & 3D Audio (`audio`)
### File Security Ramblings: ### File Security Ramblings:
Windows is starting to piss me off, so I am considering dropping official support for MSVC. MinGW and Cygwin 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 interface confirms overwrite action
- Application writes to the file after confirmation - 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). usage of the file. This is called TOCTOU (time of check, time of use).
This issue can be solved using `fopen("<file>", "w+")`, however this specific behaviour is not intuitive to those first This issue can be solved using `fopen("<file>", "a+")` and `ftell`, however this specific behaviour is not intuitive to
learning how to work with file systems. We can attempt to abstract this away with another wrapper, or simply write those first learning how to work with file systems. We can attempt to abstract this away with another wrapper, or simply
the file structure to handle this behaviour properly. The downside to this method overall is that it will break 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 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 `'+'` 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. 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. 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 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 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 `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, 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, 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 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. 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. 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: We need to be able to handle the following types of files:
- Assets, such as scenes, audio, textures, metadata, meshes, etc. - Assets, such as scenes, audio, textures, metadata, meshes, etc.
- Save files, setting files, 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. 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. 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, 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 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. that are continuous and too large to be kept around in memory, such as video formats.
Perhaps the following conventions: Perhaps the following conventions:
@@ -120,23 +121,41 @@ Perhaps the following conventions:
- Stream Asset - Stream Asset
- Resource - 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. 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. 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 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. 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 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. 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`) ## C++ Language Library (`lang`)
Implement header files for standard functions relating to the C++ Language. 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. So far this is implemented on an as-needed basis. A full implementation should be worked on continuously.

View File

@@ -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, 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 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 appropriate standing will reach out to me. Eventually, with interest, I may reach out on my own terms to negotiate usage
educative purposes at DigiPen while retaining their license on student work. 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 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. willing to work with educational institutes to protect both fennec and student work in accordance to university policy.

View File

@@ -21,40 +21,13 @@
#include <fennec/fproc/strings/string.h> #include <fennec/fproc/strings/string.h>
#ifdef _WIN32
namespace fennec namespace fennec
{ {
inline string getcwd() { string getcwd();
char cstr[MAX_PATH]; string absolute(const cstring& path);
if (GetCurrentDirectory(sizeof(str), str) == 0) { string absolute(const string& path);
return string("");
}
return string(cstr);
}
} }
#else
#include <unistd.h>
#include <linux/limits.h>
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 #endif // FENNEC_FPROC_IO_COMMON_H

View File

@@ -19,18 +19,6 @@
#ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H
#define 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 <stdio.h> #include <stdio.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -19,77 +19,259 @@
#ifndef FENNEC_FPROC_IO_FILE_H #ifndef FENNEC_FPROC_IO_FILE_H
#define FENNEC_FPROC_IO_FILE_H #define FENNEC_FPROC_IO_FILE_H
#include <asm-generic/errno-base.h>
#include <fennec/fproc/strings/cstring.h> #include <fennec/fproc/strings/cstring.h>
#include <fennec/fproc/strings/string.h> #include <fennec/fproc/strings/string.h>
#include <fennec/fproc/strings/wstring.h>
struct FILE;
namespace fennec namespace fennec
{ {
/// ///
/// \brief Mode flags for opening a file /// \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
/// <table width="100%" class="fieldtable" id="table_fennec_fproc_io_fmode">
/// <tr><th style="vertical-align: top">Flags
/// <th style="vertical-align: top">Description
///
/// <tr><td style="vertical-align: top">`read`
/// <td style="vertical-align: top">Opens file as read-only, reading from start
///
/// <tr><td style="vertical-align: top">`write`
/// <td style="vertical-align: top">Opens file as write-only, writing to end
///
/// <tr><td style="vertical-align: top">`read | write`
/// <td style="vertical-align: top">Opens file as read-write, reading from start
///
/// <tr><td style="vertical-align: top">`write | trunc`
/// <td style="vertical-align: top">Opens file as write-only, destroying contents
///
/// <tr><td style="vertical-align: top">`read | write | trunc`
/// <td style="vertical-align: top">Opens file as read-write, destroying contents
/// </table>
enum fmode_ : uint8_t
{ {
read = 0b00000001 fmode_read = 0b00000001 ///< Opens file for reading
, write = 0b00000010 , fmode_write = 0b00000010 ///< Opens file for writing
, append = 0b00000100 , fmode_trunc = 0b00000100 ///< Contents of the file will be destroyed, only compatible with write enabled modes
, noexists = 0b00001000 , 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 class file
{ {
public: 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(); file();
///
/// \brief default destructor, cleans up an open stream
~file(); ~file();
/// ///
/// \brief Opens a file at the provided path using the provided mode /// \brief move constructor
/// \param path the path to the provided file /// \param file the stream to take ownership of
/// \param mode the flags for opening the file file(file&& file) noexcept;
file(const cstring& path, fmode mode);
file(const string& path, fmode mode);
file(file&& file); file& operator=(file&& file) noexcept;
// don't allow copying streams
file(const file&) = delete; 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 ========================================================================================================= // File Access =========================================================================================================
void open(const cstring& filename, fmode mode); ///
void open(const string& filename, fmode mode); /// \brief open a file
void close(); /// \param path the path to the file
void commit(); /// \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 ===================================================================================================== // File Operations =====================================================================================================
void erase(); ///
void rename(const cstring& str); /// \brief closes the stream and erases the file
void rename(const string& str); /// \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 ==================================================================================================== // File Positioning ====================================================================================================
size_t get_pos(); size_t get_pos() const;
void set_pos(size_t i); bool set_pos(size_t i);
void rewind(); bool rewind();
bool eof() const;
// Read Operations =====================================================================================================
char getc();
wchar_t getwc();
size_t read(void* data, size_t size, size_t n);
template<typename T>
size_t read(T* data, size_t n) {
return read(static_cast<void*>(data), sizeof(T), n);
}
template<typename T, size_t n>
size_t read(T (&data)[n]) {
return read(static_cast<void*>(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<typename T>
size_t write(const T* data, size_t n) {
return write(static_cast<const void*>(data), sizeof(T), n);
}
template<typename T, size_t n>
size_t write(const T (&data)[n]) {
return write(static_cast<const void*>(data), sizeof(T), n);
}
// Error Handling ====================================================================================================== // Error Handling ======================================================================================================
bool check_error(); const char* get_error() const { return _error; }
string get_error(); void clear_error() { _error = nullptr; }
void clear_error();
//
private: private:
FILE* _handle; FILE* _handle;
string _path; string _path;
fmode _mode; uint8_t _mode;
char* _error;
}; };
} }

View File

@@ -19,18 +19,7 @@
#ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H
#define 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 <ctype.h> #include <ctype.h>
#pragma pop_macro("__cplusplus") #include <wctype.h>
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -19,18 +19,6 @@
#ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H
#define 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 <locale.h> #include <locale.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H #endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -75,7 +75,33 @@ public:
/// \details adds additional character for null termination. /// \details adds additional character for null termination.
constexpr _string(char c, size_t n) constexpr _string(char c, size_t n)
: _str(n + 1) { : _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<size_t n>
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 /// \brief String Copy Constructor
/// \param str the string to copy /// \param str the string to copy
constexpr _string(const string& str) constexpr _string(const string& str)
: _string(str, str.size() - 1) { : _string(str, str.size()) {
} }
constexpr _string(_string&& str) noexcept constexpr _string(_string&& str) noexcept
@@ -112,6 +138,10 @@ public:
return _str.capacity() - 1; return _str.capacity() - 1;
} }
constexpr bool empty() const {
return size() == 0;
}
// Access ============================================================================================================== // Access ==============================================================================================================
@@ -187,16 +217,16 @@ public:
} }
/// ///
/// \brief Finds the index of the first occurrence of `x` in the string /// \brief Finds the index of the first occurrence of `c` in the string
/// \param x the character to find /// \param c the character to find
/// \returns The index of `x` if it occurs in the string, otherwise returns `size()` /// \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 { constexpr size_t find(char c, size_t i = 0) const {
if (i >= size()) { // bounds check if (i >= size()) { // bounds check
return size(); return size();
} }
const char* loc = ::strchr(_str + i, c); // get location using strchr const char* loc = ::strchr(_str.data() + i, c); // get location using strchr
return loc ? loc - _str : size(); // return size if not found return loc ? loc - _str.data() : size(); // return size if not found
} }
/// ///
@@ -293,7 +323,18 @@ public:
constexpr void resize(size_t n) { constexpr void resize(size_t n) {
size_t i = size(); size_t i = size();
_str.reallocate(n + 1); _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 /// \param str the string to copy
/// \returns a reference to `this` /// \returns a reference to `this`
constexpr string& operator=(const string& str) { constexpr string& operator=(const string& str) {
if (str.size() > size()) resize(str.size()); resize(str.size());
fennec::memcpy(_str, str, str.size()); fennec::memcpy(_str.data(), str, str.size());
_str[str.size()] = '\0'; _str[str.size()] = '\0';
return *this; return *this;
} }
@@ -321,10 +362,12 @@ public:
/// \param i the start index /// \param i the start index
/// \param n the number of characters /// \param n the number of characters
/// \return /// \return
constexpr string substring(size_t i, size_t n = size()) const { constexpr string substring(size_t i, size_t n = npos) const {
assert(i < size(), "Array Out of Bounds"); if (i >= size()) {
return string("");
}
n = min(n, size() - i); 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 { constexpr string operator+(const cstring& str) const {
string res(size() + str.size()); // Make a new string with the size of this + str 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 fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
return res; return res;
} }
constexpr string operator+(const string& str) const { constexpr string operator+(const string& str) const {
string res(size() + str.size()); // Make a new string with the size of this + str 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 fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
return res; return res;
} }

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_STRINGS_wcstring_H
#define FENNEC_FPROC_STRINGS_wcstring_H
#include <fennec/fproc/strings/detail/__ctype.h>
#include <fennec/memory/detail/__string.h>
#include <fennec/lang/assert.h>
#include <fennec/math/common.h>
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<size_t n>
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<size_t n>
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<size_t n>
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<size_t n>
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_wstringS_wstring_H
#define FENNEC_FPROC_wstringS_wstring_H
#include <fennec/fproc/strings/detail/__ctype.h>
#include <fennec/fproc/strings/wcstring.h>
#include <fennec/lang/assert.h>
#include <fennec/memory/allocator.h>
#include <fennec/memory/common.h>
#include <fennec/math/common.h>
namespace fennec
{
// Forward def
template<typename AllocT = allocator<wchar_t>> 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<typename AllocT>
struct _wstring
{
public:
static constexpr size_t npos = -1;
using alloc_t = allocation<wchar_t, AllocT>;
// 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<size_t n>
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

View File

@@ -26,14 +26,7 @@
#define __PTRDIFF_TYPE__ ptrdiff_t #define __PTRDIFF_TYPE__ ptrdiff_t
#endif #endif
#pragma push_macro("__cplusplus")
#undef __cplusplus
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_LANG_DETAIL_INT_H #endif // FENNEC_LANG_DETAIL_INT_H

View File

@@ -19,20 +19,6 @@
#ifndef FENNEC_LANG_DETAIL_STDLIB_H #ifndef FENNEC_LANG_DETAIL_STDLIB_H
#define 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 <stdlib.h> #include <stdlib.h>
}
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_LANG_DETAIL_STDLIB_H #endif // FENNEC_LANG_DETAIL_STDLIB_H

View File

@@ -19,20 +19,11 @@
#ifndef FENNEC_MATH_DETAIL_MATH_H #ifndef FENNEC_MATH_DETAIL_MATH_H
#define 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") #pragma push_macro("__cplusplus")
#undef __cplusplus #undef __cplusplus
#include <math.h> #include <math.h>
#pragma pop_macro("__cplusplus") #pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#undef div #undef div
#undef acos #undef acos
#undef asin #undef asin

View File

@@ -691,8 +691,4 @@ template<typename genType> constexpr genType log_phi() { return
} }
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_MATH_EXT_CONSTANTS_H #endif // FENNEC_MATH_EXT_CONSTANTS_H

View File

@@ -615,8 +615,4 @@ struct vector_storage<4, SwizzleGenT, DataT>
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_MATH_VECTOR_STORAGE_H #endif // FENNEC_MATH_VECTOR_STORAGE_H

View File

@@ -508,12 +508,7 @@ public:
return _data[i]; return _data[i];
} }
constexpr const value_t operator[](size_t i) const requires requires { is_fundamental_v<value_t> == true; } { constexpr const value_t operator[](size_t i) const {
assertd(i < size(), "Array Out of Bounds");
return _data[i];
}
constexpr const value_t& operator[](size_t i) const requires requires { is_fundamental_v<value_t> == false; } {
assertd(i < size(), "Array Out of Bounds"); assertd(i < size(), "Array Out of Bounds");
return _data[i]; return _data[i];
} }

View File

@@ -23,27 +23,15 @@
// see https://git.mslockbo.org/mslockbo/fennec/src/commit/0eeb7ae3cff9d78e98dc5d9fc09bcb98b10986b9 for previous // see https://git.mslockbo.org/mslockbo/fennec/src/commit/0eeb7ae3cff9d78e98dc5d9fc09bcb98b10986b9 for previous
// implementation // implementation
#if _MSC_VER
#pragma warning(push)
#pragma warning(disable:4117)
#endif
#if __GNUC__ #if __GNUC__
#define __OPTIMIZE__ #define __OPTIMIZE__
#endif #endif
#pragma push_macro("__cplusplus")
#undef __cplusplus
#include <string.h> #include <string.h>
#include <wchar.h> #include <wchar.h>
#pragma pop_macro("__cplusplus")
#if __GNUC__ #if __GNUC__
#undef __OPTIMIZE__ #undef __OPTIMIZE__
#endif #endif
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_MEMORY_DETAIL_MEMORY_H #endif // FENNEC_MEMORY_DETAIL_MEMORY_H

View File

@@ -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; void operator delete[](void* ptr, fennec::align_t, const fennec::nothrow_t&) noexcept;
// Platform specific code
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace fennec
{
inline size_T pagesize() {
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize;
}
}
#else
#include <unistd.h>
namespace fennec
{
inline size_t pagesize() {
return sysconf(_SC_PAGESIZE);
}
}
#endif
#endif // FENNEC_MEMORY_NEW_H #endif // FENNEC_MEMORY_NEW_H

View File

@@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD 23)
add_executable(fennec-metaprogramming main.cpp add_executable(fennec-metaprogramming main.cpp
float.h float.h
integer.h) integer.h)
add_custom_command( add_custom_command(
OUTPUT .metaprogramming OUTPUT .metaprogramming
COMMAND fennec-metaprogramming COMMAND fennec-metaprogramming

View File

@@ -16,7 +16,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
// ===================================================================================================================== // =====================================================================================================================
#include <cpptrace/cpptrace.hpp> #include <stdio.h>
//#include <cpptrace/cpptrace.hpp>
void __assert_callback(const char* expression, const char* file, int line, const char* function, const char* description) 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" "At %s:%d in %s \n"
"Description: %s \n", "Description: %s \n",
expression, file, line, function, description); expression, file, line, function, description);
cpptrace::generate_trace(2).print(); // cpptrace::generate_trace(2).print();
} }

View File

@@ -18,3 +18,250 @@
#include <fennec/fproc/io/common.h> #include <fennec/fproc/io/common.h>
#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 <unistd.h>
#include <linux/limits.h>
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<size_t>(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<size_t>(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
}

View File

@@ -14,4 +14,827 @@
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
// ===================================================================================================================== // =====================================================================================================================
#include <stdlib.h>
#include <errno.h>
#include <fennec/fproc/io/file.h>
#include <fennec/fproc/io/common.h>
#include <stdio.h>
#ifdef _WIN32
#else
#include <sys/file.h>
#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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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;
}
}

View File

@@ -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<size_t>(align), size); } void* operator new (fennec::size_t size, fennec::align_t align, const fennec::nothrow_t&) { return _aligned_malloc(static_cast<size_t>(align), size); }
void* operator new[](fennec::size_t size, fennec::align_t align, const fennec::nothrow_t&) { return _aligned_malloc(static_cast<size_t>(align), size); } void* operator new[](fennec::size_t size, fennec::align_t align, const fennec::nothrow_t&) { return _aligned_malloc(static_cast<size_t>(align), size); }
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace fennec
{
inline size_T pagesize() {
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize;
}
}
#else #else
void operator delete (void* ptr) noexcept { ::free(ptr); } 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); }
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 <unistd.h>
namespace fennec
{
size_t pagesize() {
return sysconf(_SC_PAGESIZE);
}
}
#endif

View File

@@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.30) cmake_minimum_required(VERSION 3.30)
project(fennec-test) project(fennec-test)
set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 26) set(CMAKE_C_STANDARD 23)
add_executable(fennec-test main.cpp add_executable(fennec-test main.cpp
test.h test.h
@@ -26,7 +26,9 @@ add_executable(fennec-test main.cpp
tests/fproc/test_io.h 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 target_link_libraries(fennec-test PRIVATE
fennec fennec

View File

@@ -22,6 +22,7 @@
#include "../../test.h" #include "../../test.h"
#include <fennec/fproc/io/common.h> #include <fennec/fproc/io/common.h>
#include <fennec/fproc/io/file.h>
namespace fennec namespace fennec
{ {
@@ -30,7 +31,64 @@ namespace test
{ {
inline void fennec_test_fproc_io() { 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);
} }
} }