libxr  1.0
Want to be the best embedded framework
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 <new>
8#include <string>
9#include <string_view>
10#include <type_traits>
11#include <utility>
12
13#include "libxr_def.hpp"
14#include "print.hpp"
15
16namespace LibXR
17{
18template <Print::Text Source, typename... Args>
20
21namespace Detail
22{
23
28template <typename... Values>
30{
31};
32
37template <typename T, bool IsEnum = std::is_enum_v<T>>
39{
40 using Type = T;
41};
42
43template <typename T>
45{
46 using Type = std::underlying_type_t<T>;
47};
48
54template <typename T>
55static constexpr bool runtime_string_always_false = false;
56
57inline constexpr size_t runtime_string_unbounded_capacity =
58 std::numeric_limits<size_t>::max();
59inline constexpr size_t runtime_string_max_field_width =
60 std::numeric_limits<uint8_t>::max();
61
75[[nodiscard]] constexpr size_t RuntimeStringFieldCapacity(Print::FormatPackKind pack)
76{
77 constexpr size_t width = runtime_string_max_field_width;
78 constexpr size_t precision = runtime_string_max_field_width;
79
80 switch (pack)
81 {
82 case Print::FormatPackKind::I32:
83 return 1U + precision;
84 case Print::FormatPackKind::I64:
85 return 1U + precision;
86 case Print::FormatPackKind::U32:
87 return 2U + precision;
88 case Print::FormatPackKind::U64:
89 return 2U + precision;
90 case Print::FormatPackKind::Pointer:
91 return 2U + (precision > 2U * sizeof(uintptr_t) ? precision
92 : 2U * sizeof(uintptr_t));
93 case Print::FormatPackKind::Character:
94 return width;
95 case Print::FormatPackKind::StringView:
96 return runtime_string_unbounded_capacity;
97 case Print::FormatPackKind::F32:
98 return 1U + static_cast<size_t>(std::numeric_limits<float>::max_exponent10) +
99 2U + 1U + precision;
100 case Print::FormatPackKind::F64:
101 return 1U + static_cast<size_t>(std::numeric_limits<double>::max_exponent10) +
102 2U + 1U + precision;
103 case Print::FormatPackKind::LongDouble:
104 return 1U +
105 static_cast<size_t>(std::numeric_limits<long double>::max_exponent10) +
106 2U + 1U + precision;
107 }
108
109 return runtime_string_unbounded_capacity;
110}
111
112[[nodiscard]] constexpr bool RuntimeStringAddCapacity(size_t& total, size_t value)
113{
114 if (value == runtime_string_unbounded_capacity ||
115 runtime_string_unbounded_capacity - total < value)
116 {
117 return false;
118 }
119 total += value;
120 return true;
121}
122
123template <typename Built>
124[[nodiscard]] consteval size_t RuntimeStringMaxFormattedSize(size_t source_size)
125{
126 size_t total = source_size;
127 for (const auto& argument : Built::ArgumentList())
128 {
129 if (!RuntimeStringAddCapacity(total, RuntimeStringFieldCapacity(argument.pack)))
130 {
131 return runtime_string_unbounded_capacity;
132 }
133 }
134 return total;
135}
136
149template <typename T>
151{
152 using Decayed = std::remove_cvref_t<T>;
153 using Normalized = typename RuntimeStringNormalized<Decayed>::Type;
154
155 static constexpr bool is_text =
156 std::is_array_v<Decayed> || std::is_same_v<Decayed, char*> ||
157 std::is_same_v<Decayed, const char*> || std::is_same_v<Decayed, std::string> ||
158 std::is_same_v<Decayed, std::string_view>;
159
160 static constexpr bool is_pointer =
161 std::is_pointer_v<Decayed> &&
162 !std::is_function_v<std::remove_pointer_t<Decayed>>;
163
164 static constexpr bool has_static_capacity =
165 !is_text && (std::is_same_v<Decayed, std::nullptr_t> || is_pointer ||
166 std::is_integral_v<Normalized> ||
167 std::is_floating_point_v<Normalized>);
168};
169
176template <typename... Args>
178{
179 template <typename... CallArgs>
180 static constexpr bool matches =
181 std::is_same_v<RuntimeStringTypeList<
182 typename RuntimeStringArgumentTraits<CallArgs>::Decayed...>,
183 RuntimeStringTypeList<Args...>>;
184};
185
191{
192 std::string_view view;
193 ErrorCode status = ErrorCode::OK;
194};
195
196template <typename T>
197struct RuntimeStringViewTag : std::false_type
198{
199};
200
201template <Print::Text Source, typename... Args>
202struct RuntimeStringViewTag<RuntimeStringView<Source, Args...>> : std::true_type
203{
204};
205
206template <typename T>
207inline constexpr bool runtime_string_is_view_v =
209
215{
216 char* data = nullptr;
217 size_t capacity = 0;
218 size_t size = 0;
219
220 [[nodiscard]] ErrorCode Write(std::string_view chunk)
221 {
222 if (capacity - size < chunk.size())
223 {
225 }
226 if (!chunk.empty())
227 {
228 std::memcpy(data + size, chunk.data(), chunk.size());
229 size += chunk.size();
230 }
231 return ErrorCode::OK;
232 }
233};
234
235} // namespace Detail
236
253template <Print::Text Source = "", typename... Args>
255{
256 public:
257 static_assert((std::is_same_v<
258 Args, typename Detail::RuntimeStringArgumentTraits<Args>::Decayed> &&
259 ...),
260 "LibXR::RuntimeStringView argument types must be unqualified "
261 "value types");
263 "LibXR::RuntimeStringView formatted arguments cannot contain "
264 "runtime strings; use RuntimeStringView<> for text concatenation");
265
267 constexpr RuntimeStringView() = default;
268
269 RuntimeStringView(const RuntimeStringView&) = delete;
270 RuntimeStringView& operator=(const RuntimeStringView&) = delete;
271
274 : data_(other.data_),
275 size_(other.size_),
276 capacity_(other.capacity_),
277 status_(other.status_)
278 {
279 other.data_ = nullptr;
280 other.size_ = 0;
281 other.capacity_ = 0;
282 other.status_ = ErrorCode::OK;
283 }
284
285 RuntimeStringView& operator=(RuntimeStringView&&) = delete;
286
288 explicit RuntimeStringView(std::string_view text)
289 requires(Source.Size() == 0 && sizeof...(Args) == 0)
290 {
291 static_cast<void>(AssignCopy(text));
292 }
293
295 template <size_t N>
296 explicit RuntimeStringView(char (&text)[N])
297 requires(Source.Size() == 0 && sizeof...(Args) == 0)
298 {
299 static_cast<void>(AssignCopy(std::string_view(text, BoundedTextLength(text, N))));
300 }
301
303 template <size_t N>
304 explicit RuntimeStringView(const char (&text)[N])
305 requires(Source.Size() == 0 && sizeof...(Args) == 0)
306 {
307 static_cast<void>(AssignCopy(std::string_view(text, BoundedTextLength(text, N))));
308 }
309
311 template <typename CharPtr>
312 requires(Source.Size() == 0 && sizeof...(Args) == 0 &&
313 (std::is_same_v<CharPtr, char*> || std::is_same_v<CharPtr, const char*>))
314 explicit RuntimeStringView(CharPtr text)
315 {
316 static_cast<void>(AssignCopy(text));
317 }
318
320 explicit RuntimeStringView(std::nullptr_t text)
321 requires(Source.Size() == 0 && sizeof...(Args) == 0)
322 {
323 static_cast<void>(AssignCopy(static_cast<const char*>(text)));
324 }
325
327 template <typename First, typename Second, typename... Rest>
328 explicit RuntimeStringView(First&& first, Second&& second, Rest&&... rest)
329 requires(Source.Size() == 0 && sizeof...(Args) == 0)
330 {
331 static_cast<void>(AssignConcat(std::forward<First>(first),
332 std::forward<Second>(second),
333 std::forward<Rest>(rest)...));
334 }
335
340 template <typename... CallArgs>
341 [[nodiscard]] ErrorCode Reformat(CallArgs&&... args)
342 requires((Source.Size() != 0 || sizeof...(Args) != 0) &&
344 {
345 static_assert(LibXR::Format<Source>::template Matches<Args...>(),
346 "LibXR::RuntimeStringView format argument types do not match "
347 "the format");
348 using Built = typename LibXR::Format<Source>::template Compiled<Args...>;
349 static constexpr size_t max_size =
350 Detail::RuntimeStringMaxFormattedSize<Built>(Source.Size());
351 static_assert(max_size != Detail::runtime_string_unbounded_capacity,
352 "LibXR::RuntimeStringView cannot retain unbounded formatted "
353 "runtime strings");
354
355 return AssignFormatted(max_size, [&](auto& sink) {
356 return Print::FormatTo<Source>(sink, std::forward<CallArgs>(args)...);
357 });
358 }
359
364 template <typename... CallArgs>
365 [[nodiscard]] ErrorCode Reprintf(CallArgs&&... args)
366 requires((Source.Size() != 0 || sizeof...(Args) != 0) &&
368 {
369 static_assert(Print::Printf::Matches<Source, Args...>(),
370 "LibXR::RuntimeStringView printf argument types do not match "
371 "the format");
373 static constexpr size_t max_size =
374 Detail::RuntimeStringMaxFormattedSize<Built>(Source.Size());
375 static_assert(max_size != Detail::runtime_string_unbounded_capacity,
376 "LibXR::RuntimeStringView cannot retain unbounded formatted "
377 "runtime strings");
378
379 return AssignFormatted(max_size, [&](auto& sink) {
380 return Print::PrintfTo<Source>(sink, std::forward<CallArgs>(args)...);
381 });
382 }
383
385 [[nodiscard]] constexpr std::string_view View() const
386 {
387 return data_ == nullptr ? std::string_view{} : std::string_view(data_, size_);
388 }
389
391 [[nodiscard]] const char* CStr() const { return data_ == nullptr ? "" : data_; }
393 [[nodiscard]] constexpr size_t Size() const { return size_; }
395 [[nodiscard]] constexpr bool Empty() const { return size_ == 0; }
397 [[nodiscard]] constexpr ErrorCode Status() const { return status_; }
399 [[nodiscard]] constexpr operator std::string_view() const { return View(); }
401 [[nodiscard]] operator const char*() const { return CStr(); }
402
403 private:
409 [[nodiscard]] ErrorCode SetFailure(ErrorCode status)
410 {
411 if (data_ != nullptr)
412 {
413 data_[0] = '\0';
414 }
415 size_ = 0;
416 status_ = status;
417 return status_;
418 }
419
437 [[nodiscard]] ErrorCode EnsureCapacity(size_t payload_size)
438 {
439 if (payload_size == std::numeric_limits<size_t>::max())
440 {
442 }
443 if (payload_size <= capacity_ && data_ != nullptr)
444 {
445 return ErrorCode::OK;
446 }
447 if (data_ != nullptr)
448 {
450 }
451 if (payload_size == 0)
452 {
453 return ErrorCode::OK;
454 }
455
456 data_ = new (std::nothrow) char[payload_size + 1U];
457 if (data_ == nullptr)
458 {
459 ASSERT(data_ != nullptr);
460 return ErrorCode::NO_MEM;
461 }
462 capacity_ = payload_size;
463 return ErrorCode::OK;
464 }
465
470 [[nodiscard]] ErrorCode AssignCopy(std::string_view text)
471 {
472 ErrorCode status = EnsureCapacity(text.size());
473 if (status != ErrorCode::OK)
474 {
475 return SetFailure(status);
476 }
477 if (!text.empty())
478 {
479 std::memcpy(data_, text.data(), text.size());
480 }
481 if (data_ != nullptr)
482 {
483 data_[text.size()] = '\0';
484 }
485 size_ = text.size();
487 return status_;
488 }
489
495 [[nodiscard]] ErrorCode AssignCopy(const char* text)
496 {
497 return text == nullptr ? SetFailure(ErrorCode::PTR_NULL)
498 : AssignCopy(std::string_view(text, std::strlen(text)));
499 }
500
510 template <typename... Parts>
511 [[nodiscard]] ErrorCode AssignConcat(Parts&&... parts)
512 {
513 size_t total_size = 0;
514 ErrorCode status = ErrorCode::OK;
515
516 auto count = [&](auto&& part)
517 {
518 if (status != ErrorCode::OK)
519 {
520 return;
521 }
522
524 NormalizeConcatPart(std::forward<decltype(part)>(part));
525 if (text.status != ErrorCode::OK)
526 {
527 status = text.status;
528 }
529 else if (std::numeric_limits<size_t>::max() - total_size < text.view.size())
530 {
532 }
533 else
534 {
535 total_size += text.view.size();
536 }
537 };
538 (count(std::forward<Parts>(parts)), ...);
539
540 if (status != ErrorCode::OK)
541 {
542 return SetFailure(status);
543 }
544 status = EnsureCapacity(total_size);
545 if (status != ErrorCode::OK)
546 {
547 return SetFailure(status);
548 }
549
550 size_t offset = 0;
551 auto copy = [&](auto&& part)
552 {
554 NormalizeConcatPart(std::forward<decltype(part)>(part));
555 if (!text.view.empty())
556 {
557 std::memcpy(data_ + offset, text.view.data(), text.view.size());
558 offset += text.view.size();
559 }
560 };
561 (copy(std::forward<Parts>(parts)), ...);
562
563 if (data_ != nullptr)
564 {
565 data_[total_size] = '\0';
566 }
567 size_ = total_size;
569 return status_;
570 }
571
582 template <typename WriteFn>
583 [[nodiscard]] ErrorCode AssignFormatted(size_t max_size, WriteFn&& write)
584 {
585 if (data_ == nullptr)
586 {
587 ErrorCode status = EnsureCapacity(max_size);
588 if (status != ErrorCode::OK)
589 {
590 return SetFailure(status);
591 }
592 }
593
594 Detail::RuntimeStringBufferSink sink{.data = data_, .capacity = capacity_};
595 ErrorCode status = write(sink);
596 if (status != ErrorCode::OK)
597 {
598 return SetFailure(status);
599 }
600 if (data_ != nullptr)
601 {
602 data_[sink.size] = '\0';
603 }
604 size_ = sink.size;
606 return status_;
607 }
608
614 [[nodiscard]] static constexpr size_t BoundedTextLength(const char* text,
615 size_t bound) noexcept
616 {
617 size_t size = 0;
618 while (size < bound && text[size] != '\0')
619 {
620 ++size;
621 }
622 return size;
623 }
624
632 template <typename T>
633 [[nodiscard]] static constexpr Detail::RuntimeStringTextPart NormalizeConcatPart(T&& text)
634 {
635 using Bare = std::remove_cvref_t<T>;
636 if constexpr (Detail::runtime_string_is_view_v<T>)
637 {
638 const auto& retained = text;
639 return retained.Status() == ErrorCode::OK
640 ? Detail::RuntimeStringTextPart{.view = retained.View()}
641 : Detail::RuntimeStringTextPart{.status = retained.Status()};
642 }
643 else if constexpr (std::is_array_v<Bare> &&
644 std::is_same_v<std::remove_cv_t<std::remove_extent_t<Bare>>, char>)
645 {
646 return {.view = std::string_view(text, BoundedTextLength(text, std::extent_v<Bare>))};
647 }
648 else if constexpr (std::is_same_v<Bare, std::string_view>)
649 {
650 return {.view = text};
651 }
652 else if constexpr (std::is_same_v<Bare, std::string>)
653 {
654 return {.view = std::string_view(text.data(), text.size())};
655 }
656 else if constexpr (std::is_same_v<Bare, std::nullptr_t>)
657 {
658 return {.status = ErrorCode::PTR_NULL};
659 }
660 else if constexpr (std::is_same_v<Bare, char*> || std::is_same_v<Bare, const char*>)
661 {
662 return text == nullptr
665 .view = std::string_view(text, std::strlen(text))};
666 }
667 else
668 {
669 static_assert(Detail::runtime_string_always_false<T>,
670 "RuntimeStringView constructor only accepts text-like parts. "
671 "Use Reformat() or Reprintf() for numeric formatting.");
672 return {.status = ErrorCode::ARG_ERR};
673 }
674 }
675
677 char* data_ = nullptr;
679 size_t size_ = 0;
681 size_t capacity_ = 0;
684};
685
690
692template <typename First, typename Second, typename... Rest>
693RuntimeStringView(First&&, Second&&, Rest&&...) -> RuntimeStringView<>;
694
695} // namespace LibXR
static consteval bool Matches()
判断 Args... 是否与启用的转换项精确匹配 / Return whether Args... exactly match the enabled conversions
Definition printf.hpp:113
运行期构造、长期保留的 NUL 结尾字符串视图。
constexpr RuntimeStringView()=default
Constructs an empty valid view. / 构造一个空的有效视图。
size_t capacity_
不含结尾 NUL 的已探测/分配容量。 / Probed/allocated payload capacity excluding the trailing NUL.
constexpr ErrorCode Status() const
Returns the latest operation status. / 返回最近一次操作状态。
ErrorCode status_
最近一次构造或重写状态。 / Status of the latest construction or rewrite.
ErrorCode SetFailure(ErrorCode status)
记录最近一次失败,并在已有存储上保留一个有效的空 C 字符串。
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 AssignCopy(const char *text)
C 字符串入口负责空指针检查,再进入统一的文本拷贝路径。
ErrorCode Reformat(CallArgs &&... args)
使用绑定的 brace-style 格式重写当前内容。
ErrorCode AssignFormatted(size_t max_size, WriteFn &&write)
格式化重写入口;首次调用按编译期上界分配,后续调用只覆盖已有存储。
RuntimeStringView(std::nullptr_t text)
Preserves the old bare-nullptr entry for runtime null checks. / 保留裸 nullptr 入口以维持运行期空指针检查语义。
constexpr size_t Size() const
Returns the text size excluding the trailing NUL. / 返回不含结尾 NUL 的文本长度。
static constexpr size_t BoundedTextLength(const char *text, size_t bound) noexcept
在已知边界内查找文本长度;遇到首个 \0 截断,否则使用整个数组长度。
ErrorCode AssignConcat(Parts &&... parts)
拼接构造的两遍流程:先校验并统计所有片段,再一次性写入。
const char * CStr() const
Returns a NUL-terminated C string. / 返回 NUL 结尾 C 字符串。
RuntimeStringView(CharPtr text)
Copies a NUL-terminated C-string pointer into retained storage. / 拷贝 NUL 结尾 C 字符串指针到保留存储。
RuntimeStringView(RuntimeStringView &&other) noexcept
Move-constructs by taking the retained storage handle. / 移动构造并接管保留存储句柄。
ErrorCode AssignCopy(std::string_view text)
构造期文本拷贝入口;空字符串保持零分配。
size_t size_
不含结尾 NUL 的当前可见文本长度。 / Current visible payload size excluding the trailing NUL.
char * data_
长期保留的 NUL 结尾存储;对象析构时不释放。 / Retained NUL-terminated storage; not released by the destructor.
ErrorCode Reprintf(CallArgs &&... args)
使用绑定的 printf-style 格式重写当前内容。
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. / 按文本语义拷贝一个有界可变字符数组。
ErrorCode EnsureCapacity(size_t payload_size)
为首个非空结果分配保留存储,已有存储不会再次扩容。
static constexpr Detail::RuntimeStringTextPart NormalizeConcatPart(T &&text)
把拼接构造支持的输入统一归一化为只读文本片段;普通拼接只接受文本类输入, 数值格式化必须显式走 Reformat() 或 Reprintf()。
constexpr bool Empty() const
Returns whether the current visible text is empty. / 返回当前可见文本是否为空。
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ OUT_OF_RANGE
超出范围 | Out of range
@ NO_MEM
内存不足 | Insufficient memory
@ PTR_NULL
空指针 | Null pointer
@ OK
操作成功 | Operation successful
@ ARG_ERR
参数错误 | Argument error
RuntimeStringView(std::string_view) -> RuntimeStringView<>
单参数文本构造推导为普通保留字符串。 / Single text argument deduces a plain retained string.
Compile-time traits for formatted retained-string arguments.
Checks that a rewrite call uses exactly the argument types bound in RuntimeStringView<Source,...
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:16
constexpr size_t Size() const
返回不含结尾零字节的格式串长度 / Return the format length without the terminating zero byte
Definition printf.hpp:38