libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
format_compile.hpp
1#pragma once
2
3#include <array>
4#include <bit>
5#include <concepts>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9
10#include "format_protocol.hpp"
11#include "print_contract.hpp"
12
13namespace LibXR::Print
14{
50template <typename Frontend>
52{
53 private:
54 static_assert(SourceFrontend<Frontend>,
55 "LibXR::Print::FormatCompiler: frontend must expose ErrorType, "
56 "SourceData(), and SourceSize()");
57
58 using Error = typename Frontend::ErrorType;
59
66 template <size_t BlobBytes, size_t ArgCount>
67 struct ResultData
68 {
69 using CodeList =
70 std::array<uint8_t, BlobBytes>;
71 using ArgumentListData =
72 std::array<FormatArgumentInfo,
73 ArgCount>;
75
76 CodeList codes{};
77 ArgumentListData
78 arg_info{};
79 FormatProfile profile =
80 FormatProfile::None;
81 Error compile_error =
82 Error::None;
83 };
84
85 template <size_t BlobBytes, size_t ArgCount>
86 using Result = ResultData<BlobBytes, ArgCount>;
87
92 static constexpr size_t inline_text_limit = 2 * sizeof(size_t) - 1;
93
94 // 基于原始源串给单遍构建器预留的保守上界:
95 // - 普通文本最坏可膨胀为每字节 3 个码流字节(TextInline)
96 // - 值记录最坏约为每字节 2 个码流字节(如 %x -> 4 字节)
97 // - 因此 3 * SourceSize() + 1 足以覆盖临时记录流与最终结束标记。
98 // Conservative single-pass scratch bounds derived from the original source:
99 // - literal text can cost up to 3 code bytes per source byte (TextInline)
100 // - value records cost at most 2 code bytes per source byte (%x -> 4 bytes)
101 // - therefore 3 * SourceSize() + 1 safely covers the temporary record stream
102 // plus the final End marker.
103 static constexpr size_t max_code_bytes = 3 * Frontend::SourceSize() + 1;
104 static constexpr size_t max_text_pool_bytes = Frontend::SourceSize();
105 static constexpr size_t max_arg_count = Frontend::SourceSize();
106 static constexpr uint8_t unspecified_precision = std::numeric_limits<uint8_t>::max();
107
115 static consteval void EmitByte(auto& data, size_t& out, uint8_t value)
116 {
117 data[out++] = value;
118 }
119
129 template <typename T>
130 static consteval void EmitNative(auto& data, size_t& out, T value)
131 {
132 auto bytes = std::bit_cast<std::array<uint8_t, sizeof(T)>>(value);
133 for (auto byte : bytes)
134 {
135 data[out++] = byte;
136 }
137 }
138
148 template <typename T>
149 [[nodiscard]] static consteval T ReadNative(const auto& data, size_t& pos)
150 {
151 std::array<uint8_t, sizeof(T)> bytes{};
152 for (size_t i = 0; i < sizeof(T); ++i)
153 {
154 bytes[i] = data[pos++];
155 }
156 return std::bit_cast<T>(bytes);
157 }
158
165 [[nodiscard]] static consteval auto Failed(Error error)
166 {
167 Result<1, 0> result{};
168 result.compile_error = error;
169 return result;
170 }
171
179 [[nodiscard]] static consteval FormatProfile ProfileForOp(FormatOp op)
180 {
181 switch (op)
182 {
183 case FormatOp::U32Dec:
184 case FormatOp::U32ZeroPadWidth:
185 case FormatOp::I32Dec:
186 case FormatOp::U32Binary:
187 case FormatOp::U32Octal:
188 case FormatOp::U32HexLower:
189 case FormatOp::U32HexUpper:
190 return FormatProfile::NarrowInt;
191 case FormatOp::StringRaw:
192 case FormatOp::CharacterRaw:
193 return FormatProfile::TextArg;
194 case FormatOp::GenericField:
195 return FormatProfile::None;
196 case FormatOp::TextInline:
197 case FormatOp::TextRef:
198 case FormatOp::TextSpace:
199 case FormatOp::End:
200 return FormatProfile::None;
201 }
202
203 return FormatProfile::None;
204 }
205
210 [[nodiscard]] static consteval FormatOp FastFieldOp(const FormatField& field)
211 {
212 const bool raw_integer_field = IsRawIntegerField(field);
213 const bool raw_text_field = IsRawTextField(field);
214
215 if (raw_integer_field && field.type == FormatType::Signed32 &&
216 field.pack == FormatPackKind::I32)
217 {
218 return FormatOp::I32Dec;
219 }
220
221 if (field.type == FormatType::Unsigned32 && field.pack == FormatPackKind::U32 &&
222 raw_integer_field)
223 {
224 return FormatOp::U32Dec;
225 }
226
227 if (raw_integer_field && field.type == FormatType::Binary32 &&
228 field.pack == FormatPackKind::U32)
229 {
230 return FormatOp::U32Binary;
231 }
232
233 if (raw_integer_field && field.type == FormatType::Octal32 &&
234 field.pack == FormatPackKind::U32)
235 {
236 return FormatOp::U32Octal;
237 }
238
239 if (raw_integer_field && field.type == FormatType::HexLower32 &&
240 field.pack == FormatPackKind::U32)
241 {
242 return FormatOp::U32HexLower;
243 }
244
245 if (raw_integer_field && field.type == FormatType::HexUpper32 &&
246 field.pack == FormatPackKind::U32)
247 {
248 return FormatOp::U32HexUpper;
249 }
250
251 if (raw_text_field && field.type == FormatType::Character &&
252 field.pack == FormatPackKind::Character)
253 {
254 return FormatOp::CharacterRaw;
255 }
256
257 if (raw_text_field && field.type == FormatType::String &&
258 field.pack == FormatPackKind::StringView)
259 {
260 return FormatOp::StringRaw;
261 }
262
263 if (field.type == FormatType::Unsigned32 && field.pack == FormatPackKind::U32 &&
264 field.flags == static_cast<uint8_t>(FormatFlag::ZeroPad) && field.fill == ' ' &&
265 field.width != 0 && field.precision == unspecified_precision)
266 {
267 return FormatOp::U32ZeroPadWidth;
268 }
269
270 return FormatOp::GenericField;
271 }
272
277 [[nodiscard]] static consteval bool IsRawIntegerField(const FormatField& field)
278 {
279 return field.flags == 0 && field.fill == ' ' && field.width == 0 &&
280 field.precision == unspecified_precision;
281 }
282
287 [[nodiscard]] static consteval bool IsRawTextField(const FormatField& field)
288 {
289 return (field.flags == 0 ||
290 field.flags == static_cast<uint8_t>(FormatFlag::LeftAlign)) &&
291 field.fill == ' ' && field.width == 0 &&
292 field.precision == unspecified_precision;
293 }
294
299 struct ScratchBuilder
300 {
301 std::array<uint8_t, max_code_bytes>
302 code_scratch{};
304 std::array<uint8_t, max_text_pool_bytes>
305 text_scratch{};
306 std::array<FormatArgumentInfo, max_arg_count>
307 arg_scratch{};
308
309 size_t code_bytes =
310 0;
311 size_t text_pool_bytes = 0;
312 size_t arg_count = 0;
313 FormatProfile profile = FormatProfile::None;
315 Error frontend_error = Error::None;
316
327 [[nodiscard]] consteval Error Text(size_t offset, size_t text_size)
328 {
329 if (text_size == 0)
330 {
331 return Error::None;
332 }
333
334 if (text_size == 1 && Frontend::SourceData()[offset] == ' ')
335 {
336 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextSpace));
337 return Error::None;
338 }
339
340 if (text_size <= inline_text_limit)
341 {
342 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextInline));
343 for (size_t i = 0; i < text_size; ++i)
344 {
345 EmitByte(code_scratch, code_bytes,
346 static_cast<uint8_t>(Frontend::SourceData()[offset + i]));
347 }
348 EmitByte(code_scratch, code_bytes, 0);
349 return Error::None;
350 }
351
352 if (text_pool_bytes > std::numeric_limits<uint16_t>::max())
353 {
354 return Error::TextOffsetOverflow;
355 }
356 if (text_size > std::numeric_limits<uint16_t>::max())
357 {
358 return Error::TextSizeOverflow;
359 }
360
361 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextRef));
362 EmitNative(code_scratch, code_bytes, static_cast<uint16_t>(text_pool_bytes));
363 EmitNative(code_scratch, code_bytes, static_cast<uint16_t>(text_size));
364
365 for (size_t i = 0; i < text_size; ++i)
366 {
367 text_scratch[text_pool_bytes++] =
368 static_cast<uint8_t>(Frontend::SourceData()[offset + i]);
369 }
370
371 return Error::None;
372 }
373
380 [[nodiscard]] consteval Error Field(const FormatField& field)
381 {
382 auto op = FastFieldOp(field);
383 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(op));
384
385 switch (op)
386 {
387 case FormatOp::U32Dec:
388 case FormatOp::I32Dec:
389 case FormatOp::U32Binary:
390 case FormatOp::U32Octal:
391 case FormatOp::U32HexLower:
392 case FormatOp::U32HexUpper:
393 case FormatOp::StringRaw:
394 case FormatOp::CharacterRaw:
395 break;
396 case FormatOp::U32ZeroPadWidth:
397 EmitByte(code_scratch, code_bytes, field.width);
398 break;
399 case FormatOp::GenericField:
400 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.type));
401 EmitByte(code_scratch, code_bytes, field.flags);
402 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.fill));
403 EmitByte(code_scratch, code_bytes, field.width);
404 EmitByte(code_scratch, code_bytes, field.precision);
405 break;
406 case FormatOp::TextInline:
407 case FormatOp::TextRef:
408 case FormatOp::TextSpace:
409 case FormatOp::End:
410 break;
411 }
412
413 profile |=
414 op == FormatOp::GenericField ? GenericProfileFor(field.type) : ProfileForOp(op);
415
416 arg_scratch[arg_count++] = FormatArgumentInfo{
417 .pack = field.pack,
418 .rule = field.rule,
419 };
420 return Error::None;
421 }
422
428 [[nodiscard]] constexpr size_t FinalCodeBytes() const { return code_bytes + 1; }
434 [[nodiscard]] constexpr size_t FinalBlobBytes() const
435 {
436 return FinalCodeBytes() + text_pool_bytes;
437 }
438
454 template <size_t CodeBytes, size_t BlobBytes, size_t ArgCount>
455 [[nodiscard]] consteval auto Finish() const
456 {
457 Result<BlobBytes, ArgCount> result{};
458 result.profile = profile;
459 size_t scratch_in = 0;
460 size_t code_out = 0;
461
462 while (scratch_in < code_bytes)
463 {
464 auto op = static_cast<FormatOp>(code_scratch[scratch_in++]);
465 EmitByte(result.codes, code_out, static_cast<uint8_t>(op));
466
467 if (op == FormatOp::TextInline)
468 {
469 while (true)
470 {
471 uint8_t byte = code_scratch[scratch_in++];
472 EmitByte(result.codes, code_out, byte);
473 if (byte == 0)
474 {
475 break;
476 }
477 }
478 continue;
479 }
480
481 const size_t payload_bytes = FormatOpPayloadBytes(op);
482 if (payload_bytes == 0)
483 {
484 continue;
485 }
486
487 if (op == FormatOp::TextRef)
488 {
489 auto relative_offset = ReadNative<uint16_t>(code_scratch, scratch_in);
490 auto text_size = ReadNative<uint16_t>(code_scratch, scratch_in);
491 size_t absolute_offset = CodeBytes + relative_offset;
492 if (absolute_offset > std::numeric_limits<uint16_t>::max())
493 {
494 result.compile_error = Error::TextOffsetOverflow;
495 return result;
496 }
497 EmitNative(result.codes, code_out, static_cast<uint16_t>(absolute_offset));
498 EmitNative(result.codes, code_out, text_size);
499 continue;
500 }
501
502 if (payload_bytes == 1)
503 {
504 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
505 continue;
506 }
507
508 if (op == FormatOp::GenericField)
509 {
510 for (size_t i = 0; i < payload_bytes; ++i)
511 {
512 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
513 }
514 }
515 }
516
517 EmitByte(result.codes, code_out, static_cast<uint8_t>(FormatOp::End));
518
519 for (size_t i = 0; i < text_pool_bytes; ++i)
520 {
521 result.codes[CodeBytes + i] = text_scratch[i];
522 }
523 for (size_t i = 0; i < ArgCount; ++i)
524 {
525 result.arg_info[i] = arg_scratch[i];
526 }
527
528 return result;
529 }
530 };
531
533 "LibXR::Print::FormatCompiler: frontend Walk(visitor) must accept "
534 "the shared builder and return ErrorType");
535
536 public:
544 [[nodiscard]] static consteval auto Compile()
545 {
546 constexpr auto scratch = []() consteval
547 {
548 ScratchBuilder builder{};
549 builder.frontend_error = Frontend::Walk(builder);
550 return builder;
551 }();
552
553 if constexpr (scratch.frontend_error != Error::None)
554 {
555 return Failed(scratch.frontend_error);
556 }
557 else
558 {
559 return scratch.template Finish<scratch.FinalCodeBytes(), scratch.FinalBlobBytes(),
560 scratch.arg_count>();
561 }
562 }
563};
564} // namespace LibXR::Print
共享编译期后端,把前端事件整理成最终字节流和参数表 / Shared compile-time backend that turns frontend events into the final byt...
static consteval auto Compile()
把一个前端编译成最终字节流、参数表和 writer 摘要 / Compiles one frontend into the final byte stream, argument table,...
可由共享后端读取源码信息的格式前端 / Format frontend exposing source metadata to the shared backend
可向指定 visitor 发射格式事件的前端 / Frontend that can emit format events into the supplied visitor
Writer 消费的编译格式运行期协议 / Compiled-format runtime contract consumed by Writer.
一条已经决定完毕、运行期 writer 知道如何打印的值字段 / One fully-decided value field that the runtime writer knows how to p...
uint8_t flags
字段修饰位集合 / FormatFlag bitset
FormatArgumentRule rule
编译期实参匹配规则 / compile-time argument rule
FormatType type
语义写出类别 / semantic render category
FormatPackKind pack
运行期打包存储类别 / packed runtime storage kind
char fill
字段填充字符 / field fill character
uint8_t precision
已解析精度,或未指定 / parsed precision, or unspecified
uint8_t width
已解析的字段宽度 / parsed field width
作为 printf 格式源的结构化字符串字面量包装 / Structural literal wrapper used as the NTTP source for printf formats
Definition printf.hpp:17