libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
libxr_string.hpp
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5#include <cstring>
6#include <limits>
7#include <string>
8#include <string_view>
9#include <type_traits>
10#include <utility>
11
12#include "libxr_def.hpp"
13#include "print.hpp"
14
15namespace LibXR
16{
17template <Print::Text Source, typename... Args>
19
20namespace Detail
21{
22
27template <typename... Values>
29{
30};
31
36template <typename T, bool IsEnum = std::is_enum_v<T>>
38{
39 using Type = T;
40};
41
42template <typename T>
44{
45 using Type = std::underlying_type_t<T>;
46};
47
53template <typename T>
54static constexpr bool runtime_string_always_false = false;
55
56inline constexpr size_t runtime_string_unbounded_capacity =
57 std::numeric_limits<size_t>::max();
58inline constexpr size_t runtime_string_max_field_width =
59 std::numeric_limits<uint8_t>::max();
60
74[[nodiscard]] constexpr size_t RuntimeStringFieldCapacity(Print::FormatPackKind pack)
75{
76 constexpr size_t width = runtime_string_max_field_width;
77 constexpr size_t precision = runtime_string_max_field_width;
78
79 switch (pack)
80 {
81 case Print::FormatPackKind::I32:
82 return 1U + precision;
83 case Print::FormatPackKind::I64:
84 return 1U + precision;
85 case Print::FormatPackKind::U32:
86 return 2U + precision;
87 case Print::FormatPackKind::U64:
88 return 2U + precision;
89 case Print::FormatPackKind::Pointer:
90 return 2U +
91 (precision > 2U * sizeof(uintptr_t) ? precision : 2U * sizeof(uintptr_t));
92 case Print::FormatPackKind::Character:
93 return width;
94 case Print::FormatPackKind::StringView:
95 return runtime_string_unbounded_capacity;
96 case Print::FormatPackKind::F32:
97 return 1U + static_cast<size_t>(std::numeric_limits<float>::max_exponent10) + 2U +
98 1U + precision;
99 case Print::FormatPackKind::F64:
100 return 1U + static_cast<size_t>(std::numeric_limits<double>::max_exponent10) + 2U +
101 1U + precision;
102 case Print::FormatPackKind::LongDouble:
103 return 1U + static_cast<size_t>(std::numeric_limits<long double>::max_exponent10) +
104 2U + 1U + precision;
105 }
106
107 return runtime_string_unbounded_capacity;
108}
109
110[[nodiscard]] constexpr bool RuntimeStringAddCapacity(size_t& total, size_t value)
111{
112 if (value == runtime_string_unbounded_capacity ||
113 runtime_string_unbounded_capacity - total < value)
114 {
115 return false;
116 }
117 total += value;
118 return true;
119}
120
121template <typename Built>
122[[nodiscard]] consteval size_t RuntimeStringMaxFormattedSize(size_t source_size)
123{
124 size_t total = source_size;
125 for (const auto& argument : Built::ArgumentList())
126 {
127 if (!RuntimeStringAddCapacity(total, RuntimeStringFieldCapacity(argument.pack)))
128 {
129 return runtime_string_unbounded_capacity;
130 }
131 }
132 return total;
133}
134
147template <typename T>
149{
150 using Decayed = std::remove_cvref_t<T>;
151 using Normalized = typename RuntimeStringNormalized<Decayed>::Type;
152
153 static constexpr bool is_text =
154 std::is_array_v<Decayed> || std::is_same_v<Decayed, char*> ||
155 std::is_same_v<Decayed, const char*> || std::is_same_v<Decayed, std::string> ||
156 std::is_same_v<Decayed, std::string_view>;
157
158 static constexpr bool is_pointer =
159 std::is_pointer_v<Decayed> && !std::is_function_v<std::remove_pointer_t<Decayed>>;
160
161 static constexpr bool has_static_capacity =
162 !is_text &&
163 (std::is_same_v<Decayed, std::nullptr_t> || is_pointer ||
164 std::is_integral_v<Normalized> || std::is_floating_point_v<Normalized>);
165};
166
173template <typename... Args>
175{
176 template <typename... CallArgs>
177 static constexpr bool matches = std::is_same_v<
179 RuntimeStringTypeList<Args...>>;
180};
181
187{
188 std::string_view view = {};
189 ErrorCode status = ErrorCode::OK;
190};
191
192template <typename T>
193struct RuntimeStringViewTag : std::false_type
194{
195};
196
197template <Print::Text Source, typename... Args>
198struct RuntimeStringViewTag<RuntimeStringView<Source, Args...>> : std::true_type
199{
200};
201
202template <typename T>
203inline constexpr bool runtime_string_is_view_v =
205
211{
212 char* data = nullptr;
213 size_t capacity = 0;
214 size_t size = 0;
215
216 [[nodiscard]] ErrorCode Write(std::string_view chunk)
217 {
218 if (capacity - size < chunk.size())
219 {
221 }
222 if (!chunk.empty())
223 {
224 std::memcpy(data + size, chunk.data(), chunk.size());
225 size += chunk.size();
226 }
227 return ErrorCode::OK;
228 }
229};
230
231} // namespace Detail
232
249template <Print::Text Source = "", typename... Args>
251{
252 public:
253 static_assert((std::is_same_v<
254 Args, typename Detail::RuntimeStringArgumentTraits<Args>::Decayed> &&
255 ...),
256 "LibXR::RuntimeStringView argument types must be unqualified "
257 "value types");
259 "LibXR::RuntimeStringView formatted arguments cannot contain "
260 "runtime strings; use RuntimeStringView<> for text concatenation");
261
263 constexpr RuntimeStringView() = default;
264
265 RuntimeStringView(const RuntimeStringView&) = delete;
266 RuntimeStringView& operator=(const RuntimeStringView&) = delete;
267
270 : data_(other.data_),
271 size_(other.size_),
272 capacity_(other.capacity_),
273 status_(other.status_)
274 {
275 other.data_ = nullptr;
276 other.size_ = 0;
277 other.capacity_ = 0;
278 other.status_ = ErrorCode::OK;
279 }
280
281 RuntimeStringView& operator=(RuntimeStringView&&) = delete;
282
284 explicit RuntimeStringView(std::string_view text)
285 requires(Source.Size() == 0 && sizeof...(Args) == 0)
286 {
287 static_cast<void>(AssignCopy(text));
288 }
289
291 template <size_t N>
292 explicit RuntimeStringView(char (&text)[N])
293 requires(Source.Size() == 0 && sizeof...(Args) == 0)
294 {
295 static_cast<void>(AssignCopy(std::string_view(text, BoundedTextLength(text, N))));
296 }
297
299 template <size_t N>
300 explicit RuntimeStringView(const char (&text)[N])
301 requires(Source.Size() == 0 && sizeof...(Args) == 0)
302 {
303 static_cast<void>(AssignCopy(std::string_view(text, BoundedTextLength(text, N))));
304 }
305
308 template <typename CharPtr>
309 requires(Source.Size() == 0 && sizeof...(Args) == 0 &&
310 (std::is_same_v<CharPtr, char*> || std::is_same_v<CharPtr, const char*>))
311 explicit RuntimeStringView(CharPtr text)
312 {
313 static_cast<void>(AssignCopy(text));
314 }
315
318 explicit RuntimeStringView(std::nullptr_t text)
319 requires(Source.Size() == 0 && sizeof...(Args) == 0)
320 {
321 static_cast<void>(AssignCopy(static_cast<const char*>(text)));
322 }
323
325 template <typename First, typename Second, typename... Rest>
326 explicit RuntimeStringView(First&& first, Second&& second, Rest&&... rest)
327 requires(Source.Size() == 0 && sizeof...(Args) == 0)
328 {
329 static_cast<void>(AssignConcat(std::forward<First>(first),
330 std::forward<Second>(second),
331 std::forward<Rest>(rest)...));
332 }
333
338 template <typename... CallArgs>
339 [[nodiscard]] ErrorCode Reformat(CallArgs&&... args)
340 requires((Source.Size() != 0 || sizeof...(Args) != 0) &&
342 {
343 static_assert(LibXR::Format<Source>::template Matches<Args...>(),
344 "LibXR::RuntimeStringView format argument types do not match "
345 "the format");
346 using Built = typename LibXR::Format<Source>::template Compiled<Args...>;
347 static constexpr size_t max_size =
348 Detail::RuntimeStringMaxFormattedSize<Built>(Source.Size());
349 static_assert(max_size != Detail::runtime_string_unbounded_capacity,
350 "LibXR::RuntimeStringView cannot retain unbounded formatted "
351 "runtime strings");
352
353 return AssignFormatted(
354 max_size, [&](auto& sink)
355 { return Print::FormatTo<Source>(sink, std::forward<CallArgs>(args)...); });
356 }
357
362 template <typename... CallArgs>
363 [[nodiscard]] ErrorCode Reprintf(CallArgs&&... args)
364 requires((Source.Size() != 0 || sizeof...(Args) != 0) &&
366 {
367 static_assert(Print::Printf::Matches<Source, Args...>(),
368 "LibXR::RuntimeStringView printf argument types do not match "
369 "the format");
371 static constexpr size_t max_size =
372 Detail::RuntimeStringMaxFormattedSize<Built>(Source.Size());
373 static_assert(max_size != Detail::runtime_string_unbounded_capacity,
374 "LibXR::RuntimeStringView cannot retain unbounded formatted "
375 "runtime strings");
376
377 return AssignFormatted(
378 max_size, [&](auto& sink)
379 { return Print::PrintfTo<Source>(sink, std::forward<CallArgs>(args)...); });
380 }
381
383 [[nodiscard]] constexpr std::string_view View() const
384 {
385 return data_ == nullptr ? std::string_view{} : std::string_view(data_, size_);
386 }
387
389 [[nodiscard]] const char* CStr() const { return data_ == nullptr ? "" : data_; }
391 [[nodiscard]] constexpr size_t Size() const { return size_; }
393 [[nodiscard]] constexpr bool Empty() const { return size_ == 0; }
395 [[nodiscard]] constexpr ErrorCode Status() const { return status_; }
397 [[nodiscard]] constexpr operator std::string_view() const { return View(); }
399 [[nodiscard]] operator const char*() const { return CStr(); }
400
401 private:
407 [[nodiscard]] ErrorCode SetFailure(ErrorCode status)
408 {
409 if (data_ != nullptr)
410 {
411 data_[0] = '\0';
412 }
413 size_ = 0;
414 status_ = status;
415 return status_;
416 }
417
433 [[nodiscard]] ErrorCode EnsureCapacity(size_t payload_size)
434 {
435 if (payload_size == std::numeric_limits<size_t>::max())
436 {
438 }
439 if (payload_size <= capacity_ && data_ != nullptr)
440 {
441 return ErrorCode::OK;
442 }
443 if (data_ != nullptr)
444 {
446 }
447 if (payload_size == 0)
448 {
449 return ErrorCode::OK;
450 }
451
452 data_ = new char[payload_size + 1U];
453 capacity_ = payload_size;
454 return ErrorCode::OK;
455 }
456
461 [[nodiscard]] ErrorCode AssignCopy(std::string_view text)
462 {
463 ErrorCode status = EnsureCapacity(text.size());
464 if (status != ErrorCode::OK)
465 {
466 return SetFailure(status);
467 }
468 if (!text.empty())
469 {
470 std::memcpy(data_, text.data(), text.size());
471 }
472 if (data_ != nullptr)
473 {
474 data_[text.size()] = '\0';
475 }
476 size_ = text.size();
477 status_ = ErrorCode::OK;
478 return status_;
479 }
480
486 [[nodiscard]] ErrorCode AssignCopy(const char* text)
487 {
488 return text == nullptr ? SetFailure(ErrorCode::PTR_NULL)
489 : AssignCopy(std::string_view(text, std::strlen(text)));
490 }
491
501 template <typename... Parts>
502 [[nodiscard]] ErrorCode AssignConcat(Parts&&... parts)
503 {
504 size_t total_size = 0;
505 ErrorCode status = ErrorCode::OK;
506
507 auto count = [&](auto&& part)
508 {
509 if (status != ErrorCode::OK)
510 {
511 return;
512 }
513
514 Detail::RuntimeStringTextPart text =
515 NormalizeConcatPart(std::forward<decltype(part)>(part));
516 if (text.status != ErrorCode::OK)
517 {
518 status = text.status;
519 }
520 else if (std::numeric_limits<size_t>::max() - total_size < text.view.size())
521 {
523 }
524 else
525 {
526 total_size += text.view.size();
527 }
528 };
529 (count(std::forward<Parts>(parts)), ...);
530
531 if (status != ErrorCode::OK)
532 {
533 return SetFailure(status);
534 }
535 status = EnsureCapacity(total_size);
536 if (status != ErrorCode::OK)
537 {
538 return SetFailure(status);
539 }
540
541 size_t offset = 0;
542 auto copy = [&](auto&& part)
543 {
544 Detail::RuntimeStringTextPart text =
545 NormalizeConcatPart(std::forward<decltype(part)>(part));
546 if (!text.view.empty())
547 {
548 std::memcpy(data_ + offset, text.view.data(), text.view.size());
549 offset += text.view.size();
550 }
551 };
552 (copy(std::forward<Parts>(parts)), ...);
553
554 if (data_ != nullptr)
555 {
556 data_[total_size] = '\0';
557 }
558 size_ = total_size;
559 status_ = ErrorCode::OK;
560 return status_;
561 }
562
573 template <typename WriteFn>
574 [[nodiscard]] ErrorCode AssignFormatted(size_t max_size, WriteFn&& write)
575 {
576 if (data_ == nullptr)
577 {
578 ErrorCode status = EnsureCapacity(max_size);
579 if (status != ErrorCode::OK)
580 {
581 return SetFailure(status);
582 }
583 }
584
585 Detail::RuntimeStringBufferSink sink{.data = data_, .capacity = capacity_};
586 ErrorCode status = write(sink);
587 if (status != ErrorCode::OK)
588 {
589 return SetFailure(status);
590 }
591 if (data_ != nullptr)
592 {
593 data_[sink.size] = '\0';
594 }
595 size_ = sink.size;
596 status_ = ErrorCode::OK;
597 return status_;
598 }
599
605 [[nodiscard]] static constexpr size_t BoundedTextLength(const char* text,
606 size_t bound) noexcept
607 {
608 size_t size = 0;
609 while (size < bound && text[size] != '\0')
610 {
611 ++size;
612 }
613 return size;
614 }
615
623 template <typename T>
624 [[nodiscard]] static constexpr Detail::RuntimeStringTextPart NormalizeConcatPart(
625 T&& text)
626 {
627 using Bare = std::remove_cvref_t<T>;
628 if constexpr (Detail::runtime_string_is_view_v<T>)
629 {
630 const auto& retained = text;
631 return retained.Status() == ErrorCode::OK
632 ? Detail::RuntimeStringTextPart{.view = retained.View()}
633 : Detail::RuntimeStringTextPart{.status = retained.Status()};
634 }
635 else if constexpr (std::is_array_v<Bare> &&
636 std::is_same_v<std::remove_cv_t<std::remove_extent_t<Bare>>, char>)
637 {
638 return {.view =
639 std::string_view(text, BoundedTextLength(text, std::extent_v<Bare>))};
640 }
641 else if constexpr (std::is_same_v<Bare, std::string_view>)
642 {
643 return {.view = text};
644 }
645 else if constexpr (std::is_same_v<Bare, std::string>)
646 {
647 return {.view = std::string_view(text.data(), text.size())};
648 }
649 else if constexpr (std::is_same_v<Bare, std::nullptr_t>)
650 {
651 return {.status = ErrorCode::PTR_NULL};
652 }
653 else if constexpr (std::is_same_v<Bare, char*> || std::is_same_v<Bare, const char*>)
654 {
655 return text == nullptr
656 ? Detail::RuntimeStringTextPart{.status = ErrorCode::PTR_NULL}
657 : Detail::RuntimeStringTextPart{
658 .view = std::string_view(text, std::strlen(text))};
659 }
660 else
661 {
662 static_assert(Detail::runtime_string_always_false<T>,
663 "RuntimeStringView constructor only accepts text-like parts. "
664 "Use Reformat() or Reprintf() for numeric formatting.");
665 return {.status = ErrorCode::ARG_ERR};
666 }
667 }
668
671 char* data_ = nullptr;
674 size_t size_ = 0;
677 size_t capacity_ = 0;
679 ErrorCode status_ = ErrorCode::OK;
680};
681
688
691template <typename First, typename Second, typename... Rest>
692RuntimeStringView(First&&, Second&&, Rest&&...) -> RuntimeStringView<>;
693
694} // namespace LibXR
static consteval bool Matches()
判断 Args... 是否与启用的转换项精确匹配 / Return whether Args... exactly match the enabled conversions
Definition printf.hpp:138
运行期构造、长期保留的 NUL 结尾字符串视图。 Runtime-built retained NUL-terminated string view.
constexpr RuntimeStringView()=default
构造一个空的有效视图 / Constructs an empty valid view.
constexpr ErrorCode Status() const
返回最近一次操作状态 / Returns the latest operation status.
RuntimeStringView(First &&first, Second &&second, Rest &&... rest)
拼接多个文本片段到保留存储 / Concatenates text parts into retained storage.
RuntimeStringView(const char(&text)[N])
按文本语义拷贝一个有界只读字符数组 / Copies one bounded const char array as text.
ErrorCode Reformat(CallArgs &&... args)
使用绑定的 brace-style 格式重写当前内容。 Rewrite current content with the bound brace-style format.
RuntimeStringView(std::nullptr_t text)
constexpr size_t Size() const
返回不含结尾 NUL 的文本长度 / Returns the text size excluding the trailing NUL.
const char * CStr() const
返回 NUL 结尾 C 字符串 / Returns a NUL-terminated C string.
RuntimeStringView(CharPtr text)
RuntimeStringView(RuntimeStringView &&other) noexcept
移动构造并接管保留存储句柄 / Move-constructs by taking the retained storage handle.
ErrorCode Reprintf(CallArgs &&... args)
使用绑定的 printf-style 格式重写当前内容。 Rewrite current content with the bound printf-style format.
constexpr operator std::string_view() const
转换为只读字符串视图 / Converts to a read-only string view.
RuntimeStringView(std::string_view text)
拷贝文本到保留存储 / Copies text into retained storage.
constexpr std::string_view View() const
返回当前只读视图 / Returns the current read-only view.
RuntimeStringView(char(&text)[N])
按文本语义拷贝一个有界可变字符数组 / Copies one bounded mutable char array as text.
constexpr bool Empty() const
返回当前可见文本是否为空 / Returns whether the current visible text is empty.
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ OUT_OF_RANGE
超出范围 / Out of range
@ PTR_NULL
空指针 / Null pointer
@ OK
操作成功 / Operation successful
@ ARG_ERR
参数错误 / Argument error
RuntimeStringView(std::string_view) -> RuntimeStringView<>
格式化保留字符串参数的编译期类型特征。 Compile-time traits for formatted retained-string arguments.
检查刷新调用是否精确使用 RuntimeStringView<Source, Args...> 绑定的参数类型。 Checks that a rewrite call uses exactly the ...
格式化第二遍使用的保留存储写入端。 Sink used by the second formatting pass to fill retained storage.
为容量探测把枚举参数归一化到底层整数类型。 Normalizes enum arguments to their underlying type for capacity probes.
拷贝/拼接路径使用的归一化文本片段。 Normalized text part used by the copy/concatenation path.
仅用于精确比较参数类型列表的标签类型。 Type-list tag used only for exact argument-list comparison.
作为 printf 格式源的结构化字符串字面量包装 / Structural literal wrapper used as the NTTP source for printf formats
Definition printf.hpp:17
constexpr size_t Size() const
返回不含结尾零字节的格式串长度 / Return the format length without the terminating zero byte
Definition printf.hpp:43