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{
41template <typename Frontend>
43{
44 private:
45 static_assert(SourceFrontend<Frontend>,
46 "LibXR::Print::FormatCompiler: frontend must expose ErrorType, "
47 "SourceData(), and SourceSize()");
48
49 using Error = typename Frontend::ErrorType;
50
55 template <size_t BlobBytes, size_t ArgCount>
57 {
58 using CodeList =
59 std::array<uint8_t, BlobBytes>;
61 std::array<FormatArgumentInfo,
62 ArgCount>;
63
66 FormatProfile profile = FormatProfile::None;
67 Error compile_error = Error::None;
68 };
69
70 template <size_t BlobBytes, size_t ArgCount>
72
74 static constexpr size_t inline_text_limit = 2 * sizeof(size_t) - 1;
75
76 // Conservative single-pass scratch bounds derived from the original source:
77 // - literal text can cost up to 3 code bytes per source byte (TextInline)
78 // - value records cost at most 2 code bytes per source byte (%x -> 4 bytes)
79 // - therefore 3 * SourceSize() + 1 safely covers the temporary record stream
80 // plus the final End marker.
81 // 基于原始源串给单遍构建器预留的保守上界:
82 // - 普通文本最坏可膨胀为每字节 3 个码流字节(TextInline)
83 // - 值记录最坏约为每字节 2 个码流字节(如 %x -> 4 字节)
84 // - 因此 3 * SourceSize() + 1 足以覆盖临时记录流与最终结束标记。
85 static constexpr size_t max_code_bytes = 3 * Frontend::SourceSize() + 1;
86 static constexpr size_t max_text_pool_bytes = Frontend::SourceSize();
87 static constexpr size_t max_arg_count = Frontend::SourceSize();
88 static constexpr uint8_t unspecified_precision =
89 std::numeric_limits<uint8_t>::max();
90
91 static consteval void EmitByte(auto& data, size_t& out, uint8_t value)
92 {
93 data[out++] = value;
94 }
95
96 template <typename T>
97 static consteval void EmitNative(auto& data, size_t& out, T value)
98 {
99 auto bytes = std::bit_cast<std::array<uint8_t, sizeof(T)>>(value);
100 for (auto byte : bytes)
101 {
102 data[out++] = byte;
103 }
104 }
105
106 template <typename T>
107 [[nodiscard]] static consteval T ReadNative(const auto& data, size_t& pos)
108 {
109 std::array<uint8_t, sizeof(T)> bytes{};
110 for (size_t i = 0; i < sizeof(T); ++i)
111 {
112 bytes[i] = data[pos++];
113 }
114 return std::bit_cast<T>(bytes);
115 }
116
117 [[nodiscard]] static consteval auto Failed(Error error)
118 {
119 Result<1, 0> result{};
120 result.compile_error = error;
121 return result;
122 }
123
128 [[nodiscard]] static consteval FormatProfile ProfileForOp(FormatOp op)
129 {
130 switch (op)
131 {
132 case FormatOp::U32Dec:
133 case FormatOp::U32ZeroPadWidth:
134 return FormatProfile::U32;
135 case FormatOp::StringRaw:
136 return FormatProfile::TextArg;
137 case FormatOp::F32FixedPrec:
138 return FormatProfile::F32Fixed;
139 case FormatOp::F64FixedPrec:
140 return FormatProfile::F64Fixed;
141 case FormatOp::GenericField:
142 return FormatProfile::Generic;
143 case FormatOp::TextInline:
144 case FormatOp::TextRef:
145 case FormatOp::TextSpace:
146 case FormatOp::End:
147 return FormatProfile::None;
148 }
149
150 return FormatProfile::None;
151 }
152
157 [[nodiscard]] static consteval FormatOp FastFieldOp(const FormatField& field)
158 {
159 if (field.type == FormatType::Unsigned32 && field.pack == FormatPackKind::U32 &&
160 field.flags == 0 && field.fill == ' ' && field.width == 0 &&
161 field.precision == unspecified_precision)
162 {
163 return FormatOp::U32Dec;
164 }
165
166 if (field.type == FormatType::Unsigned32 && field.pack == FormatPackKind::U32 &&
167 field.flags == static_cast<uint8_t>(FormatFlag::ZeroPad) &&
168 field.fill == ' ' && field.width != 0 &&
169 field.precision == unspecified_precision)
170 {
171 return FormatOp::U32ZeroPadWidth;
172 }
173
174 if (field.type == FormatType::String &&
175 field.pack == FormatPackKind::StringView && field.flags == 0 &&
176 field.fill == ' ' &&
177 field.width == 0 && field.precision == unspecified_precision)
178 {
179 return FormatOp::StringRaw;
180 }
181
182 if (field.type == FormatType::FloatFixed && field.pack == FormatPackKind::F32 &&
183 field.flags == 0 && field.fill == ' ' && field.width == 0 &&
184 field.precision != unspecified_precision)
185 {
186 return FormatOp::F32FixedPrec;
187 }
188
189 if (field.type == FormatType::DoubleFixed && field.pack == FormatPackKind::F64 &&
190 field.flags == 0 && field.fill == ' ' && field.width == 0 &&
191 field.precision != unspecified_precision)
192 {
193 return FormatOp::F64FixedPrec;
194 }
195
196 return FormatOp::GenericField;
197 }
198
204 {
205 std::array<uint8_t, max_code_bytes> code_scratch{};
206 std::array<uint8_t, max_text_pool_bytes> text_scratch{};
207 std::array<FormatArgumentInfo, max_arg_count> arg_scratch{};
208
209 size_t code_bytes = 0;
210 size_t text_pool_bytes = 0;
211 size_t arg_count = 0;
212 FormatProfile profile = FormatProfile::None;
213 Error frontend_error = Error::None;
214
219 [[nodiscard]] consteval Error Text(size_t offset, size_t text_size)
220 {
221 if (text_size == 0)
222 {
223 return Error::None;
224 }
225
226 if (text_size == 1 && Frontend::SourceData()[offset] == ' ')
227 {
228 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextSpace));
229 return Error::None;
230 }
231
232 if (text_size <= inline_text_limit)
233 {
234 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextInline));
235 for (size_t i = 0; i < text_size; ++i)
236 {
237 EmitByte(code_scratch, code_bytes,
238 static_cast<uint8_t>(Frontend::SourceData()[offset + i]));
239 }
240 EmitByte(code_scratch, code_bytes, 0);
241 return Error::None;
242 }
243
244 if (text_pool_bytes > std::numeric_limits<uint16_t>::max())
245 {
246 return Error::TextOffsetOverflow;
247 }
248 if (text_size > std::numeric_limits<uint16_t>::max())
249 {
250 return Error::TextSizeOverflow;
251 }
252
253 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(FormatOp::TextRef));
254 EmitNative(code_scratch, code_bytes, static_cast<uint16_t>(text_pool_bytes));
255 EmitNative(code_scratch, code_bytes, static_cast<uint16_t>(text_size));
256
257 for (size_t i = 0; i < text_size; ++i)
258 {
260 static_cast<uint8_t>(Frontend::SourceData()[offset + i]);
261 }
262
263 return Error::None;
264 }
265
271 [[nodiscard]] consteval Error Field(const FormatField& field)
272 {
273 auto op = FastFieldOp(field);
274 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(op));
275
276 switch (op)
277 {
278 case FormatOp::U32Dec:
279 case FormatOp::StringRaw:
280 break;
281 case FormatOp::U32ZeroPadWidth:
282 EmitByte(code_scratch, code_bytes, field.width);
283 break;
284 case FormatOp::F32FixedPrec:
285 case FormatOp::F64FixedPrec:
286 EmitByte(code_scratch, code_bytes, field.precision);
287 break;
288 case FormatOp::GenericField:
289 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.type));
290 EmitByte(code_scratch, code_bytes, field.flags);
291 EmitByte(code_scratch, code_bytes, static_cast<uint8_t>(field.fill));
292 EmitByte(code_scratch, code_bytes, field.width);
293 EmitByte(code_scratch, code_bytes, field.precision);
294 break;
295 case FormatOp::TextInline:
296 case FormatOp::TextRef:
297 case FormatOp::TextSpace:
298 case FormatOp::End:
299 break;
300 }
301
302 profile |= ProfileForOp(op);
303
305 .pack = field.pack,
306 .rule = field.rule,
307 };
308 return Error::None;
309 }
310
312 [[nodiscard]] constexpr size_t FinalCodeBytes() const { return code_bytes + 1; }
314 [[nodiscard]] constexpr size_t FinalBlobBytes() const
315 {
317 }
318
329 template <size_t CodeBytes, size_t BlobBytes, size_t ArgCount>
330 [[nodiscard]] consteval auto Finish() const
331 {
333 result.profile = profile;
334 size_t scratch_in = 0;
335 size_t code_out = 0;
336
337 while (scratch_in < code_bytes)
338 {
339 auto op = static_cast<FormatOp>(code_scratch[scratch_in++]);
340 EmitByte(result.codes, code_out, static_cast<uint8_t>(op));
341
342 if (op == FormatOp::TextInline)
343 {
344 while (true)
345 {
346 uint8_t byte = code_scratch[scratch_in++];
347 EmitByte(result.codes, code_out, byte);
348 if (byte == 0)
349 {
350 break;
351 }
352 }
353 continue;
354 }
355
356 if (op == FormatOp::TextRef)
357 {
358 auto relative_offset = ReadNative<uint16_t>(code_scratch, scratch_in);
359 auto text_size = ReadNative<uint16_t>(code_scratch, scratch_in);
360 size_t absolute_offset = CodeBytes + relative_offset;
361 if (absolute_offset > std::numeric_limits<uint16_t>::max())
362 {
363 result.compile_error = Error::TextOffsetOverflow;
364 return result;
365 }
366 EmitNative(result.codes, code_out, static_cast<uint16_t>(absolute_offset));
367 EmitNative(result.codes, code_out, text_size);
368 continue;
369 }
370
371 if (op == FormatOp::U32ZeroPadWidth || op == FormatOp::F32FixedPrec ||
372 op == FormatOp::F64FixedPrec)
373 {
374 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
375 continue;
376 }
377
378 if (op == FormatOp::GenericField)
379 {
380 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
381 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
382 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
383 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
384 EmitByte(result.codes, code_out, code_scratch[scratch_in++]);
385 }
386 }
387
388 EmitByte(result.codes, code_out, static_cast<uint8_t>(FormatOp::End));
389
390 for (size_t i = 0; i < text_pool_bytes; ++i)
391 {
392 result.codes[CodeBytes + i] = text_scratch[i];
393 }
394 for (size_t i = 0; i < ArgCount; ++i)
395 {
396 result.arg_info[i] = arg_scratch[i];
397 }
398
399 return result;
400 }
401 };
402
404 "LibXR::Print::FormatCompiler: frontend Walk(visitor) must accept "
405 "the shared builder and return ErrorType");
406
407 public:
412 [[nodiscard]] static consteval auto Compile()
413 {
414 constexpr auto scratch = []() consteval {
415 ScratchBuilder builder{};
416 builder.frontend_error = Frontend::Walk(builder);
417 return builder;
418 }();
419
420 if constexpr (scratch.frontend_error != Error::None)
421 {
422 return Failed(scratch.frontend_error);
423 }
424 else
425 {
426 return scratch.template Finish<scratch.FinalCodeBytes(),
427 scratch.FinalBlobBytes(), scratch.arg_count>();
428 }
429 }
430};
431} // namespace LibXR::Print
Shared compile-time backend that lowers frontend text/field events into one final compiled format.
static constexpr size_t inline_text_limit
Maximum inline literal size before the backend spills to TextRef. / 超过该长度后,字面文本会从内嵌模式切换为 TextRef.
static consteval auto Compile()
Compiles the frontend into one final compiled format.
static consteval FormatOp FastFieldOp(const FormatField &field)
Chooses the narrowest opcode that preserves one normalized field's behavior.
static consteval FormatProfile ProfileForOp(FormatOp op)
Maps one opcode family to the runtime executor profile bit it requires.
可由共享后端读取源码信息的格式前端。
可向指定 visitor 发射格式事件的前端。
Compiled-format runtime contract consumed by Writer.
Final tightly-sized compiled format emitted 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
ordered compile-time argument metadata / 按顺序排列的编译期参数元信息
CodeList codes
code stream first, text pool second / 前半记录流,后半文本池
Single-pass scratch builder driven directly by frontend 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
Final record-stream size including the End marker. / 含 End 结束标记在内的最终记录流字节数
std::array< uint8_t, max_text_pool_bytes > text_scratch
temporary trailing text pool / 临时尾部文本池
FormatProfile profile
required runtime executor profile / 需要的运行期执行器配置
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
Materializes the exact final compiled format from the scratch buffers.
consteval Error Field(const FormatField &field)
Appends one normalized value field into the scratch record stream and argument metadata table.
Error frontend_error
first frontend failure / 首个前端失败原因
std::array< uint8_t, max_code_bytes > code_scratch
temporary record stream without final End / 临时记录流,不含最终 End
size_t text_pool_bytes
scratch text-pool bytes / 临时文本池字节数
Normalized value-field semantics shared by all formatting frontends.
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 / 已解析的字段宽度