libxr  1.0
Want to be the best embedded framework
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>
68 {
69 using CodeList =
70 std::array<uint8_t, BlobBytes>;
72 std::array<FormatArgumentInfo,
73 ArgCount>;
75
79 FormatProfile profile =
80 FormatProfile::None;
82 Error::None;
83 };
84
85 template <size_t BlobBytes, size_t ArgCount>
87
92 static constexpr size_t inline_text_limit = 2 * sizeof(size_t) - 1;
93
94 // Conservative single-pass scratch bounds derived from the original source:
95 // - literal text can cost up to 3 code bytes per source byte (TextInline)
96 // - value records cost at most 2 code bytes per source byte (%x -> 4 bytes)
97 // - therefore 3 * SourceSize() + 1 safely covers the temporary record stream
98 // plus the final End marker.
99 // 基于原始源串给单遍构建器预留的保守上界:
100 // - 普通文本最坏可膨胀为每字节 3 个码流字节(TextInline)
101 // - 值记录最坏约为每字节 2 个码流字节(如 %x -> 4 字节)
102 // - 因此 3 * SourceSize() + 1 足以覆盖临时记录流与最终结束标记。
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
300 {
301 std::array<uint8_t, max_code_bytes>
304 std::array<uint8_t, max_text_pool_bytes>
306 std::array<FormatArgumentInfo, max_arg_count>
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 {
346 static_cast<uint8_t>(Frontend::SourceData()[offset + i]));
347 }
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 {
368 static_cast<uint8_t>(Frontend::SourceData()[offset + i]);
369 }
370
371 return Error::None;
372 }
373
381 [[nodiscard]] consteval Error Field(const FormatField& field)
382 {
383 auto op = FastFieldOp(field);
384 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(op));
385
386 switch (op)
387 {
388 case FormatOp::U32Dec:
389 case FormatOp::I32Dec:
390 case FormatOp::U32Binary:
391 case FormatOp::U32Octal:
392 case FormatOp::U32HexLower:
393 case FormatOp::U32HexUpper:
394 case FormatOp::StringRaw:
395 case FormatOp::CharacterRaw:
396 break;
397 case FormatOp::U32ZeroPadWidth:
399 break;
400 case FormatOp::GenericField:
401 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.type));
403 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.fill));
406 break;
407 case FormatOp::TextInline:
408 case FormatOp::TextRef:
409 case FormatOp::TextSpace:
410 case FormatOp::End:
411 break;
412 }
413
414 profile |=
415 op == FormatOp::GenericField ? GenericProfileFor(field.type) : ProfileForOp(op);
416
418 .pack = field.pack,
419 .rule = field.rule,
420 };
421 return Error::None;
422 }
423
429 [[nodiscard]] constexpr size_t FinalCodeBytes() const { return code_bytes + 1; }
435 [[nodiscard]] constexpr size_t FinalBlobBytes() const
436 {
438 }
439
455 template <size_t CodeBytes, size_t BlobBytes, size_t ArgCount>
456 [[nodiscard]] consteval auto Finish() const
457 {
459 result.profile = profile;
460 size_t scratch_in = 0;
461 size_t code_out = 0;
462
463 while (scratch_in < code_bytes)
464 {
465 auto op = static_cast<FormatOp>(code_scratch[scratch_in++]);
466 EmitByte(result.codes, code_out, static_cast<uint8_t>(op));
467
468 if (op == FormatOp::TextInline)
469 {
470 while (true)
471 {
472 uint8_t byte = code_scratch[scratch_in++];
473 EmitByte(result.codes, code_out, byte);
474 if (byte == 0)
475 {
476 break;
477 }
478 }
479 continue;
480 }
481
482 const size_t payload_bytes = FormatOpPayloadBytes(op);
483 if (payload_bytes == 0)
484 {
485 continue;
486 }
487
488 if (op == FormatOp::TextRef)
489 {
490 auto relative_offset = ReadNative<uint16_t>(code_scratch, scratch_in);
491 auto text_size = ReadNative<uint16_t>(code_scratch, scratch_in);
492 size_t absolute_offset = CodeBytes + relative_offset;
493 if (absolute_offset > std::numeric_limits<uint16_t>::max())
494 {
495 result.compile_error = Error::TextOffsetOverflow;
496 return result;
497 }
498 EmitNative(result.codes, code_out, static_cast<uint16_t>(absolute_offset));
499 EmitNative(result.codes, code_out, text_size);
500 continue;
501 }
502
503 if (payload_bytes == 1)
504 {
505 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
506 continue;
507 }
508
509 if (op == FormatOp::GenericField)
510 {
511 for (size_t i = 0; i < payload_bytes; ++i)
512 {
513 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
514 }
515 }
516 }
517
518 EmitByte(result.codes, code_out, static_cast<uint8_t>(FormatOp::End));
519
520 for (size_t i = 0; i < text_pool_bytes; ++i)
521 {
522 result.codes[CodeBytes + i] = text_scratch[i];
523 }
524 for (size_t i = 0; i < ArgCount; ++i)
525 {
526 result.arg_info[i] = arg_scratch[i];
527 }
528
529 return result;
530 }
531 };
532
534 "LibXR::Print::FormatCompiler: frontend Walk(visitor) must accept "
535 "the shared builder and return ErrorType");
536
537 public:
545 [[nodiscard]] static consteval auto Compile()
546 {
547 constexpr auto scratch = []() consteval
548 {
549 ScratchBuilder builder{};
550 builder.frontend_error = Frontend::Walk(builder);
551 return builder;
552 }();
553
554 if constexpr (scratch.frontend_error != Error::None)
555 {
556 return Failed(scratch.frontend_error);
557 }
558 else
559 {
560 return scratch.template Finish<scratch.FinalCodeBytes(), scratch.FinalBlobBytes(),
561 scratch.arg_count>();
562 }
563 }
564};
565} // namespace LibXR::Print
共享编译期后端,把前端事件整理成最终字节流和参数表。 / Shared compile-time backend that turns frontend events into the final by...
static consteval T ReadNative(const auto &data, size_t &pos)
从临时字节缓冲区读取一个按本机字节序编码的 POD 值 / Read one native-endian POD value from a scratch byte buffer
static consteval void EmitNative(auto &data, size_t &out, T value)
向临时或最终字节缓冲区追加一个按本机字节序编码的 POD 值 / Append one native-endian POD value into a scratch or final byte buff...
static constexpr size_t inline_text_limit
超过该长度后,字面文本会从内嵌模式切换为 TextRef / Maximum inline literal size before the backend spills to TextRef.
static consteval bool IsRawTextField(const FormatField &field)
判断字段是否满足裸文本快路径条件。 / Tests whether one field matches the raw text fast-path gate.
static consteval void EmitByte(auto &data, size_t &out, uint8_t value)
向临时或最终字节缓冲区追加一个原始字节 / Append one raw byte into a scratch or final byte buffer
static consteval auto Failed(Error error)
构造只携带编译错误的最小失败结果。 / Builds the minimal failed result carrying only the compile error.
static consteval bool IsRawIntegerField(const FormatField &field)
判断字段是否满足裸整数快路径条件。 / Tests whether one field matches the raw integer fast-path gate.
static consteval auto Compile()
把一个前端编译成最终字节流、参数表和 writer 摘要。 / Compiles one frontend into the final byte stream, argument table,...
static consteval FormatOp FastFieldOp(const FormatField &field)
为一个字段选择仍能正确打印它的最小操作码。 / Chooses the smallest opcode that can still print this field correctly.
static consteval FormatProfile ProfileForOp(FormatOp op)
指出某个操作码族需要打开哪一个 writer-profile 位。 / Says which writer-profile bit one opcode family needs.
可由共享后端读取源码信息的格式前端 / 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.
当前后端实例产出的最终精确尺寸编译格式 / Final exact-size compiled format produced by this backend instance
std::array< uint8_t, BlobBytes > CodeList
final runtime byte block / 最终运行期字节块
ArgumentListData arg_info
ordered argument metadata / 按参数顺序排列的参数元信息
Error compile_error
first compile-time failure, if any / 首个编译期失败原因
FormatProfile profile
compile-time executor profile / 编译期执行器配置
std::array< FormatArgumentInfo, ArgCount > ArgumentListData
CodeList codes
code stream first, text pool second / 前半记录流,后半文本池
由前端文本/字段事件直接喂给的单遍临时构建器。 / One-pass scratch builder fed directly by frontend text/field events.
size_t arg_count
consumed runtime arguments / 运行期参数个数
size_t code_bytes
scratch record-stream bytes, excluding End / 临时记录流字节数,不含 End
std::array< FormatArgumentInfo, max_arg_count > arg_scratch
temporary ordered argument metadata / 临时参数元信息表
constexpr size_t FinalCodeBytes() const
含 End 结束标记在内的最终记录流字节数 / Final record-stream size including the End marker.
std::array< uint8_t, max_text_pool_bytes > text_scratch
temporary trailing text pool / 临时尾部文本池
consteval Error Text(size_t offset, size_t text_size)
将单个字面文本片段追加到临时缓冲区。 / Appends one literal-text span into the scratch buffers.
constexpr size_t FinalBlobBytes() const
含尾部文本池在内的最终保留字节块大小 / Final retained byte-block size including the trailing text pool.
consteval auto Finish() const
把临时缓冲打包成最终精确尺寸的编译格式。 / Packs the scratch buffers into the final exact-size compiled format.
consteval Error Field(const FormatField &field)
将一个值字段追加到临时字节流和临时参数表中。 / Appends one value field into the scratch byte stream and scratch argument ta...
Error frontend_error
first frontend failure / 首个前端失败原因
std::array< uint8_t, max_code_bytes > code_scratch
size_t text_pool_bytes
scratch text-pool bytes / 临时文本池字节数
一条已经决定完毕、运行期 writer 知道如何打印的值字段。 / One fully-decided value field that the runtime writer knows how to ...
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 / 已解析的字段宽度