- 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
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/

View File

@@ -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
@@ -81,9 +82,9 @@ The crux of this issue falls at the following specific behaviour:
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("<file>", "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
This issue can be solved using `fopen("<file>", "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.
@@ -127,16 +128,34 @@ We could also declare the file interface extern so that only internal files know
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.
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.
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.

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,
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.

View File

@@ -21,40 +21,13 @@
#include <fennec/fproc/strings/string.h>
#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 <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

View File

@@ -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 <stdio.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -19,77 +19,259 @@
#ifndef 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/string.h>
struct FILE;
#include <fennec/fproc/strings/wstring.h>
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
/// <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
, 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<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 ======================================================================================================
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;
};
}

View File

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

View File

@@ -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 <locale.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -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<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
/// \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;
}

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
#endif
#pragma push_macro("__cplusplus")
#undef __cplusplus
#include <stddef.h>
#include <stdint.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_LANG_DETAIL_INT_H

View File

@@ -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 <stdlib.h>
}
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_LANG_DETAIL_STDLIB_H

View File

@@ -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 <math.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#undef div
#undef acos
#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

View File

@@ -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

View File

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

View File

@@ -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 <string.h>
#include <wchar.h>
#pragma pop_macro("__cplusplus")
#if __GNUC__
#undef __OPTIMIZE__
#endif
#if _MSC_VER
#pragma warning(pop)
#endif
#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;
// 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

View File

@@ -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

View File

@@ -16,7 +16,8 @@
// 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)
{
@@ -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();
}

View File

@@ -18,3 +18,250 @@
#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

@@ -15,3 +15,826 @@
// You should have received a copy of the GNU General Public License
// 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); }
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
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); }
#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)
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

View File

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