M core/CMakeLists.txt +1 -1
@@ 22,6 22,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_F
add_library(core STATIC ${core_src})
-set_property(TARGET core PROPERTY CXX_STANDARD 20)
+set_property(TARGET core PROPERTY CXX_STANDARD 23)
target_include_directories(core PUBLIC "${CMAKE_CURRENT_LIST_DIR}")
A => core/core/exceptions/error_linux.cpp +24 -0
@@ 0,0 1,24 @@
+#if defined(__linux) || defined(__APPLE__)
+
+#include "pch.h"
+
+#include <core/exceptions/error_linux.h>
+#include <string.h>
+
+namespace core
+{
+
+std::string translate_system_error(int error_code)
+{
+ char error_buffer[256];
+ return strerror_r(error_code, error_buffer, sizeof(error_buffer));
+}
+
+std::string translate_system_error()
+{
+ return translate_system_error(errno);
+}
+
+
+}
+#endif
A => core/core/exceptions/error_linux.h +13 -0
@@ 0,0 1,13 @@
+#pragma once
+
+#if defined(__linux) || defined(__APPLE__)
+
+namespace core
+{
+
+std::string translate_system_error(int error_code);
+std::string translate_system_error();
+
+}
+
+#endif
A => core/core/exceptions/error_win.cpp +41 -0
@@ 0,0 1,41 @@
+#if defined(_WIN32)
+
+#include "pch.h"
+
+#include <core/exceptions/error_win.h>
+#include <string>
+
+namespace core
+{
+
+std::string translate_system_error(DWORD error_code)
+{
+ LPTSTR string = nullptr;
+
+ DWORD result = FormatMessage(
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, // unused with FORMAT_MESSAGE_FROM_SYSTEM
+ error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ reinterpret_cast<LPTSTR>(&string),
+ 0, // minimum size for output buffer
+ NULL
+ );
+
+ if (result == 0) {
+ return std::to_string(static_cast<unsigned>(error_code));
+ }
+
+ std::string error_string = string;
+ LocalFree(string);
+
+ return error_string;
+}
+
+std::string translate_system_error()
+{
+ return translate_system_error(GetLastError());
+}
+
+}
+#endif
A => core/core/exceptions/error_win.h +20 -0
@@ 0,0 1,20 @@
+#pragma once
+
+#if defined(_WIN32)
+
+#include <windows.h>
+
+namespace core
+{
+
+std::string translate_system_error(DWORD error_code);
+std::string translate_system_error();
+
+inline std::string translate_system_error(int error_code)
+{
+ return translate_system_error(static_cast<DWORD>(error_code));
+}
+
+}
+
+#endif
A => core/core/exceptions/exception.cpp +12 -0
@@ 0,0 1,12 @@
+#include "pch.h"
+
+#include <core/exceptions/exception.h>
+
+namespace core
+{
+
+Exception::~Exception()
+{
+}
+
+}
M core/core/exceptions/exception.h +5 -5
@@ 1,5 1,7 @@
#pragma once
+#include <stdexcept>
+
namespace core
{
@@ 7,12 9,10 @@ namespace core
/// @{
/// Base class for exceptions.
-/// TODO: this type isn't noexcept copy constructible and thus may cause program termination if that happens while rethrown.
-/// It is more tricky since the std::runtime_error isn't wide string compatible so that can't be the base class.
-struct Exception
+struct Exception : std::runtime_error
{
- Exception(const std::string &msg) : message(msg) {}
- std::string message;
+ Exception(const std::string &msg) : runtime_error(msg) {}
+ virtual ~Exception() override;
};
/// @}
M core/core/io/file_helpers.cpp +135 -19
@@ 2,14 2,19 @@
#include <algorithm>
#include <core/io/file_helpers.h>
+#include <core/io/file_reader.h>
+#include <core/io/file_writer.h>
+#include <core/io/path.h>
+#include <fstream>
#include <sstream>
namespace core
{
-bool match_include_dir_and_file(const std::string &file, const std::vector<std::string> &include_dirs, std::string &result)
+bool match_include_dir_and_file(const std::string &filename, const std::vector<std::string> &include_dirs, std::string &result)
{
std::vector<char> temp_string;
+ std::string error;
for (auto &dir : include_dirs) {
temp_string.clear();
// add dir
@@ 20,13 25,15 @@ bool match_include_dir_and_file(const st
temp_string.push_back('/');
// add file
- temp_string.insert(temp_string.end(), file.begin(), file.end());
+ temp_string.insert(temp_string.end(), filename.begin(), filename.end());
// null terminate
temp_string.push_back(0);
// check if file exists
- if (file_exists(temp_string.data())) {
+ bool exists = false;
+ bool success = file_exists(temp_string.data(), exists, error);
+ if (success && exists) {
result = temp_string.data();
return true;
}
@@ 34,31 41,140 @@ bool match_include_dir_and_file(const st
return false;
}
-std::string base_name(const std::string &filename)
+bool make_directories(const std::string &dir, std::string &error)
+{
+ if (dir.empty()) {
+ return true;
+ }
+
+ // check if already there
+ bool exists = false;
+ bool success = dir_exists(dir, exists, error);
+ if (success && exists) {
+ return true;
+ }
+
+ // make the parent directories first
+ success = make_directories(path_pop(dir), error);
+ if (!success) {
+ return false;
+ }
+
+ // try to make this one
+ success = make_directory(dir, error);
+ return success;
+}
+
+bool file_size(const std::string &filename, size_t &size, std::string &error)
{
- size_t pos = filename.rfind(".");
- // early out if no punctual character was found
- if (pos == std::string::npos)
- return filename;
+ std::ifstream file;
+ // open file and place read offset at end to measure size
+ #if defined(_MSC_VER)
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ std::stringstream ss;
+ ss << "Filename '" << filename << "' can't be converted from utf8 to wide string";
+ error = ss.str();
+ return false;
+ }
+ file.open(wide_filename, std::ios::in | std::ios::ate | std::ios::binary);
+ #elif defined(__GNUC__)
+ file.open(filename, std::ios::in | std::ios::ate | std::ios::binary);
+ #else
+ #error "Platform not supported"
+ #endif
+ if (!file.is_open()) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << '\'';
+ error = ss.str();
+ return false;
+ }
- return filename.substr(0, pos);
+ // get file size
+ size = static_cast<size_t>(file.tellg());
+ file.close();
+
+ return true;
}
-std::string file_extension(const std::string &filename)
+bool load_file(const std::string &filename, std::vector<uint8_t> &contents, std::string &error)
{
- size_t pos = filename.rfind(".");
- // early out if no punctual character was found
- if (pos == std::string::npos)
- return std::string();
+ FileReader reader;
+ if (!reader.open(filename, error)) {
+ return false;
+ }
+ size_t size = reader.size();
+ contents.resize(size);
+ if (!reader.read(contents.data(), size, error)) {
+ return false;
+ }
+ return reader.close(error);
+}
- return filename.substr(pos, std::string::npos);
+bool save_file(const std::string &filename, std::span<uint8_t> contents, std::string &error)
+{
+ return FileWriter::write_contents(filename, contents.data(), contents.size(), error);
+}
+
+bool save_file(const std::string &filename, const std::string &contents, std::string &error)
+{
+ return FileWriter::write_contents(filename, contents, error);
}
-std::string to_front_slashes(const std::string &path)
+bool open_input(const std::string &filename, std::ifstream &stream, bool binary, std::string &error)
{
- std::string front_slash_path(path);
- std::replace(front_slash_path.begin(), front_slash_path.end(), '\\', '/');
- return front_slash_path;
+ std::ios_base::openmode mode = binary ? (std::ios::in | std::ios::binary) : (std::ios::in);
+ #if defined(_MSC_VER)
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ std::stringstream ss;
+ ss << "Filename '" << filename << "' can't be converted from utf8 to wide string";
+ error = ss.str();
+ return false;
+ }
+ stream.open(wide_filename, mode);
+ #elif defined(__GNUC__)
+ stream.open(filename, mode);
+ #else
+ #error "Platform not supported"
+ #endif
+ if (!stream.is_open()) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << '\'';
+ error = ss.str();
+ return false;
+ }
+ return true;
}
+bool open_output(const std::string &filename, std::ofstream &stream, bool binary, std::string &error)
+{
+ std::ios_base::openmode mode = binary ? (std::ios::out | std::ios::binary | std::ios::trunc) : (std::ios::out | std::ios::trunc);
+ #if defined(_MSC_VER)
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ std::stringstream ss;
+ ss << "Filename '" << filename << "' can't be converted from utf8 to wide string";
+ error = ss.str();
+ return false;
+ }
+ stream.open(wide_filename, mode);
+ #elif defined(__GNUC__)
+ stream.open(filename, mode);
+ #else
+ #error "Platform not supported"
+ #endif
+ if (!stream.is_open()) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << '\'';
+ error = ss.str();
+ return false;
+ }
+ return true;
+}
+
+
} // namespace core
M core/core/io/file_helpers.h +37 -9
@@ 1,5 1,7 @@
#pragma once
+#include <span>
+
namespace core
{
@@ 8,21 10,47 @@ namespace core
/// Determines if there is an include directory that matches a file part.
/// @return True if a file part matches an include directory. @a result is updated in that case.
-bool match_include_dir_and_file(const std::string &file, const std::vector<std::string> &include_dirs, std::string &result);
+bool match_include_dir_and_file(const std::string &filename, const std::vector<std::string> &include_dirs, std::string &result);
+
+/// Check if a file exists.
+/// @return True if successful.
+[[nodiscard]] bool file_exists(const std::string &filename, bool &exists, std::string &error);
/// Check if a file exists.
-/// @return True if the file exists.
-bool file_exists(const char *file);
+/// @return True if successful.
+[[nodiscard]] bool dir_exists(const std::string &filename, bool &exists, std::string &error);
+
+/// Make a single directory.
+/// @return True if successful.
+[[nodiscard]] bool make_directory(const std::string &dir, std::string &error);
-/// Returns the filename without extension.
-std::string base_name(const std::string &filename);
+/// Make all directories in a path.
+/// @return True if successful.
+[[nodiscard]] bool make_directories(const std::string &dir, std::string &error);
+
+/// Determines the size of the file.
+/// @return True if successful.
+[[nodiscard]] bool file_size(const std::string &filename, size_t &size, std::string &error);
-/// Returns the file extension including the punctual character.
-std::string file_extension(const std::string &filename);
+/// Load an entire file.
+/// @return True if successful.
+[[nodiscard]] bool load_file(const std::string &filename, std::vector<uint8_t> &contents, std::string &error);
+
+/// Save an entire file.
+/// @return True if successful.
+[[nodiscard]] bool save_file(const std::string &filename, std::span<uint8_t> contents, std::string &error);
+[[nodiscard]] bool save_file(const std::string &filename, const std::string &contents, std::string &error);
-/// Change all back-slashes to front-slashes.
-std::string to_front_slashes(const std::string &path);
+/// @return True if successful.
+[[nodiscard]] bool list_dir(const std::string &path, std::vector<std::string> &result, std::string &error);
+/// Open a file for input and create an input stream.
+/// @return True if successful.
+[[nodiscard]] bool open_input(const std::string &filename, std::ifstream &stream, bool binary, std::string &error);
+
+/// Open a file for output and create an output stream.
+/// @return True if successful.
+[[nodiscard]] bool open_output(const std::string &filename, std::ofstream &stream, bool binary, std::string &error);
/// @}
M core/core/io/file_helpers_linux.cpp +72 -5
@@ 2,8 2,13 @@
#if defined(__linux) || defined(__APPLE__)
+#include <algorithm>
+#include <core/exceptions/error_linux.h>
#include <core/io/file_helpers.h>
+#include <core/strings/utf8.h>
#include <cstring>
+#include <dirent.h>
+#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ 11,16 16,78 @@
namespace core
{
-bool file_exists(const char *file)
+bool file_exists(const std::string &filename, bool &exists, std::string &error)
+{
+ struct stat s;
+ memset(&s, 0, sizeof(s));
+ int success = stat(filename.c_str(), &s);
+ if (success != 0) {
+ error = translate_system_error();
+ exists = false;
+ return errno == ENOENT;
+ }
+
+ error.clear();
+ bool is_regular_file = S_ISREG(s.st_mode);
+ exists = is_regular_file;
+ return true;
+}
+
+bool dir_exists(const std::string &filename, bool &exists, std::string &error)
{
struct stat s;
memset(&s, 0, sizeof(s));
- int success = stat(file, &s);
- if (success != 0)
+ int success = stat(filename.c_str(), &s);
+ if (success != 0) {
+ error = translate_system_error();
+ exists = false;
+ return errno == ENOENT;
+ }
+
+ error.clear();
+ bool is_dir = S_ISDIR(s.st_mode);
+ exists = is_dir;
+ return true;
+}
+
+bool make_directory(const std::string &dir, std::string &error)
+{
+ mode_t mode = S_IRWXU; // user can do anything, everyone else nothing
+ int result = mkdir(dir.c_str(), mode);
+ bool success = result == 0 || errno == EEXIST; // check for exist error in case of a race condition
+ if (!success) {
+ error = translate_system_error();
return false;
+ }
+ error.clear();
+ return true;
+}
- bool is_regular_file = S_ISREG(s.st_mode);
- return is_regular_file;
+bool list_dir(const std::string &path, std::vector<std::string> &result, std::string &error)
+{
+ DIR *dir = opendir(path.c_str());
+ if (dir == nullptr) {
+ std::stringstream ss;
+ ss << "Can't list directory '" << path << "': " << translate_system_error();
+ error = ss.str();
+ return false;
+ }
+
+ struct dirent *ent;
+ while (true) {
+ ent = readdir(dir);
+ if (ent == nullptr)
+ break;
+ if (ent->d_name[0] == '.' && ent->d_name[1] == '\0')
+ continue;
+ if (ent->d_name[0] == '.' && ent->d_name[1] == '.' && ent->d_name[2] == '\0')
+ continue;
+ result.push_back(ent->d_name);
+ }
+ closedir(dir);
+
+ std::sort(result.begin(), result.end());
+ return true;
}
} // namespace core
M core/core/io/file_helpers_win.cpp +124 -4
@@ 2,17 2,137 @@
#if defined(_WIN32)
+#include <algorithm>
+#include <core/exceptions/error_win.h>
#include <core/io/file_helpers.h>
+#include <core/io/path.h>
#include <core/strings/utf8.h>
+#include <sstream>
namespace core
{
-bool file_exists(const char *file)
+bool file_exists(const std::string &filename, bool &exists, std::string &error)
+{
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ error = "Filename can't be converted from utf8 to wide string";
+ exists = false;
+ return false;
+ }
+ DWORD attributes = GetFileAttributesW(wide_filename.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
+ std::stringstream ss;
+ ss << "Failed to check if file '" << filename << "' exists: " << translate_system_error();
+ error = ss.str();
+ exists = false;
+ return false;
+ }
+ error.clear();
+ exists = !(attributes & FILE_ATTRIBUTE_DIRECTORY);
+ return true;
+}
+
+bool dir_exists(const std::string &filename, bool &exists, std::string &error)
+{
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ error = "Path can't be converted from utf8 to wide string";
+ exists = false;
+ return false;
+ }
+ DWORD attributes = GetFileAttributesW(wide_filename.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
+ std::stringstream ss;
+ ss << "Failed to check if directory '" << filename << "' exists: " << translate_system_error();
+ error = ss.str();
+ exists = false;
+ return false;
+ }
+ error.clear();
+ exists = attributes & FILE_ATTRIBUTE_DIRECTORY;
+ return true;
+}
+
+bool make_directory(const std::string &dir, std::string &error)
{
- std::wstring wide_file = utf8_to_wide(file);
- DWORD attributes = GetFileAttributesW(wide_file.c_str());
- return (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY));
+ std::wstring wide_dir;
+ {
+ bool success = utf8_to_wide(dir, wide_dir);
+ if (!success) {
+ error = "Path can't be converted from utf8 to wide string";
+ return false;
+ }
+ }
+
+ BOOL success = CreateDirectoryW(wide_dir.c_str(), NULL);
+ if (!success) {
+ std::stringstream ss;
+ ss << "Failed to create directory '" << dir << "': " << translate_system_error();
+ error = ss.str();
+ return false;
+ }
+
+ error.clear();
+ return true;
+}
+
+bool list_dir(const std::string &path, std::vector<std::string> &result, std::string &error)
+{
+ std::string search_path = path_concat(path, "*");
+ std::wstring wide_search_path;
+ if (!utf8_to_wide(search_path, wide_search_path)) {
+ error = "Path can't be converted from utf8 to wide string";
+ return false;
+ }
+
+ if (wide_search_path.size() + 1 > MAX_PATH) { // +1 for termination character
+ std::stringstream ss;
+ ss << "Path '" << path << "' is too long to list directory";
+ error = ss.str();
+ return false;
+ }
+
+ WIN32_FIND_DATAW find_data;
+ HANDLE h = FindFirstFileW(wide_search_path.c_str(), &find_data);
+
+ if (h == INVALID_HANDLE_VALUE) {
+ std::stringstream ss;
+ ss << "Can't list directory '" << path << "': " << translate_system_error();
+ error = ss.str();
+ return false;
+ }
+
+ do {
+ std::wstring wide_filename = find_data.cFileName;
+ std::string narrow_filename;
+ if (!wide_to_utf8(wide_filename, narrow_filename)) {
+ // it wouldn't be nice to fail everything for one broken file so just skip it
+ continue;
+ }
+
+ if (narrow_filename == "." || narrow_filename == "..") {
+ continue;
+ }
+
+ result.push_back(narrow_filename);
+ } while (FindNextFileW(h, &find_data));
+
+ std::sort(result.begin(), result.end());
+
+ DWORD last_error = GetLastError();
+ FindClose(h);
+ if (last_error != ERROR_NO_MORE_FILES) {
+ std::stringstream ss;
+ ss << "Can't list directory '" << path << "': " << translate_system_error(last_error);
+ error = ss.str();
+ return false;
+ }
+
+ error.clear();
+ return true;
}
} // namespace core
M core/core/io/file_id.h +1 -1
@@ 17,7 17,7 @@ namespace core
/// Get the file id from a path. This can be used to determine if
/// two files are the exact same.
/// @return False if the file can't be opened.
-bool file_id(const std::string &file, FileId &id);
+[[nodiscard]] bool file_id(const std::string &file, FileId &id, std::string &error);
/// @}
M core/core/io/file_id_linux.cpp +14 -3
@@ 2,28 2,39 @@
#if defined(__linux) || defined(__APPLE__)
+#include <core/exceptions/error_linux.h>
#include <core/io/file_id.h>
#include <cstring>
+#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
namespace core
{
-bool file_id(const std::string &file, FileId &id)
+bool file_id(const std::string &filename, FileId &id, std::string &error)
{
struct stat s;
memset(&s, 0, sizeof(s));
- int success = stat(file.c_str(), &s);
- if (success != 0)
+ int result = stat(filename.c_str(), &s);
+ bool success = result == 0;
+ if (!success) {
+ std::stringstream ss;
+ ss << "Failed to get file status for '" << filename << "': " << translate_system_error();
+ error = ss.str();
return false;
+ }
if (S_ISREG(s.st_mode)) {
+ error.clear();
id.device = s.st_dev;
id.inode = s.st_ino;
return true;
}
+ std::stringstream ss;
+ ss << "The path '" << filename << "' isn't a regular file";
+ error = ss.str();
return false;
}
M core/core/io/file_id_win.cpp +20 -3
@@ 2,20 2,36 @@
#if defined(_WIN32)
+#include <core/exceptions/error_win.h>
#include <core/io/file_id.h>
#include <core/strings/utf8.h>
+#include <sstream>
namespace core
{
-bool file_id(const std::string &file, FileId &id)
+bool file_id(const std::string &filename, FileId &id, std::string &error)
{
- HANDLE h = CreateFileW(utf8_to_wide(file).c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if (h == INVALID_HANDLE_VALUE)
+ std::wstring wide_filename;
+ bool success = utf8_to_wide(filename, wide_filename);
+ if (!success) {
+ error = "Path can't be converted from utf8 to wide string";
return false;
+ }
+
+ HANDLE h = CreateFileW(wide_filename.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << "': " << translate_system_error();
+ error = ss.str();
+ return false;
+ }
BY_HANDLE_FILE_INFORMATION hfi;
BOOL result = GetFileInformationByHandle(h, &hfi);
if (!result) {
+ std::stringstream ss;
+ ss << "Failed to get file information for '" << filename << "': " << translate_system_error();
+ error = ss.str();
CloseHandle(h);
return false;
}
@@ 25,6 41,7 @@ bool file_id(const std::string &file, Fi
id.file_index_lo = hfi.nFileIndexLow;
CloseHandle(h);
+ error.clear();
return true;
}
M core/core/io/file_id_win.h +2 -0
@@ 1,5 1,7 @@
#pragma once
+#include <windows.h>
+
namespace core
{
A => core/core/io/file_reader.cpp +89 -0
@@ 0,0 1,89 @@
+#include "pch.h"
+
+#include <core/math/sign.h>
+#include <core/io/file_reader.h>
+#include <core/strings/utf8.h>
+#include <sstream>
+
+namespace core {
+
+bool FileReader::open(const std::string &filename, std::string &error)
+{
+ _filename = filename;
+ #if defined(_MSC_VER)
+ _file.open(convert_utf8_to_wide(filename), std::ios::in | std::ios::ate | std::ios::binary);
+ #elif defined(__GNUC__)
+ _file.open(filename, std::ios::in | std::ios::ate | std::ios::binary);
+ #else
+ #error "Platform not supported"
+ #endif
+
+ if (!_file.is_open()) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << "' for reading";
+ error = ss.str();
+ return false;
+ }
+
+ _size = static_cast<size_t>(_file.tellg());
+ _file.seekg(0);
+
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Failed to seek in '" << filename << "' when reading";
+ error = ss.str();
+ return false;
+ }
+
+ return true;
+}
+
+size_t FileReader::size() const
+{
+ return _size;
+}
+
+bool FileReader::seek(size_t pos, std::string &error)
+{
+ _file.seekg(static_cast<std::streampos>(core::sign_cast(pos)));
+
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Failed to seek in '" << _filename << "'";
+ error = ss.str();
+ return false;
+ }
+ return true;
+}
+
+bool FileReader::read(uint8_t *data, size_t &size, std::string &error)
+{
+ static_assert(sizeof(uint8_t) == sizeof(char), "unexpected size of char");
+ assert(_file.is_open());
+
+ _file.read(reinterpret_cast<char *>(data), core::sign_cast(size));
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Error when reading file '" << _filename << '\'';
+ error = ss.str();
+ return false;
+ }
+
+ size = static_cast<size_t>(_file.gcount());
+ return true;
+}
+
+bool FileReader::close(std::string &error)
+{
+ _file.close();
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Error when closing file '" << _filename << "'";
+ error = ss.str();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace core
A => core/core/io/file_reader.h +47 -0
@@ 0,0 1,47 @@
+#pragma once
+
+#include <fstream>
+
+namespace core {
+
+/// @addtogroup io
+/// @{
+
+/// This handles reading binary files.
+class FileReader
+{
+public:
+ /// Open a file for reading.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful.
+ [[nodiscard]] bool open(const std::string &filename, std::string &error);
+
+ /// @return Size of file in bytes.
+ [[nodiscard]] size_t size() const;
+
+ /// Seek to an absolute position in the file.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful.
+ [[nodiscard]] bool seek(size_t pos, std::string &error);
+
+ /// Read data from the opened file.
+ /// @param size As in parameter it tells the size of the buffer and as out
+ /// parameter it returns the actual size loaded.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful.
+ [[nodiscard]] bool read(uint8_t *data, size_t &size, std::string &error);
+
+ /// Close the file.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful.
+ [[nodiscard]] bool close(std::string &error);
+
+private:
+ std::string _filename;
+ std::ifstream _file;
+ size_t _size;
+};
+
+/// @}
+
+} // namespace core
M core/core/io/file_writer.cpp +72 -15
@@ 1,39 1,96 @@
#include "pch.h"
-#include <core/exceptions/file_exception.h>
+#include <core/math/sign.h>
#include <core/io/file_writer.h>
#include <core/strings/utf8.h>
+#include <sstream>
namespace core {
-void FileWriter::open(const std::string &filename)
+bool FileWriter::open(const std::string &filename, std::string &error)
{
+ _filename = filename;
#if defined(_MSC_VER)
- std::wstring wide_filename;
- try {
- wide_filename = convert_utf8_to_wide(filename);
- } catch (Exception &e) {
- throw FileException("Path cannot be converted to wide byte format: " + filename);
- }
- _file.open(wide_filename, std::ios::out | std::ios::trunc | std::ios::binary);
+ _file.open(convert_utf8_to_wide(filename), std::ios::out | std::ios::trunc | std::ios::binary);
#elif defined(__GNUC__)
_file.open(filename, std::ios::out | std::ios::trunc | std::ios::binary);
#else
#error "Platform not supported"
#endif
- if (!_file.is_open())
- throw FileException("Failed to open " + filename);
+ if (!_file.is_open()) {
+ std::stringstream ss;
+ ss << "Failed to open '" << filename << "' for writing";
+ error = ss.str();
+ return false;
+ }
+
+ return true;
}
-void FileWriter::write(const uint8_t *data, uint32_t size)
+bool FileWriter::write(const uint8_t *data, size_t size, std::string &error)
{
static_assert(sizeof(uint8_t) == sizeof(char), "unexpected size of char");
assert(_file.is_open());
- _file.write(reinterpret_cast<const char *>(data), size);
- if (_file.fail())
- throw FileException("Error when writing file");
+ _file.write(reinterpret_cast<const char *>(data), core::sign_cast(size));
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Error when writing file '" << _filename << '\'';
+ error = ss.str();
+ return false;
+ }
+
+ return true;
+}
+
+bool FileWriter::close(std::string &error)
+{
+ _file.close();
+ if (_file.fail()) {
+ std::stringstream ss;
+ ss << "Error when closing file '" << _filename << "'";
+ error = ss.str();
+ return false;
+ }
+
+ return true;
+}
+
+bool FileWriter::write_contents(const std::string &filename, const uint8_t *data, size_t size, std::string &error)
+{
+ core::FileWriter writer;
+ if (!writer.open(filename, error)) {
+ return false;
+ }
+ if (size != 0) {
+ if (!writer.write(data, size, error)) {
+ return false;
+ }
+ }
+
+ return writer.close(error);
+}
+
+bool FileWriter::write_contents(const std::string &filename, const std::vector<uint8_t> &data, std::string &error)
+{
+ return write_contents(filename, data.data(), data.size(), error);
+}
+
+bool FileWriter::write_contents(const std::string &filename, const std::vector<char> &data, std::string &error)
+{
+ return write_contents(filename, reinterpret_cast<const uint8_t *>(data.data()), data.size(), error);
+}
+
+bool FileWriter::write_contents(const std::string &filename, const std::stringstream &data, std::string &error)
+{
+ std::string contents = data.str();
+ return write_contents(filename, reinterpret_cast<const uint8_t *>(contents.data()), contents.size(), error);
+}
+
+bool FileWriter::write_contents(const std::string &filename, const std::string &data, std::string &error)
+{
+ return write_contents(filename, reinterpret_cast<const uint8_t *>(data.data()), data.size(), error);
}
} // namespace core
M core/core/io/file_writer.h +25 -2
@@ 11,13 11,36 @@ namespace core {
class FileWriter
{
public:
- void open(const std::string &filename);
- void write(const uint8_t *data, uint32_t size);
+ /// Open a file for writing.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful
+ [[nodiscard]] bool open(const std::string &filename, std::string &error);
+
+ /// Write data to the opened file.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful
+ [[nodiscard]] bool write(const uint8_t *data, size_t size, std::string &error);
+
+ /// Close the file.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful
+ [[nodiscard]] bool close(std::string &error);
+
+ /// Write binary data in a single function call.
+ /// @param error Is filled with the error reason in case of an error.
+ /// @return True if successful
+ [[nodiscard]] static bool write_contents(const std::string &filename, const uint8_t *data, size_t size, std::string &error);
+ [[nodiscard]] static bool write_contents(const std::string &filename, const std::vector<uint8_t> &data, std::string &error);
+ [[nodiscard]] static bool write_contents(const std::string &filename, const std::vector<char> &data, std::string &error);
+ [[nodiscard]] static bool write_contents(const std::string &filename, const std::stringstream &data, std::string &error);
+ [[nodiscard]] static bool write_contents(const std::string &filename, const std::string &data, std::string &error);
private:
+ std::string _filename;
std::ofstream _file;
};
+
/// @}
} // namespace core
A => core/core/io/path.cpp +88 -0
@@ 0,0 1,88 @@
+#include "pch.h"
+
+#include <algorithm>
+#include <core/io/path.h>
+#include <core/math/sign.h>
+#include <cstring>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace core
+{
+
+#if defined(_WIN32)
+ const char path_separators[2] = {'\\', '/'};
+#endif
+
+#if defined(__linux) || defined(__APPLE__)
+ const char path_separators[1] = {'/'};
+#endif
+
+std::string path_concat(const std::string &dir, const std::string &path)
+{
+ if (dir.empty()) {
+ return path;
+ }
+ if (path.empty()) {
+ return dir;
+ }
+
+ if (std::find(std::begin(path_separators), std::end(path_separators), dir.back()) != std::end(path_separators)) {
+ return dir + path;
+ }
+ std::string result;
+ result.reserve(dir.size() + 1 + path.size());
+ result.append(dir);
+ result.push_back(path_separator());
+ result.append(path);
+ return result;
+}
+
+std::string path_pop(const std::string &path)
+{
+ auto it = std::find_end(std::begin(path), std::end(path), std::begin(path_separators), std::end(path_separators));
+ if (it == std::end(path)) {
+ return std::string();
+ }
+ return path.substr(0, unsign_cast(it - std::begin(path)));
+}
+
+std::string base_name(const std::string &filename)
+{
+ size_t pos = filename.rfind(".");
+
+ if (pos == std::string::npos)
+ return filename;
+
+ return filename.substr(0, pos);
+}
+
+std::string filename(const std::string &path)
+{
+ auto it = std::find_end(std::begin(path), std::end(path), std::begin(path_separators), std::end(path_separators));
+ if (it == std::end(path)) {
+ return path;
+ }
+
+ return path.substr(unsign_cast(it - std::begin(path) + 1));
+}
+
+std::string file_extension(const std::string &filename)
+{
+ size_t pos = filename.rfind(".");
+
+ if (pos == std::string::npos)
+ return std::string();
+
+ return filename.substr(pos, std::string::npos);
+}
+
+std::string to_front_slashes(const std::string &path)
+{
+ std::string front_slash_path(path);
+ std::replace(front_slash_path.begin(), front_slash_path.end(), '\\', '/');
+ return front_slash_path;
+}
+
+}
A => core/core/io/path.h +46 -0
@@ 0,0 1,46 @@
+#pragma once
+
+#include <string>
+
+namespace core
+{
+
+#if defined(_WIN32)
+ /// Returns the character to separate directories in a path.
+ constexpr char path_separator()
+ {
+ return '\\';
+ }
+
+ extern const char path_separators[2];
+#endif
+
+#if defined(__linux) || defined(__APPLE__)
+ /// Returns the character to separate directories in a path.
+ constexpr char path_separator()
+ {
+ return '/';
+ }
+
+ extern const char path_separators[1];
+#endif
+
+/// Concatenate a directory with a file or directory, adding the directory separator inbetween if necessary.
+std::string path_concat(const std::string &dir, const std::string &path);
+
+/// Returns the path with last component removed.
+std::string path_pop(const std::string &path);
+
+/// Get the filename from a path.
+std::string filename(const std::string &path);
+
+/// Returns the filename without extension.
+std::string base_name(const std::string &filename);
+
+/// Returns the file extension including the punctual character.
+std::string file_extension(const std::string &filename);
+
+/// Change all back-slashes to front-slashes.
+std::string to_front_slashes(const std::string &path);
+
+}
M core/core/io/text_reader.cpp +12 -32
@@ 1,47 1,27 @@
#include "pch.h"
-#include <core/exceptions/file_exception.h>
+#include <core/io/file_reader.h>
#include <core/io/text_reader.h>
#include <core/strings/utf8.h>
-#include <fstream>
-#include <locale>
#include <vector>
namespace core
{
-std::string load_file(const std::string &filename)
+bool load_file(const std::string &filename, std::string &contents, std::string &error)
{
- // load the file contents
- std::ifstream file;
- // open file and place read offset at end to measure size
- #if defined(_MSC_VER)
- std::wstring wide_filename;
- try {
- wide_filename = convert_utf8_to_wide(filename);
- } catch (Exception &e) {
- throw FileException("Path cannot be converted to wide byte format: " + filename);
- }
- file.open(wide_filename, std::ios::in | std::ios::ate | std::ios::binary);
- #elif defined(__GNUC__)
- file.open(filename, std::ios::in | std::ios::ate | std::ios::binary);
- #else
- #error "Platform not supported"
- #endif
- if (!file.is_open())
- throw FileException("Failed to open " + filename);
-
- // get file size
- uint64_t size = static_cast<uint64_t>(file.tellg());
+ FileReader reader;
+ if (!reader.open(filename, error)) {
+ return false;
+ }
+ size_t size = reader.size();
std::vector<char> data;
data.resize(size);
-
- // read contents
- file.seekg(0, std::ios::beg);
- file.read(data.data(), static_cast<std::streamsize>(size));
- file.close();
-
- return std::string(data.data(), data.size());
+ if (!reader.read(reinterpret_cast<uint8_t *>(data.data()), size, error)) {
+ return false;
+ }
+ contents = std::string(data.data(), size);
+ return reader.close(error);
}
} // namespace core
M core/core/io/text_reader.h +1 -1
@@ 7,7 7,7 @@ namespace core
/// @{
/// Read an utf8 encoded file and return the contents as an utf8 encoded character array.
-std::string load_file(const std::string &filename);
+[[nodiscard]] bool load_file(const std::string &filename, std::string &contents, std::string &error);
/// @}
M jasm/CMakeLists.txt +1 -1
@@ 32,7 32,7 @@ if (${MINGW})
)
endif()
-set_property(TARGET jasm PROPERTY CXX_STANDARD 20)
+set_property(TARGET jasm PROPERTY CXX_STANDARD 23)
target_link_libraries(jasm core)
M jasm/assemble/assembler_impl/assembler_impl.cpp +15 -10
@@ 7,6 7,7 @@
#include <core/io/file_helpers.h>
#include <core/io/file_id.h>
#include <core/io/file_writer.h>
+#include <core/io/path.h>
#include <core/math/sign.h>
#include <core/strings/utf8.h>
#include <exceptions/assembly_exception.h>
@@ 729,7 730,8 @@ size_t Assembler::syntax_analyze(const s
core::FileId fid;
std::string file_path = filename;
match_include_dir_and_file(filename, _include_dirs, file_path);
- if (!core::file_id(file_path, fid)) {
+ std::string error;
+ if (!core::file_id(file_path, fid, error)) {
size_t file_index = _used_files.size();
// make sure that the file has front slashes to get the output from linux and pc unit tests match
_used_files.emplace_back(core::to_front_slashes(filename));
@@ 1071,9 1073,10 @@ void Assembler::dump_symbols(const std::
std::string utf8 = ss.str();
// write to disk
- FileWriter wr;
- wr.open(filename);
- wr.write(reinterpret_cast<const uint8_t *>(utf8.data()), static_cast<uint32_t>(utf8.size()));
+ std::string error;
+ if (!core::save_file(filename, utf8, error)) {
+ throw AssemblyException(error);
+ }
}
struct SimpleSymbolInformation
@@ 1211,9 1214,10 @@ void Assembler::dump_vice_symbols(const
std::string utf8 = ss.str();
// write to disk
- FileWriter wr;
- wr.open(filename);
- wr.write(reinterpret_cast<const uint8_t *>(utf8.c_str()), static_cast<uint32_t>(utf8.size()));
+ std::string error;
+ if (!core::save_file(filename, utf8, error)) {
+ throw AssemblyException(error);
+ }
}
void Assembler::dump_gba_symbols(const std::string &filename)
@@ 1288,9 1292,10 @@ void Assembler::dump_gba_symbols(const s
std::string utf8 = ss.str();
// write to disk
- FileWriter wr;
- wr.open(filename);
- wr.write(reinterpret_cast<const uint8_t *>(utf8.c_str()), static_cast<uint32_t>(utf8.size()));
+ std::string error;
+ if (!core::save_file(filename, utf8, error)) {
+ throw AssemblyException(error);
+ }
}
void Assembler::recurse_print_sections(const Section §ion, int indent)
M jasm/io/data_reader.cpp +4 -4
@@ 79,15 79,15 @@ bool DataReader::size(uint64_t handle, s
try {
info.size = info.size_future.get();
} catch (Exception &e) {
- info.file_size_error = e.message;
+ info.file_size_error = e.what();
}
}
#else
try {
load(&info);
} catch (Exception &e) {
- info.file_size_error = e.message;
- info.file_data_error = e.message;
+ info.file_size_error = e.what();
+ info.file_data_error = e.what();
}
info.loaded = true;
#endif
@@ 116,7 116,7 @@ bool DataReader::data(uint64_t handle, c
load(&info);
#endif
} catch (Exception &e) {
- info.file_data_error = e.message;
+ info.file_data_error = e.what();
}
info.loaded = true;
}
M jasm/main.cpp +15 -4
@@ 34,7 34,10 @@ static const char *revision_hash =
void write_file(int32_t start_address, bool load_addr_header, bool multi_bank_mode, const Section &first_section, const std::string &output_file, const std::vector<uint8_t> &data, const StringRepository &strings, const std::vector<std::string> &used_files)
{
FileWriter file;
- file.open(output_file);
+ std::string error;
+ if (!file.open(output_file, error)) {
+ throw AssemblyException(error);
+ }
if (load_addr_header) {
if (!multi_bank_mode) {
if (start_address < 0 || start_address > 0xffff) {
@@ 47,9 50,17 @@ void write_file(int32_t start_address, b
std::array<uint8_t, 2> header;
header[0] = static_cast<uint8_t>(start_address & 0xff);
header[1] = static_cast<uint8_t>(start_address >> 8);
- file.write(header.data(), static_cast<uint32_t>(header.size()));
+ if (!file.write(header.data(), static_cast<uint32_t>(header.size()), error)) {
+ throw AssemblyException(error);
+ }
}
- file.write(&data[0], static_cast<uint32_t>(data.size()));
+ if (!file.write(&data[0], static_cast<uint32_t>(data.size()), error)) {
+ throw AssemblyException(error);
+ }
+
+ if (!file.close(error)) {
+ throw AssemblyException(error);
+ }
}
void merge_sections_and_write(CommandLineArgs &args, const std::vector<Section> §ions, const StringRepository &strings, const std::vector<std::string> &used_files)
@@ 179,7 190,7 @@ int safe_main(int argc, const char * con
assemble(args);
}
catch (Exception &e) {
- error() << e.message << '\n';
+ error() << e.what() << '\n';
return_code = 10;
}
return return_code;
M jasm/tokenize/tokenizer.cpp +5 -7
@@ 42,13 42,11 @@ void Tokenizer::tokenize(uint32_t file_i
_processor_stack.push_back(_processor);
std::string contents;
- try {
- contents = core::load_file(file_path);
- } catch (core::Exception &e) {
+ std::string error;
+ if (!core::load_file(file_path, contents, error)) {
std::stringstream ss;
- ss << e.message << "\nwhile loading '" << _filename << "'";
- e.message = ss.str();
- throw e;
+ ss << error << "\nwhile loading '" << _filename << "'";
+ throw AssemblyException(ss.str());
}
// convert to wide strings to simplify the character classification
@@ 56,7 54,7 @@ void Tokenizer::tokenize(uint32_t file_i
try {
wide_contents = core::utf8_to_wide(contents);
} catch (core::Exception &) {
- throw core::FileException("File contents isn't utf8 encoded: " + _filename);
+ throw AssemblyException("File contents isn't utf8 encoded: " + _filename);
}
// classify all characters