commit d1890b6e182f265a17273523f1a8e3fae7288775 Author: Sebastian Lundegård Kylander Date: Sat Jul 11 19:53:00 2026 +0200 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..bf787e4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,118 @@ +--- +BasedOnStyle: Microsoft +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: true +AlignConsecutiveBitFields: true +AlignConsecutiveDeclarations: true +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlignOperands: AlignAfterOperator +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterJavaFieldAnnotations: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +ColumnLimit: 0 +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 0 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +FixNamespaceComments: false +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: NoIndent +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 0 +PenaltyBreakBeforeFirstCallParameter: 0 +PenaltyBreakComment: 0 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 0 +PenaltyBreakTemplateDeclaration: 0 +PenaltyExcessCharacter: 0 +PenaltyReturnTypeOnItsOwnLine: 0 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +TabWidth: 4 +UseTab: AlignWithSpaces +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01cadaf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# see https://github.com/CppCXY/EmmyLuaCodeStyle +[*.lua] +indent_style = space +indent_size = 2 +trailing_table_separator = smart +call_arg_parentheses = remove_string_only +space_around_table_field_list = false +line_space_after_do_statement = min(2) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b144a8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Intermediate files +.build/ +.obj/ +.vs/ + +# Generated project files +*.sln* + +# Output files +/bin/ diff --git a/application/main.cpp b/application/main.cpp new file mode 100644 index 0000000..ae1b62a --- /dev/null +++ b/application/main.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include + +void print_help(std::filesystem::path application_path) +{ + std::println("Usage: {}", application_path.stem().string()); +} + +int main(int argc, char* argv[]) +{ + core::command_line_args_view command_line_args_view{argc, argv}; + + char help_option_name[]{"--help"}; + char* fallback_argv[]{argv[0], help_option_name}; + + if (std::ranges::empty(command_line_args_view)) + command_line_args_view = core::command_line_args_view(2, fallback_argv); + + for (auto&& arg : command_line_args_view) + { + arg.with("--help", [&] { + print_help(command_line_args_view.appliation_path()); + }); + } + + return 0; +} diff --git a/application/premake5.lua b/application/premake5.lua new file mode 100644 index 0000000..9e173d7 --- /dev/null +++ b/application/premake5.lua @@ -0,0 +1,13 @@ +project "cpp-application" +do + kind "ConsoleApp" + location "%{_MAIN_SCRIPT_DIR}/.build" + externalincludedirs {"%{_MAIN_SCRIPT_DIR}"} + files { + "**.cpp", + "**.h", + } + links { + "cpp-core-library", + } +end diff --git a/core-library/io/file-stream.cpp b/core-library/io/file-stream.cpp new file mode 100644 index 0000000..d27cd86 --- /dev/null +++ b/core-library/io/file-stream.cpp @@ -0,0 +1,69 @@ +#include "file-stream.h" + +#include "core-library/io/io-helper.h" + +#include + +core::file_stream::~file_stream() +{ + close(); +} + +core::file_stream::file_stream(file_stream&& other) noexcept +: m_file_handle(std::exchange(other.m_file_handle, nullptr)) +{ +} + +core::file_stream& core::file_stream::operator=(file_stream&& other) +{ + if (this != &other) + { + core::close_file(m_file_handle); + m_file_handle = std::exchange(other.m_file_handle, nullptr); + } + + return *this; +} + +core::file_stream core::file_stream::open_for_reading(const std::filesystem::path& file_path) +{ + file_stream stream{}; + stream.m_file_handle = core::open_file(file_path.string().c_str(), "rb"); + + return stream; +} + +std::string core::file_stream::file_contents_as_string(const std::filesystem::path& file_path) +{ + file_stream stream = open_for_reading(file_path); + return stream.read_all_as_string(); +} + +std::string core::file_stream::read_all_as_string() +{ + if (!m_file_handle) + return {}; + + core::seek_file_to_end(m_file_handle); + const std::size_t file_size = static_cast(core::get_file_offset(m_file_handle)); + core::seek_file_to_beginning(m_file_handle); + + if (file_size == 0) + return {}; + + std::string result(file_size, '\0'); + core::read_from_file(&result[0], 1, file_size, m_file_handle); + + return result; +} + +void core::file_stream::close() +{ + if (m_file_handle) + { + if (int result = std::fclose(m_file_handle); result != 0) + throw std::system_error(std::error_code{result, std::system_category()}); + + m_file_handle = nullptr; + } +} diff --git a/core-library/io/file-stream.h b/core-library/io/file-stream.h new file mode 100644 index 0000000..8033c8f --- /dev/null +++ b/core-library/io/file-stream.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace core { + +class file_stream +{ +public: + file_stream(const file_stream&) = delete; + file_stream& operator=(const file_stream&) = delete; + file_stream(file_stream&&) noexcept; + file_stream& operator=(file_stream&&); + ~file_stream(); + + static file_stream open_for_reading(const std::filesystem::path& file_path); + static std::string file_contents_as_string(const std::filesystem::path& file_path); + + std::string read_all_as_string(); + void close(); + +private: + file_stream() = default; + + FILE* m_file_handle{}; +}; + +} diff --git a/core-library/io/io-helper.cpp b/core-library/io/io-helper.cpp new file mode 100644 index 0000000..703fb03 --- /dev/null +++ b/core-library/io/io-helper.cpp @@ -0,0 +1,72 @@ +#include "io-helper.h" + +#include + +std::FILE* core::open_file(const char* file_path, const char* mode) +{ + std::FILE* file = std::fopen(file_path, mode); + if (!file) + throw std::system_error(errno, std::system_category()); + + return file; +} + +void core::seek_file_to_beginning(std::FILE* file, long offset) +{ + const int result = std::fseek(file, offset, SEEK_SET); + if (result != 0) + throw std::system_error(errno, std::system_category()); +} + +void core::seek_file_relatively(std::FILE* file, long offset) +{ + const int result = std::fseek(file, offset, SEEK_CUR); + if (result != 0) + throw std::system_error(errno, std::system_category()); +} + +void core::seek_file_to_end(std::FILE* file, long offset) +{ + const int result = std::fseek(file, offset, SEEK_END); + if (result != 0) + throw std::system_error(errno, std::system_category()); +} + +long core::get_file_offset(std::FILE* file) +{ + const long offset = std::ftell(file); + if (offset < 0) + throw std::system_error(errno, std::system_category()); + + return offset; +} + +std::size_t core::read_from_file(void* buffer, std::size_t size, std::size_t count, std::FILE* file) +{ + const std::size_t objects_read = std::fread(buffer, size, count, file); + if (objects_read < count) + { + if (std::feof(file)) + throw std::runtime_error("Failed to read from file: End-of-file reached."); + else + throw std::runtime_error("Failed to read from file: Unknown error."); + } + + return objects_read; +} + +std::size_t core::write_to_file(const void* buffer, std::size_t size, std::size_t count, std::FILE* file) +{ + const std::size_t objects_written = std::fwrite(buffer, size, count, file); + if (objects_written < count) + throw std::runtime_error("Failed to write to file."); + + return objects_written; +} + +void core::close_file(std::FILE* file) +{ + int result = std::fclose(file); + if (result != 0) + throw std::runtime_error("Failed to close file."); +} diff --git a/core-library/io/io-helper.h b/core-library/io/io-helper.h new file mode 100644 index 0000000..462e729 --- /dev/null +++ b/core-library/io/io-helper.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace core { + +extern std::FILE* open_file(const char* file_path, const char* mode); +extern void seek_file_to_beginning(std::FILE* file, long offset = 0); +extern void seek_file_relatively(std::FILE* file, long offset); +extern void seek_file_to_end(std::FILE* file, long offset = 0); +extern long get_file_offset(std::FILE* file); +extern std::size_t read_from_file(void* buffer, std::size_t size, std::size_t count, std::FILE* file); +extern std::size_t write_to_file(const void* buffer, std::size_t size, std::size_t count, std::FILE* file); +extern void close_file(std::FILE* file); + +} diff --git a/core-library/premake5.lua b/core-library/premake5.lua new file mode 100644 index 0000000..22a8122 --- /dev/null +++ b/core-library/premake5.lua @@ -0,0 +1,17 @@ +project "cpp-core-library" +do + kind "StaticLib" + location "%{_MAIN_SCRIPT_DIR}/.build" + includedirs {"%{_MAIN_SCRIPT_DIR}"} + files { + "**.cpp", + "**.h", + } + + filter "toolset:msc*" + do + defines {"_CRT_SECURE_NO_WARNINGS"} + end + + filter "" +end diff --git a/core-library/traits/function-traits.h b/core-library/traits/function-traits.h new file mode 100644 index 0000000..d09bba7 --- /dev/null +++ b/core-library/traits/function-traits.h @@ -0,0 +1,61 @@ +#pragma once +#include + +namespace core { + +template +concept callable = requires { &T::operator(); }; + +template +struct function_traits +{ + using return_type = void; + using first_argument_type = void; +}; + +template + requires callable +struct function_traits +{ + using return_type = typename function_traits::return_type; + using first_argument_type = typename function_traits::first_argument_type; +}; + +template +struct function_traits +{ + using return_type = typename Ret; + using first_argument_type = typename Arg0; +}; + +template +struct function_traits +{ + using return_type = typename Ret; + using first_argument_type = typename Arg0; +}; + +template +struct function_traits +{ + using return_type = typename Ret; + using first_argument_type = typename Arg0; +}; + +template +struct function_traits +{ + using return_type = typename Ret; + using first_argument_type = typename Arg0; +}; + +template +using return_type_t = typename function_traits::return_type; + +template +using first_argument_type_t = typename function_traits::first_argument_type; + +template +concept callable_with_converted_argument = requires(T&& callable, Arg&& arg) { callable(typename traits::first_argument_type_t{arg}); }; + +} diff --git a/core-library/views/command-line-args-view.h b/core-library/views/command-line-args-view.h new file mode 100644 index 0000000..56c4526 --- /dev/null +++ b/core-library/views/command-line-args-view.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include +#include + +namespace core { + +class command_line_argument +{ +public: + constexpr command_line_argument(char* arg, std::span modifiers) + : m_arg_view{arg} + , m_modifiers{modifiers} + { + } + + template + requires std::invocable + void with(std::string_view arg_to_match_view, F&& function) const + { + if (arg_to_match_view == m_arg_view) + std::invoke(std::forward(function)); + } + +private: + std::string_view m_arg_view; + std::span m_modifiers; +}; + +class command_line_args_view : public std::ranges::view_interface +{ +public: + constexpr command_line_args_view() = default; + + constexpr command_line_args_view(int argc, char* argv[]) + : m_argc{--argc} + , m_application_path{argv++[0]} + , m_argv{argv} + { + } + + class iterator + { + public: + constexpr iterator(int argc, char** argv, int index) + : m_argv{argv} + , m_argc{argc} + , m_index{index} + { + } + + constexpr iterator& operator++() + { + ++m_index; + + while (m_index < m_argc) + { + const std::string_view arg_view{m_argv[m_index]}; + if (arg_view.starts_with("--")) + m_modifier_start_index = m_index++; + } + + return *this; + } + + constexpr command_line_argument operator*() const + { + if (m_modifier_start_index != -1) + return command_line_argument{m_argv[m_index], std::span(&m_argv[m_modifier_start_index], &m_argv[m_index])}; + else + return command_line_argument{m_argv[m_index], {}}; + } + + constexpr bool operator!=(const iterator& other) const { return m_argv != other.m_argv || m_index != other.m_index; } + + private: + char** m_argv; + int m_argc; + int m_index; + int m_modifier_start_index{-1}; + }; + + constexpr auto&& appliation_path() const { return m_application_path; } + + constexpr iterator begin() const { return iterator{m_argc, m_argv, 0}; } + constexpr iterator end() const { return iterator{m_argc, m_argv, m_argc}; } + constexpr std::size_t size() const { return static_cast(m_argc); } + +private: + int m_argc{}; + char* m_application_path{}; + char** m_argv{}; +}; + +} diff --git a/core-library/views/trim-elements-view.h b/core-library/views/trim-elements-view.h new file mode 100644 index 0000000..a796dc8 --- /dev/null +++ b/core-library/views/trim-elements-view.h @@ -0,0 +1,55 @@ +#pragma once +#include "core-library/views/trim-view.h" + +namespace core { + +template + requires requires(Range&& range) { + (void)range.empty(); + (void)trim_view{(*std::begin(range))}; + } +class trim_elements_view : public std::ranges::view_interface> +{ +public: + constexpr trim_elements_view(Range range) + : m_range(std::move(range)) + { + } + + class iterator + { + public: + using base_t = std::ranges::iterator_t; + + constexpr iterator(base_t base, base_t end) + : m_base{base} + , m_end{end} + { + } + + constexpr bool operator!=(iterator other) const { return m_base != other.m_base; } + constexpr auto operator*() const { return trim_view{*m_base}; } + + constexpr iterator& operator++() + { + // Skip empty elements + while (++m_base != m_end && std::ranges::empty(**this)) {} + + return *this; + } + + private: + base_t m_base; + base_t m_end; + }; + + constexpr auto begin() { return iterator{std::begin(m_range), std::end(m_range)}; } + constexpr auto end() { return iterator{std::end(m_range), std::end(m_range)}; } + +private: + Range m_range{}; +}; + +constexpr view_closure trim_elements{}; + +} diff --git a/core-library/views/trim-view.h b/core-library/views/trim-view.h new file mode 100644 index 0000000..2fb5e1b --- /dev/null +++ b/core-library/views/trim-view.h @@ -0,0 +1,53 @@ +#pragma once +#include "core-library/views/view-closure.h" + +#include + +namespace core { + +template + requires requires(Range&& range) { + *std::begin(range) == ' '; // Comparable with char literals. + } +class trim_view : public std::ranges::view_interface> +{ +public: + constexpr explicit trim_view(Range range) + : m_range(std::move(range)) + { + } + + static constexpr bool is_space(auto&& c) + { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0'; + } + + constexpr auto begin() + { + auto it = std::begin(m_range); + while (it != std::end(m_range) && is_space(*it)) + ++it; + + return it; + } + + constexpr auto end() + { + auto nonspace_end = std::end(m_range); + + for (auto it = std::begin(m_range); it != std::end(m_range);) + { + if (!is_space(*it++)) + nonspace_end = it; + } + + return nonspace_end; + } + +private: + Range m_range{}; +}; + +constexpr view_closure trim{}; + +} diff --git a/core-library/views/view-closure.h b/core-library/views/view-closure.h new file mode 100644 index 0000000..126b9f6 --- /dev/null +++ b/core-library/views/view-closure.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace core { + +template typename ViewTemplate> +class view_closure : public std::ranges::range_adaptor_closure> +{ +public: + template + constexpr auto operator()(Range&& range) const + requires requires { ViewTemplate>{std::forward(range)}; } + { + return ViewTemplate>{std::forward(range)}; + } +}; + +} diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..665c104 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,28 @@ +require("premake", ">=5.0.0-beta8") + +workspace "cpp-template" +do + platforms {"x64"} + configurations {"Debug", "Release"} + startproject "cpp-application" + + -- Default fields for all projects + targetdir "bin/%{cfg.buildcfg:lower()}" + objdir ".obj/%{cfg.buildcfg:lower()}" + cppdialect "C++23" + multiprocessorcompile "On" + optimize "Full" + symbols "On" + warnings "High" + + filter "configurations:Debug" + do + optimize "Off" + symbols "Full" + end + + filter "" + + include "core-library" + include "application" +end