wireforge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. wireforge/__init__.py +3 -0
  2. wireforge/_data/cmake/WireForgeConfig.cmake +29 -0
  3. wireforge/_data/cmake/WireForgeConfigVersion.cmake +15 -0
  4. wireforge/_data/cmake/WireForgeGenerate.cmake +64 -0
  5. wireforge/_data/runtime/LICENSE +21 -0
  6. wireforge/_data/runtime/include/wireforge/array_access.hpp +35 -0
  7. wireforge/_data/runtime/include/wireforge/bit_utils.hpp +226 -0
  8. wireforge/_data/runtime/include/wireforge/byte_io.hpp +387 -0
  9. wireforge/_data/runtime/include/wireforge/checksum.hpp +201 -0
  10. wireforge/_data/runtime/include/wireforge/codec_support.hpp +412 -0
  11. wireforge/_data/runtime/include/wireforge/config.hpp +41 -0
  12. wireforge/_data/runtime/include/wireforge/delegate.hpp +101 -0
  13. wireforge/_data/runtime/include/wireforge/dispatcher.hpp +339 -0
  14. wireforge/_data/runtime/include/wireforge/expr_support.hpp +38 -0
  15. wireforge/_data/runtime/include/wireforge/frame_decoder.hpp +262 -0
  16. wireforge/_data/runtime/include/wireforge/frame_layout.hpp +20 -0
  17. wireforge/_data/runtime/include/wireforge/link.hpp +223 -0
  18. wireforge/_data/runtime/include/wireforge/lock.hpp +146 -0
  19. wireforge/_data/runtime/include/wireforge/ring_buffer.hpp +226 -0
  20. wireforge/_data/runtime/include/wireforge/span.hpp +116 -0
  21. wireforge/_data/runtime/include/wireforge/static_vector.hpp +264 -0
  22. wireforge/_data/runtime/include/wireforge/status.hpp +75 -0
  23. wireforge/_data/runtime/include/wireforge/transport.hpp +95 -0
  24. wireforge/_data/runtime/include/wireforge/validation.hpp +153 -0
  25. wireforge/_data/runtime/include/wireforge/version.hpp +22 -0
  26. wireforge/_data/runtime/requirements.yaml +438 -0
  27. wireforge/_data/schema/deviations.schema.json +47 -0
  28. wireforge/_data/schema/fragment.schema.json +79 -0
  29. wireforge/_data/schema/llr_ids.schema.json +25 -0
  30. wireforge/_data/schema/project.schema.json +1240 -0
  31. wireforge/_data/schema/runtime_requirements.schema.json +34 -0
  32. wireforge/_data/ui/assets/index-6Pomu6U4.js +44 -0
  33. wireforge/_data/ui/assets/index-GVdDyUAG.css +1 -0
  34. wireforge/_data/ui/index.html +13 -0
  35. wireforge/bundle.py +177 -0
  36. wireforge/ci_check.py +155 -0
  37. wireforge/cli.py +617 -0
  38. wireforge/codec.py +897 -0
  39. wireforge/compat.py +536 -0
  40. wireforge/coverage_points.py +64 -0
  41. wireforge/cpp.py +273 -0
  42. wireforge/cpp_codec.py +440 -0
  43. wireforge/cpp_interface.py +874 -0
  44. wireforge/cpp_tests.py +1993 -0
  45. wireforge/differential.py +586 -0
  46. wireforge/docs_markdown.py +72 -0
  47. wireforge/expr.py +598 -0
  48. wireforge/findings.py +187 -0
  49. wireforge/fmt.py +351 -0
  50. wireforge/generate.py +644 -0
  51. wireforge/golden.py +253 -0
  52. wireforge/hlr_import.py +112 -0
  53. wireforge/impact.py +249 -0
  54. wireforge/importers.py +402 -0
  55. wireforge/issues.py +27 -0
  56. wireforge/layout.py +681 -0
  57. wireforge/lifecycle.py +1266 -0
  58. wireforge/lifecycle_results.py +205 -0
  59. wireforge/lifecycle_rules.py +78 -0
  60. wireforge/lifecycle_runtime.py +375 -0
  61. wireforge/llr.py +693 -0
  62. wireforge/llr_ids.py +104 -0
  63. wireforge/loader.py +422 -0
  64. wireforge/mcp_server.py +357 -0
  65. wireforge/model.py +322 -0
  66. wireforge/plain.py +15 -0
  67. wireforge/py.typed +0 -0
  68. wireforge/reqif.py +289 -0
  69. wireforge/resources.py +43 -0
  70. wireforge/results.py +377 -0
  71. wireforge/runtime_reqs.py +147 -0
  72. wireforge/schema.py +151 -0
  73. wireforge/server/__init__.py +1 -0
  74. wireforge/server/app.py +619 -0
  75. wireforge/server/oidc.py +99 -0
  76. wireforge/server/storage.py +146 -0
  77. wireforge/service.py +1354 -0
  78. wireforge/templates/command/project.yaml +38 -0
  79. wireforge/templates/raw/project.yaml +28 -0
  80. wireforge/templates/telemetry/project.yaml +51 -0
  81. wireforge/test_cases.py +475 -0
  82. wireforge/test_project.py +478 -0
  83. wireforge/trace.py +442 -0
  84. wireforge/validate.py +1241 -0
  85. wireforge/vcs.py +44 -0
  86. wireforge/vectors.py +222 -0
  87. wireforge/versioning.py +137 -0
  88. wireforge/wireshark.py +792 -0
  89. wireforge/wireshark_runtime.lua +725 -0
  90. wireforge-0.1.0.dist-info/METADATA +91 -0
  91. wireforge-0.1.0.dist-info/RECORD +96 -0
  92. wireforge-0.1.0.dist-info/WHEEL +5 -0
  93. wireforge-0.1.0.dist-info/entry_points.txt +2 -0
  94. wireforge-0.1.0.dist-info/licenses/LICENSE +98 -0
  95. wireforge-0.1.0.dist-info/licenses/runtime/LICENSE +21 -0
  96. wireforge-0.1.0.dist-info/top_level.txt +1 -0
wireforge/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """WireForge: schema-driven wire-format toolkit."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,29 @@
1
+ # CMake package of WireForge: find_package(WireForge) gives
2
+ # - wireforge::runtime: the header-only C++17 runtime;
3
+ # - wireforge_generate(): generates C++ code from a WireForge project at configure time.
4
+ # Works from an installed tree (<prefix>/lib/cmake/WireForge) and from the Python package
5
+ # (`wireforge cmake-dir` prints this directory).
6
+ include_guard(GLOBAL)
7
+
8
+ set(WIREFORGE_INCLUDE_DIR "")
9
+ foreach(candidate "${CMAKE_CURRENT_LIST_DIR}/../runtime/include" "${CMAKE_CURRENT_LIST_DIR}/../../../include"
10
+ "${CMAKE_CURRENT_LIST_DIR}/../../runtime/include")
11
+ if(NOT WIREFORGE_INCLUDE_DIR AND EXISTS "${candidate}/wireforge/status.hpp")
12
+ get_filename_component(WIREFORGE_INCLUDE_DIR "${candidate}" ABSOLUTE)
13
+ endif()
14
+ endforeach()
15
+ if(NOT WIREFORGE_INCLUDE_DIR)
16
+ set(WireForge_FOUND FALSE)
17
+ set(WireForge_NOT_FOUND_MESSAGE "the WireForge runtime headers were not found next to ${CMAKE_CURRENT_LIST_DIR}")
18
+ return()
19
+ endif()
20
+
21
+ if(NOT TARGET wireforge::runtime)
22
+ add_library(wireforge::runtime INTERFACE IMPORTED)
23
+ set_target_properties(wireforge::runtime PROPERTIES
24
+ INTERFACE_INCLUDE_DIRECTORIES "${WIREFORGE_INCLUDE_DIR}"
25
+ INTERFACE_COMPILE_FEATURES cxx_std_17)
26
+ endif()
27
+
28
+ include("${CMAKE_CURRENT_LIST_DIR}/WireForgeGenerate.cmake")
29
+ set(WireForge_FOUND TRUE)
@@ -0,0 +1,15 @@
1
+ # Version of the WireForge CMake package; compatible within the same major version.
2
+ set(PACKAGE_VERSION "0.1.0")
3
+ if(PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION)
4
+ set(PACKAGE_VERSION_COMPATIBLE FALSE)
5
+ else()
6
+ string(REGEX MATCH "^[0-9]+" _wireforge_major "${PACKAGE_VERSION}")
7
+ if(PACKAGE_FIND_VERSION_MAJOR STREQUAL "" OR PACKAGE_FIND_VERSION_MAJOR STREQUAL _wireforge_major)
8
+ set(PACKAGE_VERSION_COMPATIBLE TRUE)
9
+ else()
10
+ set(PACKAGE_VERSION_COMPATIBLE FALSE)
11
+ endif()
12
+ if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
13
+ set(PACKAGE_VERSION_EXACT TRUE)
14
+ endif()
15
+ endif()
@@ -0,0 +1,64 @@
1
+ # wireforge_generate(<target> PROJECT <dir> [OUTPUT <dir>] [PLUGINS <file>] [TESTS])
2
+ #
3
+ # Runs `wireforge generate` at configure time (again whenever a model file changes) and creates
4
+ # the INTERFACE library <target> with the generated headers, the project's user include
5
+ # directories and wireforge::runtime. With TESTS the GoogleTest project is generated too
6
+ # (<OUTPUT>/<project>/generated_tests). The generator is the `wireforge` command, or
7
+ # `python -m wireforge.cli` when only the Python package is found (WIREFORGE_COMMAND overrides).
8
+ include_guard(GLOBAL)
9
+
10
+ function(_wireforge_command result)
11
+ if(WIREFORGE_COMMAND)
12
+ set(${result} ${WIREFORGE_COMMAND} PARENT_SCOPE)
13
+ return()
14
+ endif()
15
+ find_program(WIREFORGE_EXECUTABLE wireforge)
16
+ if(WIREFORGE_EXECUTABLE)
17
+ set(${result} "${WIREFORGE_EXECUTABLE}" PARENT_SCOPE)
18
+ return()
19
+ endif()
20
+ find_package(Python3 REQUIRED COMPONENTS Interpreter)
21
+ set(${result} "${Python3_EXECUTABLE}" -m wireforge.cli PARENT_SCOPE)
22
+ endfunction()
23
+
24
+ function(wireforge_generate target)
25
+ cmake_parse_arguments(PARSE_ARGV 1 arg "TESTS" "PROJECT;OUTPUT;PLUGINS" "")
26
+ if(NOT arg_PROJECT)
27
+ message(FATAL_ERROR "wireforge_generate(${target}): PROJECT is required")
28
+ endif()
29
+ get_filename_component(project "${arg_PROJECT}" ABSOLUTE)
30
+ set(output "${CMAKE_CURRENT_BINARY_DIR}/wireforge_${target}")
31
+ if(arg_OUTPUT)
32
+ get_filename_component(output "${arg_OUTPUT}" ABSOLUTE)
33
+ endif()
34
+ _wireforge_command(command)
35
+ set(arguments generate "${project}" -o "${output}")
36
+ if(arg_TESTS)
37
+ list(APPEND arguments --tests)
38
+ endif()
39
+ if(arg_PLUGINS)
40
+ list(APPEND arguments --plugins "${arg_PLUGINS}")
41
+ endif()
42
+ execute_process(COMMAND ${command} ${arguments} RESULT_VARIABLE failed OUTPUT_VARIABLE log ERROR_VARIABLE log)
43
+ if(failed)
44
+ message(FATAL_ERROR "wireforge_generate(${target}) failed:\n${log}")
45
+ endif()
46
+ file(GLOB_RECURSE model_files CONFIGURE_DEPENDS "${project}/*.yaml")
47
+ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${model_files})
48
+ execute_process(COMMAND ${command} get "${project}" /user_includes/include_dirs --json
49
+ RESULT_VARIABLE missing OUTPUT_VARIABLE answer ERROR_QUIET)
50
+ set(include_dirs "")
51
+ if(NOT missing)
52
+ string(JSON count LENGTH "${answer}" data value)
53
+ if(count GREATER 0)
54
+ math(EXPR last "${count} - 1")
55
+ foreach(index RANGE ${last})
56
+ string(JSON directory GET "${answer}" data value ${index})
57
+ list(APPEND include_dirs "${project}/${directory}")
58
+ endforeach()
59
+ endif()
60
+ endif()
61
+ add_library(${target} INTERFACE)
62
+ target_include_directories(${target} INTERFACE "${output}" ${include_dirs})
63
+ target_link_libraries(${target} INTERFACE wireforge::runtime)
64
+ endfunction()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oguzhan KAYNAK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @file array_access.hpp
3
+ * @brief Element access of std::array without the exception path of std::array::at().
4
+ */
5
+ #ifndef WIREFORGE_ARRAY_ACCESS_HPP
6
+ #define WIREFORGE_ARRAY_ACCESS_HPP
7
+
8
+ #include <array>
9
+ #include <cstddef>
10
+ #include <iterator>
11
+
12
+ namespace wireforge {
13
+
14
+ /**
15
+ * @brief Element @p index of @p array.
16
+ *
17
+ * The caller guarantees `index < N` (the runtime only uses indices that are bounded by
18
+ * construction). Unlike std::array::at() this references no exception-throwing library code, so
19
+ * builds without exceptions and without heap stay free of them.
20
+ */
21
+ template <typename T, std::size_t N>
22
+ [[nodiscard]] constexpr T& array_at(std::array<T, N>& array, std::size_t index) noexcept {
23
+ return *std::next(array.begin(), static_cast<std::ptrdiff_t>(index));
24
+ }
25
+
26
+ /// @brief Element @p index of a const @p array (see the non-const overload).
27
+ template <typename T, std::size_t N>
28
+ [[nodiscard]] constexpr const T& array_at(const std::array<T, N>& array,
29
+ std::size_t index) noexcept {
30
+ return *std::next(array.begin(), static_cast<std::ptrdiff_t>(index));
31
+ }
32
+
33
+ } // namespace wireforge
34
+
35
+ #endif // WIREFORGE_ARRAY_ACCESS_HPP
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @file bit_utils.hpp
3
+ * @brief Bit masks, two's complement helpers and packing of bit-field groups.
4
+ */
5
+ #ifndef WIREFORGE_BIT_UTILS_HPP
6
+ #define WIREFORGE_BIT_UTILS_HPP
7
+
8
+ #include "wireforge/status.hpp"
9
+
10
+ #include <cstdint>
11
+ #include <type_traits>
12
+
13
+ namespace wireforge {
14
+
15
+ /**
16
+ * @brief Converts @p value to the integer type @p To; the value must fit.
17
+ *
18
+ * For conversions whose source width depends on the platform (std::size_t is 32 bits wide on
19
+ * 32-bit targets), so that the same code is free of useless-cast and conversion warnings on
20
+ * every target.
21
+ */
22
+ template <typename To, typename From> [[nodiscard]] constexpr To narrow_to(From value) noexcept {
23
+ static_assert(std::is_integral_v<To> && std::is_integral_v<From>, "integer conversions only");
24
+ return static_cast<To>(value);
25
+ }
26
+
27
+ /// @brief Order in which the fields of a bit group are placed in the group.
28
+ enum class BitOrder : std::uint8_t {
29
+ MsbFirst, ///< The first field takes the most significant bits of the group.
30
+ LsbFirst, ///< The first field takes the least significant bits of the group.
31
+ };
32
+
33
+ /// @brief Largest supported bit width (fields and bit groups).
34
+ inline constexpr unsigned kMaxBits = 64U;
35
+
36
+ /**
37
+ * @brief Mask with the low @p bits bits set.
38
+ * @param bits Number of bits, 0..64.
39
+ */
40
+ [[nodiscard]] constexpr std::uint64_t low_mask(unsigned bits) noexcept {
41
+ return bits >= kMaxBits ? ~std::uint64_t{0U} : ((std::uint64_t{1U} << bits) - 1U);
42
+ }
43
+
44
+ /// @brief true when @p value is representable as an unsigned integer of @p bits bits.
45
+ [[nodiscard]] constexpr bool fits_unsigned(std::uint64_t value, unsigned bits) noexcept {
46
+ return (value & ~low_mask(bits)) == 0U;
47
+ }
48
+
49
+ /**
50
+ * @brief true when @p value is representable as a two's complement integer of @p bits bits.
51
+ * @param value Value to test.
52
+ * @param bits Width, 1..64 (0 always gives false).
53
+ */
54
+ [[nodiscard]] constexpr bool fits_signed(std::int64_t value, unsigned bits) noexcept {
55
+ if (bits == 0U) {
56
+ return false;
57
+ }
58
+ if (bits >= kMaxBits) {
59
+ return true;
60
+ }
61
+ const std::int64_t limit = std::int64_t{1} << (bits - 1U);
62
+ return value >= -limit && value < limit;
63
+ }
64
+
65
+ /// @brief Two's complement bit pattern of @p value truncated to @p bits bits.
66
+ [[nodiscard]] constexpr std::uint64_t to_twos_complement(std::int64_t value,
67
+ unsigned bits) noexcept {
68
+ return static_cast<std::uint64_t>(value) & low_mask(bits);
69
+ }
70
+
71
+ /**
72
+ * @brief Interprets the low @p bits bits of @p raw as two's complement and sign-extends them.
73
+ *
74
+ * Avoids the implementation-defined unsigned-to-signed conversion of C++17.
75
+ * @param raw Bit pattern; bits above @p bits are ignored.
76
+ * @param bits Width, 1..64.
77
+ */
78
+ [[nodiscard]] constexpr std::int64_t sign_extend(std::uint64_t raw, unsigned bits) noexcept {
79
+ const std::uint64_t mask = low_mask(bits);
80
+ const std::uint64_t value = raw & mask;
81
+ const std::uint64_t sign_bit = std::uint64_t{1U} << (bits - 1U);
82
+ if ((value & sign_bit) == 0U) {
83
+ return static_cast<std::int64_t>(value);
84
+ }
85
+ return -static_cast<std::int64_t>(~value & mask) - 1;
86
+ }
87
+
88
+ /// @brief Reverses the order of the low @p bits bits of @p value.
89
+ [[nodiscard]] constexpr std::uint64_t reflect(std::uint64_t value, unsigned bits) noexcept {
90
+ std::uint64_t result = 0U;
91
+ for (unsigned index = 0U; index < bits; ++index) {
92
+ result = (result << 1U) | ((value >> index) & 1U);
93
+ }
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * @brief Packs consecutive bit fields into one container integer.
99
+ *
100
+ * The group is 8..64 bits wide, a multiple of 8. Fields are added in declaration order with put();
101
+ * when complete(), the container is written with ByteWriter::write_bits in the message byte order.
102
+ */
103
+ class BitPacker {
104
+ public:
105
+ /**
106
+ * @param total_bits Width of the group: 8, 16, ..., 64.
107
+ * @param order Placement of the first field.
108
+ */
109
+ constexpr BitPacker(unsigned total_bits, BitOrder order) noexcept
110
+ : total_bits_(total_bits), order_(order) {}
111
+
112
+ /**
113
+ * @brief Appends the next unsigned field.
114
+ * @param value Field value.
115
+ * @param bits Field width.
116
+ * @return Status::OutOfRange when @p value needs more than @p bits bits; Status::InvalidState
117
+ * when the group width is invalid, @p bits is 0 or the field does not fit the group.
118
+ */
119
+ constexpr Status put(std::uint64_t value, unsigned bits) noexcept {
120
+ if (!valid() || bits == 0U || bits > total_bits_ - used_bits_) {
121
+ return Status::InvalidState;
122
+ }
123
+ if (!fits_unsigned(value, bits)) {
124
+ return Status::OutOfRange;
125
+ }
126
+ const unsigned shift =
127
+ order_ == BitOrder::MsbFirst ? total_bits_ - used_bits_ - bits : used_bits_;
128
+ container_ |= value << shift;
129
+ used_bits_ += bits;
130
+ return Status::Ok;
131
+ }
132
+
133
+ /**
134
+ * @brief Appends the next signed field as @p bits-bit two's complement.
135
+ * @return As put(); Status::OutOfRange when @p value does not fit @p bits bits.
136
+ */
137
+ constexpr Status put_signed(std::int64_t value, unsigned bits) noexcept {
138
+ if (!fits_signed(value, bits)) {
139
+ return bits == 0U ? Status::InvalidState : Status::OutOfRange;
140
+ }
141
+ return put(to_twos_complement(value, bits), bits);
142
+ }
143
+
144
+ /// @brief true when the group width is 8..64 and a multiple of 8.
145
+ [[nodiscard]] constexpr bool valid() const noexcept {
146
+ return total_bits_ >= 8U && total_bits_ <= kMaxBits && total_bits_ % 8U == 0U;
147
+ }
148
+ /// @brief true when the fields fill the whole group.
149
+ [[nodiscard]] constexpr bool complete() const noexcept {
150
+ return valid() && used_bits_ == total_bits_;
151
+ }
152
+ /// @brief Width of the group in bits.
153
+ [[nodiscard]] constexpr unsigned total_bits() const noexcept { return total_bits_; }
154
+ /// @brief Packed value of the group.
155
+ [[nodiscard]] constexpr std::uint64_t container() const noexcept { return container_; }
156
+
157
+ private:
158
+ unsigned total_bits_;
159
+ unsigned used_bits_ = 0U;
160
+ BitOrder order_;
161
+ std::uint64_t container_ = 0U;
162
+ };
163
+
164
+ /// @brief Splits the container integer of a bit group back into fields, in declaration order.
165
+ class BitUnpacker {
166
+ public:
167
+ /**
168
+ * @param container Packed group value, usually from ByteReader::read_bits.
169
+ * @param total_bits Width of the group: 8, 16, ..., 64.
170
+ * @param order Placement of the first field.
171
+ */
172
+ constexpr BitUnpacker(std::uint64_t container, unsigned total_bits, BitOrder order) noexcept
173
+ : container_(container), total_bits_(total_bits), order_(order) {}
174
+
175
+ /**
176
+ * @brief Extracts the next unsigned field.
177
+ * @param[out] value Field value; unchanged on failure.
178
+ * @param bits Field width.
179
+ * @return Status::InvalidState when the group is invalid, @p bits is 0 or the group is
180
+ * exhausted.
181
+ */
182
+ constexpr Status get(std::uint64_t& value, unsigned bits) noexcept {
183
+ if (!valid() || bits == 0U || bits > total_bits_ - used_bits_) {
184
+ return Status::InvalidState;
185
+ }
186
+ const unsigned shift =
187
+ order_ == BitOrder::MsbFirst ? total_bits_ - used_bits_ - bits : used_bits_;
188
+ value = (container_ >> shift) & low_mask(bits);
189
+ used_bits_ += bits;
190
+ return Status::Ok;
191
+ }
192
+
193
+ /**
194
+ * @brief Extracts the next field as @p bits-bit two's complement, sign-extended.
195
+ * @param[out] value Field value; unchanged on failure.
196
+ * @param bits Field width.
197
+ * @return As get().
198
+ */
199
+ constexpr Status get_signed(std::int64_t& value, unsigned bits) noexcept {
200
+ std::uint64_t raw = 0U;
201
+ const Status status = get(raw, bits);
202
+ if (status == Status::Ok) {
203
+ value = sign_extend(raw, bits);
204
+ }
205
+ return status;
206
+ }
207
+
208
+ /// @brief true when the group width is 8..64 and a multiple of 8.
209
+ [[nodiscard]] constexpr bool valid() const noexcept {
210
+ return total_bits_ >= 8U && total_bits_ <= kMaxBits && total_bits_ % 8U == 0U;
211
+ }
212
+ /// @brief true when every bit of the group has been extracted.
213
+ [[nodiscard]] constexpr bool complete() const noexcept {
214
+ return valid() && used_bits_ == total_bits_;
215
+ }
216
+
217
+ private:
218
+ std::uint64_t container_;
219
+ unsigned total_bits_;
220
+ unsigned used_bits_ = 0U;
221
+ BitOrder order_;
222
+ };
223
+
224
+ } // namespace wireforge
225
+
226
+ #endif // WIREFORGE_BIT_UTILS_HPP