From 79553ab1fa3d707612e80dd03bf95ca65fa0c411 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Tue, 4 Feb 2025 23:11:08 +0100 Subject: refactor file browser --- lib/nes/common/CMakeLists.txt | 3 +- lib/nes/common/box.hh | 29 ++++++++-- lib/nes/common/debug.hh | 18 +++++- lib/nes/common/list.hh | 24 +++++--- lib/nes/common/path-builder.cc | 14 ++--- lib/nes/common/path-builder.hh | 21 ++++++- lib/nes/common/path-view.cc | 4 +- lib/nes/common/span.hh | 33 +++++++++-- lib/nes/common/status.hh | 42 ++++++++++++++ lib/nes/common/string-builder.cc | 116 ++++++++++++++++++++------------------- lib/nes/common/string-builder.hh | 37 +++++++++---- 11 files changed, 243 insertions(+), 98 deletions(-) create mode 100644 lib/nes/common/status.hh (limited to 'lib/nes/common') diff --git a/lib/nes/common/CMakeLists.txt b/lib/nes/common/CMakeLists.txt index a7c1d76..a5703ff 100644 --- a/lib/nes/common/CMakeLists.txt +++ b/lib/nes/common/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources( path-view.cc path-builder.hh path-builder.cc - span.hh) \ No newline at end of file + span.hh + status.hh) \ No newline at end of file diff --git a/lib/nes/common/box.hh b/lib/nes/common/box.hh index 3a65613..7bf2848 100644 --- a/lib/nes/common/box.hh +++ b/lib/nes/common/box.hh @@ -1,6 +1,7 @@ #pragma once #include "nes/common/types.hh" +#include "nes/common/debug.hh" namespace nes { @@ -28,10 +29,30 @@ namespace nes auto operator=(box const&) -> box& = delete; auto operator=(box&&) -> box& = delete; - auto operator*() -> T& { return *get(); } - auto operator*() const -> T const& { return *get(); } - auto operator->() -> T* { return get(); } - auto operator->() const -> T const* { return get(); } + auto operator*() -> T& + { + NES_ASSERT(has_value() && "expected box to be non-empty"); + return *get(); + } + + auto operator*() const -> T const& + { + NES_ASSERT(has_value() && "expected box to be non-empty"); + return *get(); + } + + auto operator->() -> T* + { + NES_ASSERT(has_value() && "expected box to be non-empty"); + return get(); + } + + auto operator->() const -> T const* + { + NES_ASSERT(has_value() && "expected box to be non-empty"); + return get(); + } + operator bool() const { return has_value(); } auto has_value() const -> bool { return initialized_; } diff --git a/lib/nes/common/debug.hh b/lib/nes/common/debug.hh index 409d55a..30370e9 100644 --- a/lib/nes/common/debug.hh +++ b/lib/nes/common/debug.hh @@ -5,11 +5,25 @@ #include #define NES_DEBUG_LOG(name, ...) \ - spdlog::info("[" #name "] " __VA_ARGS__) + spdlog::info("[" #name "] " __VA_ARGS__) #else #define NES_DEBUG_LOG(name, fmt, ...) \ - (void)0 + (void)0 + +#endif + +#ifdef NDEBUG + +#define NES_ASSERT(...) \ + (void)0 + +#else + +#include + +#define NES_ASSERT(...) \ + assert(__VA_ARGS__) #endif \ No newline at end of file diff --git a/lib/nes/common/list.hh b/lib/nes/common/list.hh index b1fddf0..46f7027 100644 --- a/lib/nes/common/list.hh +++ b/lib/nes/common/list.hh @@ -1,5 +1,7 @@ #pragma once +#include "nes/common/debug.hh" + namespace nes { /// An intrusive doubly-linked list. @@ -39,15 +41,23 @@ namespace nes auto operator==(iterator const& other) -> bool { return current_ == other.current_; } auto operator!=(iterator const& other) -> bool { return current_ != other.current_; } - auto operator*() const -> T* { return current_; } + auto operator*() const -> T& + { + NES_ASSERT(current_ != nullptr && "invalid iterator"); + return *current_; + } + + auto operator->() const -> T* + { + NES_ASSERT(current_ != nullptr && "invalid iterator"); + return current_; + } auto operator++() -> iterator& { - if (current_) - { - node& current_node = *current_; - current_ = current_node.next_; - } + NES_ASSERT(current_ != nullptr && "invalid iterator"); + node& current_node = *current_; + current_ = current_node.next_; return *this; } @@ -126,7 +136,7 @@ namespace nes /// Remove an item from the list by its iterator. auto remove(iterator it) -> bool { - if (it != end()) { return remove(**it); } + if (it != end()) { return remove(*it); } return false; } }; diff --git a/lib/nes/common/path-builder.cc b/lib/nes/common/path-builder.cc index 794d565..b66226f 100644 --- a/lib/nes/common/path-builder.cc +++ b/lib/nes/common/path-builder.cc @@ -9,28 +9,28 @@ namespace nes if (buffer_.get_length() > 0) { buffer_[length_++] = '/'; } } - auto path_builder::push(std::string_view component) -> bool + auto path_builder::push(std::string_view component) -> status { // Trim leading and trailing slashes while (!component.empty() && component[0] == '/') { component = component.substr(1); } while (!component.empty() && component[component.length() - 1] == '/') { component = component.substr(0, component.length() - 1); } - if (component.empty() || std::find(component.begin(), component.end(), '/') != component.end()) { return false; } + if (component.empty() || std::find(component.begin(), component.end(), '/') != component.end()) { return status::error_invalid_path; } // Check if there is enough remaining capacity for "/" auto const add_leading_slash = length_ == 0 || buffer_[length_ - 1] != '/'; auto const length = component.length() + (add_leading_slash ? 1 : 0); - if (length_ + length > buffer_.get_length()) { return false; } + if (length_ + length > buffer_.get_length()) { return status::error_buffer_overflow; } // Append "/" if (add_leading_slash) { buffer_[length_++] = '/'; } for (auto i = u32{ 0 }; i < component.length(); ++i) { buffer_[length_++] = component[i]; } - return true; + return status::success; } - auto path_builder::pop() -> bool + auto path_builder::pop() -> status { - if (length_ == 0 || buffer_[length_ - 1] == '/') { return false; } + if (length_ == 0 || buffer_[length_ - 1] == '/') { return status::error_invalid_path; } // Find the start of the last component. auto last_start = length_ - 1; @@ -41,6 +41,6 @@ namespace nes // Drop everything including the trailing slash. length_ = last_start; - return true; + return status::success; } } // namespace nes \ No newline at end of file diff --git a/lib/nes/common/path-builder.hh b/lib/nes/common/path-builder.hh index b64fb28..e7980ea 100644 --- a/lib/nes/common/path-builder.hh +++ b/lib/nes/common/path-builder.hh @@ -3,6 +3,7 @@ #include "nes/common/types.hh" #include "nes/common/span.hh" #include "nes/common/path-view.hh" +#include "nes/common/status.hh" #include namespace nes @@ -10,6 +11,9 @@ namespace nes /// A mutable path with a fixed-length buffer. class path_builder { + template + friend class path_buffer; + span buffer_; u32 length_{ 0 }; @@ -19,8 +23,8 @@ namespace nes auto get_components() const -> path_view { return path_view{ get_path() }; } auto get_path() const -> std::string_view { return std::string_view{ buffer_.get_data(), length_ }; } - auto push(std::string_view) -> bool; - auto pop() -> bool; + auto push(std::string_view) -> status; + auto pop() -> status; }; /// A path builder with a buffer embedded into it. @@ -34,5 +38,18 @@ namespace nes : path_builder{ buffer_ } { } + + path_buffer(path_buffer const& other) + : path_builder{ buffer_ } + { + std::copy_n(other.buffer_, Capacity, buffer_); + length_ = other.length_; + } + + auto operator=(path_buffer const& other) + { + std::copy_n(other.buffer_, Capacity, buffer_); + length_ = other.length_; + } }; } // namespace nes \ No newline at end of file diff --git a/lib/nes/common/path-view.cc b/lib/nes/common/path-view.cc index 47f198b..743c060 100644 --- a/lib/nes/common/path-view.cc +++ b/lib/nes/common/path-view.cc @@ -1,10 +1,12 @@ #include "nes/common/path-view.hh" +#include "nes/common/debug.hh" namespace nes { auto path_view::iterator::operator*() const -> std::string_view { - return current_ ? std::string_view{ current_, component_length_ } : ""; + NES_ASSERT(current_ != nullptr && component_length_ > 0 && "invalid iterator"); + return std::string_view{ current_, component_length_ }; } auto path_view::iterator::operator++() -> iterator& diff --git a/lib/nes/common/span.hh b/lib/nes/common/span.hh index 16eb129..6779c37 100644 --- a/lib/nes/common/span.hh +++ b/lib/nes/common/span.hh @@ -1,6 +1,7 @@ #pragma once #include "nes/common/types.hh" +#include "nes/common/debug.hh" namespace nes { @@ -21,6 +22,9 @@ namespace nes { using type = T; }; + + template + using remove_const_type = typename remove_const::type; } // namespace detail /// Implementation of a container similar to std::span in C++20. @@ -54,7 +58,7 @@ namespace nes static_assert(Length == ActualLength || Length == detail::span_dynamic_length); } - span(span::type, Length> const other) + span(span, Length> const other) : span{ other.data_, other.length_, {} } { } @@ -65,12 +69,31 @@ namespace nes auto begin() const -> iterator { return data_; } auto end() const -> iterator { return data_ + length_; } - auto subspan(u32 const first, u32 const length) const -> span { return span{ data_ + first, length }; } - auto subspan(u32 const first) const -> span { return subspan(length_ - first); } + auto subspan(u32 const first, u32 const length) const -> span + { + NES_ASSERT(first + length <= get_length() && "subspan out of bounds"); + return span{ data_ + first, length }; + } + + auto subspan(u32 const first) const -> span + { + NES_ASSERT(first <= get_length() && "subspan out of bounds"); + return subspan(length_ - first); + } + template - auto subspan(u32 const first) const -> span { return span{ data_ + first, OtherLength, {} }; } + auto subspan(u32 const first) const -> span + { + static_assert(OtherLength < Length); + NES_ASSERT(first + OtherLength <= get_length() && "subspan out of bounds"); + return span{ data_ + first, OtherLength, {} }; + } - auto operator[](u32 const index) const -> T& { return data_[index]; } + auto operator[](u32 const index) const -> T& + { + NES_ASSERT(index < get_length() && "index out of bounds"); + return data_[index]; + } private: explicit span(T* data, u32 const length, detail::span_unchecked) diff --git a/lib/nes/common/status.hh b/lib/nes/common/status.hh new file mode 100644 index 0000000..4b23b60 --- /dev/null +++ b/lib/nes/common/status.hh @@ -0,0 +1,42 @@ +#pragma once + +namespace nes +{ + /// A status code. + enum class status + { + success, + error_system_error, + error_invalid_ines_data, + error_unsupported_mapper, + error_unsupported_instruction, + error_buffer_overflow, + error_invalid_path, + error_invalid_format_string, + }; + + constexpr auto to_string(status const status) -> char const* + { + switch (status) + { + case status::success: + return "Success"; + case status::error_system_error: + return "System error"; + case status::error_invalid_ines_data: + return "Invalid iNES data"; + case status::error_unsupported_mapper: + return "Unsupported mapper"; + case status::error_unsupported_instruction: + return "Unsupported instruction"; + case status::error_buffer_overflow: + return "Buffer overflow"; + case status::error_invalid_path: + return "Invalid path"; + case status::error_invalid_format_string: + return "Invalid format string"; + } + + return "(invalid)"; + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/common/string-builder.cc b/lib/nes/common/string-builder.cc index 087f0d4..7747aaa 100644 --- a/lib/nes/common/string-builder.cc +++ b/lib/nes/common/string-builder.cc @@ -7,70 +7,73 @@ namespace nes { } - auto string_builder::append_char(char const value) -> string_builder& + auto string_builder::append_char(char const value) -> status { - if (length_ == buffer_.get_length()) - { - is_good_ = false; - return *this; - } + if (length_ == buffer_.get_length()) { return status::error_buffer_overflow; } buffer_[length_] = value; length_ += 1; - return *this; + return status::success; } - auto string_builder::append_string(std::string_view const value) -> string_builder& + auto string_builder::append_string(std::string_view const value) -> status { + auto copy = *this; for (auto const c : value) { - append_char(c); + if (auto const s = copy.append_char(c); s != status::success) { return s; } } - return *this; + *this = copy; + return status::success; } - auto string_builder::append_int(i64 value, number_format const format) -> string_builder& + auto string_builder::append_int(i64 value, number_format const format) -> status { + auto copy = *this; if (value < 0) { - append_char('-'); + if (auto const s = copy.append_char('-'); s != status::success) { return s; } value += 1; // to prevent lossy conversion to unsigned auto abs_value = static_cast(-value); abs_value += 1; // to correct the previous offset - append_int(abs_value, format); + if (auto const s = copy.append_int(abs_value, format); s != status::success) { return s; } } else { - append_int(static_cast(value), format); + if (auto const s = copy.append_int(static_cast(value), format); s != status::success) { return s; } } - return *this; + *this = copy; + return status::success; } - auto string_builder::append_int(u64 value, number_format const format) -> string_builder& + auto string_builder::append_int(u64 value, number_format const format) -> status { constexpr auto max_string_length = u32{ 64 }; // worst case is a 64-bit integer in binary representation constexpr auto digits = "0123456789abcdef"; + auto copy = *this; auto base = u64{ 10 }; + auto prefix = ""; switch (format) { case number_format::binary: - append_string("0b"); + prefix = "0b"; base = 2; break; case number_format::octal: - append_string("0"); + prefix = "0"; base = 8; break; case number_format::decimal: break; case number_format::hexadecimal: - append_string("0x"); + prefix = "0x"; base = 16; break; } + if (auto const s = copy.append_string(prefix); s != status::success) { return s; } char buf[max_string_length + 1] = {}; // null-terminated auto pos = max_string_length; @@ -89,19 +92,21 @@ namespace nes buf[pos] = '0'; } - append_string(&buf[pos]); - return *this; + if (const auto s = copy.append_string(&buf[pos]); s != status::success) { return s; } + + *this = copy; + return status::success; } - auto string_builder::append_bool(bool const value) -> string_builder& + auto string_builder::append_bool(bool const value) -> status { - append_string(value ? "true" : "false"); - return *this; + return append_string(value ? "true" : "false"); } - auto string_builder::append_format( - std::string_view const fmt, format_arg const* args, u32 const arg_count) -> string_builder& + auto string_builder::append_format(std::string_view const fmt, format_arg const* args, u32 const arg_count) -> status { + auto copy = *this; + // Go through the format string, inserting the arguments for the templates as necessary. auto current_arg = u32{ 0 }; for (auto i = u32{ 0 }; i < fmt.length(); ++i) @@ -112,7 +117,7 @@ namespace nes if (fmt[i] == '{') { // escaped '{' - append_char('{'); + if (auto const s = copy.append_char('{'); s != status::success) { return s; } } else { @@ -128,21 +133,20 @@ namespace nes if (fmt[i] != '}') { // unmatched '{' - is_good_ = false; - break; + return status::error_invalid_format_string; } // end of template auto const p = std::string_view{ params, params_length }; if (current_arg < arg_count) { - append_format_arg(args[current_arg], p); + if (auto const s = copy.append_format_arg(args[current_arg], p); s != status::success) { return s; } current_arg += 1; } else { // missing argument in parameter pack - is_good_ = false; + return status::error_invalid_format_string; } } } @@ -153,39 +157,41 @@ namespace nes if (fmt[i] == '}') { // escaped '}' - append_char('}'); + if (auto const s = copy.append_char('}'); s != status::success) { return s; } } else { // unmatched '}' - is_good_ = false; - break; + return status::error_invalid_format_string; } } else { // normal character in the template string - append_char(fmt[i]); + if (auto const s = copy.append_char(fmt[i]); s != status::success) { return s; } } } if (current_arg < arg_count) { // not all supplied arguments were used - is_good_ = false; + return status::error_invalid_format_string; } - return *this; + *this = copy; + return status::success; } - auto string_builder::append_format_arg(format_arg const arg, std::string_view const params) -> string_builder& + auto string_builder::append_format_arg(format_arg const arg, std::string_view const params) -> status { auto const parse_params = [&] { if (!params.empty()) { - is_good_ = false; + return status::error_invalid_format_string; } + + return status::success; }; auto const parse_params_integer = [&](number_format& out_format) @@ -208,47 +214,43 @@ namespace nes } else { - out_format = number_format::decimal; - is_good_ = false; + return status::error_invalid_format_string; } + + return status::success; }; switch (arg.type) { case format_arg_type::string: { - parse_params(); - append_string(arg.string); - break; + if (auto const s = parse_params(); s != status::success) { return s; } + return append_string(arg.string); } case format_arg_type::character: { - parse_params(); - append_char(arg.character); - break; + if (auto const s = parse_params(); s != status::success) { return s; } + return append_char(arg.character); } case format_arg_type::signed_integer: { auto format = number_format{}; - parse_params_integer(format); - append_int(arg.signed_integer, format); - break; + if (auto const s = parse_params_integer(format); s != status::success) { return s; } + return append_int(arg.signed_integer, format); } case format_arg_type::unsigned_integer: { auto format = number_format{}; - parse_params_integer(format); - append_int(arg.unsigned_integer, format); - break; + if (auto const s = parse_params_integer(format); s != status::success) { return s; } + return append_int(arg.unsigned_integer, format); } case format_arg_type::boolean: { - parse_params(); - append_bool(arg.character); - break; + if (auto const s = parse_params(); s != status::success) { return s; } + return append_bool(arg.character); } } - return *this; + return status::error_invalid_format_string; } } // namespace nes \ No newline at end of file diff --git a/lib/nes/common/string-builder.hh b/lib/nes/common/string-builder.hh index 256c267..0298b8e 100644 --- a/lib/nes/common/string-builder.hh +++ b/lib/nes/common/string-builder.hh @@ -2,6 +2,7 @@ #include "nes/common/types.hh" #include "nes/common/span.hh" +#include "nes/common/status.hh" #include namespace nes @@ -17,9 +18,11 @@ namespace nes /// Builds a string into a fixed-length buffer. class string_builder { + template + friend class string_buffer; + span buffer_; u32 length_{ 0 }; - bool is_good_{ true }; enum class format_arg_type { @@ -65,14 +68,11 @@ namespace nes /// Get the resulting string. auto get_result() const -> std::string_view { return std::string_view{ buffer_.get_data(), length_ }; } - /// Returns false after an invalid format string or buffer overflow. - auto is_good() const -> bool { return is_good_; } - - auto append_char(char) -> string_builder&; - auto append_string(std::string_view) -> string_builder&; - auto append_int(i64, number_format = number_format::decimal) -> string_builder&; - auto append_int(u64, number_format = number_format::decimal) -> string_builder&; - auto append_bool(bool) -> string_builder&; + auto append_char(char) -> status; + auto append_string(std::string_view) -> status; + auto append_int(i64, number_format = number_format::decimal) -> status; + auto append_int(u64, number_format = number_format::decimal) -> status; + auto append_bool(bool) -> status; /// Print a templated string. /// @@ -82,7 +82,7 @@ namespace nes /// /// Curly braces can be escaped by repeating them twice like {{this}}. template - auto append_format(std::string_view const fmt, Args... args) -> string_builder& + auto append_format(std::string_view const fmt, Args... args) -> status { constexpr u32 arg_count = sizeof...(Args); // Add another argument at the end so we don't have an empty array expression. @@ -92,8 +92,8 @@ namespace nes } private: - auto append_format(std::string_view fmt, format_arg const* args, u32 arg_count) -> string_builder&; - auto append_format_arg(format_arg, std::string_view params) -> string_builder&; + auto append_format(std::string_view fmt, format_arg const* args, u32 arg_count) -> status; + auto append_format_arg(format_arg, std::string_view params) -> status; }; /// A string builder with a buffer embedded into it. @@ -107,5 +107,18 @@ namespace nes : string_builder{ buffer_ } { } + + string_buffer(string_buffer const& other) + : string_builder{ buffer_ } + { + std::copy_n(other.buffer_, Capacity, buffer_); + length_ = other.length_; + } + + auto operator=(string_buffer const& other) + { + std::copy_n(other.buffer_, Capacity, buffer_); + length_ = other.length_; + } }; } // namespace nes \ No newline at end of file -- cgit v1.3.1