7#include "format_compile.hpp"
9namespace LibXR::Print::Detail::PrintfCompile
24enum class ValueKind : uint8_t
48 ValueKind
type = ValueKind::None;
67 flags |=
static_cast<uint8_t
>(FormatFlag::LeftAlign);
71 flags |=
static_cast<uint8_t
>(FormatFlag::ForceSign);
75 flags |=
static_cast<uint8_t
>(FormatFlag::SpaceSign);
79 flags |=
static_cast<uint8_t
>(FormatFlag::Alternate);
83 flags |=
static_cast<uint8_t
>(FormatFlag::ZeroPad);
87 flags |=
static_cast<uint8_t
>(FormatFlag::UpperCase);
103enum class FeatureGate : uint8_t
118enum class LengthPolicy : uint8_t
132 ValueKind
type = ValueKind::None;
134 FeatureGate::Integer;
136 LengthPolicy::NoneOnly;
144inline constexpr size_t length_rule_count =
145 static_cast<size_t>(Length::LongDouble) + 1;
151inline constexpr std::array<FormatArgumentRule, length_rule_count> signed_rules{
152 FormatArgumentRule::SignedAny,
153 FormatArgumentRule::SignedChar,
154 FormatArgumentRule::SignedShort,
155 FormatArgumentRule::SignedLong,
156 FormatArgumentRule::SignedLongLong,
157 FormatArgumentRule::SignedIntMax,
158 FormatArgumentRule::SignedSize,
159 FormatArgumentRule::SignedPtrDiff,
160 FormatArgumentRule::None,
167inline constexpr std::array<FormatArgumentRule, length_rule_count> unsigned_rules{
168 FormatArgumentRule::UnsignedAny,
169 FormatArgumentRule::UnsignedChar,
170 FormatArgumentRule::UnsignedShort,
171 FormatArgumentRule::UnsignedLong,
172 FormatArgumentRule::UnsignedLongLong,
173 FormatArgumentRule::UnsignedIntMax,
174 FormatArgumentRule::UnsignedSize,
175 FormatArgumentRule::UnsignedPtrDiff,
176 FormatArgumentRule::None,
183inline constexpr std::array<SpecifierDescriptor, 17> specifiers{{
184 {
'd', ValueKind::Signed, FeatureGate::Integer, LengthPolicy::Integer,
false},
185 {
'i', ValueKind::Signed, FeatureGate::Integer, LengthPolicy::Integer,
false},
186 {
'u', ValueKind::Unsigned, FeatureGate::Integer, LengthPolicy::Integer,
false},
187 {
'b', ValueKind::Binary, FeatureGate::IntegerBase8_16, LengthPolicy::Integer,
false},
188 {
'B', ValueKind::Binary, FeatureGate::IntegerBase8_16, LengthPolicy::Integer,
true},
189 {
'o', ValueKind::Octal, FeatureGate::IntegerBase8_16, LengthPolicy::Integer,
false},
190 {
'x', ValueKind::HexLower, FeatureGate::IntegerBase8_16, LengthPolicy::Integer,
192 {
'X', ValueKind::HexUpper, FeatureGate::IntegerBase8_16, LengthPolicy::Integer,
194 {
'p', ValueKind::Pointer, FeatureGate::Pointer, LengthPolicy::NoneOnly,
false},
195 {
'c', ValueKind::Character, FeatureGate::Text, LengthPolicy::NoneOnly,
false},
196 {
's', ValueKind::String, FeatureGate::Text, LengthPolicy::NoneOnly,
false},
197 {
'f', ValueKind::FloatFixed, FeatureGate::FloatFixed, LengthPolicy::Float,
false},
198 {
'F', ValueKind::FloatFixed, FeatureGate::FloatFixed, LengthPolicy::Float,
true},
199 {
'e', ValueKind::FloatScientific, FeatureGate::FloatScientific,
200 LengthPolicy::Float,
false},
201 {
'E', ValueKind::FloatScientific, FeatureGate::FloatScientific,
202 LengthPolicy::Float,
true},
203 {
'g', ValueKind::FloatGeneral, FeatureGate::FloatGeneral, LengthPolicy::Float,
205 {
'G', ValueKind::FloatGeneral, FeatureGate::FloatGeneral, LengthPolicy::Float,
209namespace SourceSyntax
226template <
size_t MaxFieldCount>
229 std::array<size_t, MaxFieldCount>
order{};
234 [[nodiscard]]
consteval Error Text(
size_t,
size_t)
const {
return Error::None; }
239 size_t used_argument_count = conversion.
arg_index + 1;
252template <
size_t FieldCount,
size_t ArgCount>
255 std::array<size_t, FieldCount>
order{};
256 std::array<FormatArgumentInfo, ArgCount>
args{};
261[[nodiscard]]
constexpr bool IsDigit(
char ch)
263 return ch >=
'0' && ch <=
'9';
267[[nodiscard]]
constexpr bool HasEmbeddedNul(std::string_view source)
269 for (
char ch : source)
288[[nodiscard]]
consteval Error ParseArgumentIndex(std::string_view source,
size_t& pos,
289 IndexingState& indexing,
290 Conversion& conversion)
292 if (pos >= source.size() || !IsDigit(source[pos]))
299 while (probe < source.size() && IsDigit(source[probe]))
301 auto digit =
static_cast<size_t>(source[probe] -
'0');
302 if (index > (std::numeric_limits<size_t>::max() - digit) / 10)
304 return Error::NumberOverflow;
307 index = index * 10 + digit;
311 if (probe >= source.size() || source[probe] !=
'$')
316 if (!Config::enable_explicit_argument_indexing)
318 return Error::PositionalArgumentDisabled;
323 return Error::InvalidArgumentIndex;
325 if (indexing.uses_sequential)
327 return Error::MixedIndexing;
330 indexing.uses_positional =
true;
331 conversion.positional =
true;
332 conversion.arg_index = index - 1;
344[[nodiscard]]
constexpr size_t LengthIndex(
Length length)
346 return static_cast<size_t>(length);
354 case ValueKind::Unsigned:
355 case ValueKind::Binary:
356 case ValueKind::Octal:
357 case ValueKind::HexLower:
358 case ValueKind::HexUpper:
368 for (
const auto& descriptor : specifiers)
370 if (descriptor.token == token)
384 case FeatureGate::Integer:
385 return Config::enable_integer;
386 case FeatureGate::IntegerBase8_16:
387 return Config::enable_integer_base8_16;
388 case FeatureGate::Pointer:
389 return Config::enable_pointer;
390 case FeatureGate::Text:
391 return Config::enable_text;
392 case FeatureGate::FloatFixed:
393 return Config::enable_float_fixed &&
394 (length != Length::LongDouble || Config::enable_float_long_double);
395 case FeatureGate::FloatScientific:
396 return Config::enable_float_scientific &&
397 (length != Length::LongDouble || Config::enable_float_long_double);
398 case FeatureGate::FloatGeneral:
399 return Config::enable_float_general &&
400 (length != Length::LongDouble || Config::enable_float_long_double);
411 case LengthPolicy::Integer:
412 return length != Length::LongDouble;
413 case LengthPolicy::NoneOnly:
414 return length == Length::Default;
415 case LengthPolicy::Float:
416 return length == Length::Default || length == Length::LongDouble;
424 const std::array<FormatArgumentRule, length_rule_count>& rules,
Length length)
426 return rules[LengthIndex(length)];
434 case FormatArgumentRule::SignedLong:
435 return sizeof(long) >
sizeof(int32_t);
436 case FormatArgumentRule::SignedLongLong:
437 return sizeof(
long long) >
sizeof(int32_t);
438 case FormatArgumentRule::SignedIntMax:
439 return sizeof(intmax_t) >
sizeof(int32_t);
440 case FormatArgumentRule::SignedSize:
441 return sizeof(std::make_signed_t<size_t>) >
sizeof(int32_t);
442 case FormatArgumentRule::SignedPtrDiff:
443 return sizeof(ptrdiff_t) >
sizeof(int32_t);
444 case FormatArgumentRule::UnsignedLong:
445 return sizeof(
unsigned long) >
sizeof(uint32_t);
446 case FormatArgumentRule::UnsignedLongLong:
447 return sizeof(
unsigned long long) >
sizeof(uint32_t);
448 case FormatArgumentRule::UnsignedIntMax:
449 return sizeof(uintmax_t) >
sizeof(uint32_t);
450 case FormatArgumentRule::UnsignedSize:
451 return sizeof(size_t) >
sizeof(uint32_t);
452 case FormatArgumentRule::UnsignedPtrDiff:
453 return sizeof(std::make_unsigned_t<ptrdiff_t>) >
sizeof(uint32_t);
462 return Config::enable_float_double;
470 FormatArgumentRule rule)
472 if (conversion.
length == Length::LongDouble)
474 if (conversion.
type == ValueKind::FloatFixed)
476 return FormatType::LongDoubleFixed;
478 if (conversion.
type == ValueKind::FloatScientific)
480 return FormatType::LongDoubleScientific;
482 if (conversion.
type == ValueKind::FloatGeneral)
484 return FormatType::LongDoubleGeneral;
488 if (conversion.
type == ValueKind::Signed)
492 if (conversion.
type == ValueKind::Unsigned)
495 : FormatType::Unsigned32;
497 if (conversion.
type == ValueKind::Binary)
501 if (conversion.
type == ValueKind::Octal)
505 if (conversion.
type == ValueKind::HexLower)
508 : FormatType::HexLower32;
510 if (conversion.
type == ValueKind::HexUpper)
513 : FormatType::HexUpper32;
516 switch (conversion.
type)
518 case ValueKind::Pointer:
519 return FormatType::Pointer;
520 case ValueKind::Character:
521 return FormatType::Character;
522 case ValueKind::String:
523 return FormatType::String;
524 case ValueKind::FloatFixed:
526 : FormatType::FloatFixed;
527 case ValueKind::FloatScientific:
529 : FormatType::FloatScientific;
530 case ValueKind::FloatGeneral:
532 : FormatType::FloatGeneral;
533 case ValueKind::None:
534 case ValueKind::Signed:
535 case ValueKind::Unsigned:
536 case ValueKind::Binary:
537 case ValueKind::Octal:
538 case ValueKind::HexLower:
539 case ValueKind::HexUpper:
540 return FormatType::End;
543 return FormatType::End;
551 case FormatType::Signed32:
552 return FormatPackKind::I32;
553 case FormatType::Signed64:
554 return FormatPackKind::I64;
555 case FormatType::Unsigned32:
556 case FormatType::Binary32:
557 case FormatType::Octal32:
558 case FormatType::HexLower32:
559 case FormatType::HexUpper32:
560 return FormatPackKind::U32;
561 case FormatType::Unsigned64:
562 case FormatType::Binary64:
563 case FormatType::Octal64:
564 case FormatType::HexLower64:
565 case FormatType::HexUpper64:
566 return FormatPackKind::U64;
567 case FormatType::Pointer:
568 return FormatPackKind::Pointer;
569 case FormatType::Character:
570 return FormatPackKind::Character;
571 case FormatType::String:
572 return FormatPackKind::StringView;
573 case FormatType::FloatFixed:
574 case FormatType::FloatScientific:
575 case FormatType::FloatGeneral:
576 return FormatPackKind::F32;
577 case FormatType::DoubleFixed:
578 case FormatType::DoubleScientific:
579 case FormatType::DoubleGeneral:
580 return FormatPackKind::F64;
581 case FormatType::LongDoubleFixed:
582 case FormatType::LongDoubleScientific:
583 case FormatType::LongDoubleGeneral:
584 return FormatPackKind::LongDouble;
585 case FormatType::End:
586 case FormatType::TextInline:
587 case FormatType::TextRef:
588 case FormatType::TextSpace:
589 return FormatPackKind::U32;
592 return FormatPackKind::U32;
598 if (conversion.
type == ValueKind::Signed)
608 switch (conversion.
type)
610 case ValueKind::Pointer:
611 return FormatArgumentRule::Pointer;
612 case ValueKind::Character:
613 return FormatArgumentRule::Character;
614 case ValueKind::String:
615 return FormatArgumentRule::String;
616 case ValueKind::FloatFixed:
617 case ValueKind::FloatScientific:
618 case ValueKind::FloatGeneral:
619 return (conversion.
length == Length::LongDouble)
620 ? FormatArgumentRule::LongDouble
621 : FormatArgumentRule::Float;
622 case ValueKind::None:
623 case ValueKind::Signed:
624 case ValueKind::Unsigned:
625 case ValueKind::Binary:
626 case ValueKind::Octal:
627 case ValueKind::HexLower:
628 case ValueKind::HexUpper:
632 return FormatArgumentRule::None;
641 return Error::InvalidLength;
657 .width = conversion.
width,
667[[nodiscard]]
consteval Error ParseByte(std::string_view source,
size_t& pos,
668 uint8_t limit, uint8_t& value)
671 if (pos >= source.size() || !IsDigit(source[pos]))
676 while (pos < source.size() && IsDigit(source[pos]))
678 auto digit =
static_cast<uint8_t
>(source[pos] -
'0');
679 if (value >
static_cast<uint8_t
>((limit - digit) / 10))
681 return Error::NumberOverflow;
684 value =
static_cast<uint8_t
>(value * 10 + digit);
692[[nodiscard]]
consteval Error ParseFlags(std::string_view source,
size_t& pos,
693 Conversion& conversion)
695 while (pos < source.size())
700 conversion.left_align =
true;
703 conversion.force_sign =
true;
706 conversion.space_sign =
true;
709 if (!Config::enable_alternate)
711 return Error::InvalidSpecifier;
713 conversion.alternate =
true;
716 conversion.zero_pad =
true;
728[[nodiscard]]
consteval Error ParseWidth(std::string_view source,
size_t& pos,
729 Conversion& conversion)
731 if (pos < source.size() && source[pos] ==
'*')
733 return Error::DynamicField;
736 if (!Config::enable_width && pos < source.size() && IsDigit(source[pos]))
738 return Error::InvalidSpecifier;
741 return ParseByte(source, pos, std::numeric_limits<uint8_t>::max(), conversion.width);
745[[nodiscard]]
consteval Error ParsePrecision(std::string_view source,
size_t& pos,
746 Conversion& conversion)
748 if (pos >= source.size() || source[pos] !=
'.')
753 if (!Config::enable_precision)
755 return Error::InvalidSpecifier;
759 if (pos < source.size() && source[pos] ==
'*')
761 return Error::DynamicField;
764 if (pos < source.size() && IsDigit(source[pos]))
766 conversion.has_precision =
true;
768 source, pos,
static_cast<uint8_t
>(std::numeric_limits<uint8_t>::max() - 1),
769 conversion.precision);
772 conversion.has_precision =
true;
773 conversion.precision = 0;
777consteval void ParseLength(std::string_view source,
size_t& pos, Conversion& conversion)
779 if (pos >= source.size())
784 char token = source[pos];
785 if (token ==
'h' || token ==
'l')
788 conversion.length = (token ==
'h') ? Length::Short : Length::Long;
789 if (pos < source.size() && source[pos] == token)
791 conversion.length = (token ==
'h') ? Length::Char : Length::LongLong;
800 conversion.length = Length::IntMax;
804 conversion.length = Length::Size;
808 conversion.length = Length::PtrDiff;
812 conversion.length = Length::LongDouble;
820[[nodiscard]]
consteval Error ParseSpecifier(std::string_view source,
size_t& pos,
821 Conversion& conversion)
823 if (pos >= source.size())
825 return Error::UnexpectedEnd;
829 if (descriptor.type == ValueKind::None)
831 return Error::InvalidSpecifier;
835 return Error::InvalidLength;
839 return Error::InvalidSpecifier;
842 conversion.type = descriptor.type;
843 conversion.upper_case = conversion.upper_case || descriptor.upper_case;
848[[nodiscard]]
consteval Error Parse(std::string_view source,
size_t& pos,
849 IndexingState& indexing, Conversion& conversion)
852 if (pos >= source.size())
854 return Error::UnexpectedEnd;
857 auto error = ParseArgumentIndex(source, pos, indexing, conversion);
858 if (error != Error::None)
863 error = ParseFlags(source, pos, conversion);
864 if (error != Error::None)
869 error = ParseWidth(source, pos, conversion);
870 if (error != Error::None)
875 error = ParsePrecision(source, pos, conversion);
876 if (error != Error::None)
881 ParseLength(source, pos, conversion);
882 error = ParseSpecifier(source, pos, conversion);
883 if (error != Error::None)
889 if (error != Error::None)
894 if (!conversion.positional)
896 if (indexing.uses_positional)
898 return Error::MixedIndexing;
901 indexing.uses_sequential =
true;
902 conversion.arg_index = indexing.next_index++;
908[[nodiscard]]
consteval Error WalkSource(std::string_view source,
auto& visitor)
910 if (HasEmbeddedNul(source))
912 return Error::EmbeddedNul;
916 size_t text_begin = 0;
917 IndexingState indexing{};
919 while (pos < source.size())
921 if (source[pos] !=
'%')
927 auto error = visitor.Text(text_begin, pos - text_begin);
928 if (error != Error::None)
933 if (pos + 1 < source.size() && source[pos + 1] ==
'%')
935 error = visitor.Text(pos, 1);
936 if (error != Error::None)
946 auto parse_pos = pos;
947 Conversion conversion{};
948 error = Parse(source, parse_pos, indexing, conversion);
949 if (error != Error::None)
954 error = visitor.Field(conversion);
955 if (error != Error::None)
964 return visitor.Text(text_begin, source.size() - text_begin);
967template <Text Source>
968[[nodiscard]]
consteval auto Analyze()
970 constexpr auto scratch = []()
consteval {
971 SourceAnalysisScratch<Source.Size()> visitor{};
973 WalkSource(std::string_view(Source.Data(), Source.Size()), visitor);
977 SourceAnalysis<scratch.field_count, scratch.argument_count> result{};
978 result.error = scratch.error;
979 for (
size_t i = 0; i < scratch.field_count; ++i)
981 result.order[i] = scratch.order[i];
984 if constexpr (scratch.error != Error::None)
990 struct AnalysisVisitor
992 SourceAnalysis<scratch.field_count, scratch.argument_count>& result;
994 [[nodiscard]]
consteval Error Text(
size_t,
size_t)
const {
return Error::None; }
996 [[nodiscard]]
consteval Error Field(
const Conversion& conversion)
998 auto field = Lowering::LowerField(conversion);
999 auto info = FormatArgumentInfo{
1003 auto& slot = result.args[conversion.arg_index];
1004 if (slot.rule != FormatArgumentRule::None &&
1005 (slot.rule != info.rule || slot.pack != info.pack))
1007 return Error::ConflictingArgument;
1015 AnalysisVisitor visitor{result};
1016 result.error = WalkSource(std::string_view(Source.Data(), Source.Size()), visitor);
1022namespace Lowering = SourceSyntax::Lowering;
1028template <Text Source>
1029[[nodiscard]]
consteval auto Analyze()
1031 return SourceSyntax::Analyze<Source>();
1039template <Text Source>
1040[[nodiscard]]
consteval Error WalkAndLower(
auto& visitor)
1042 struct LoweringVisitor
1044 decltype(visitor)& inner;
1046 [[nodiscard]]
consteval Error Text(
size_t offset,
size_t text_size)
1048 return inner.Text(offset, text_size);
1051 [[nodiscard]]
consteval Error Field(
const Conversion& conversion)
1053 return inner.Field(Lowering::LowerField(conversion));
1057 LoweringVisitor lowering{visitor};
1058 return SourceSyntax::WalkSource(std::string_view(Source.Data(), Source.Size()),
1063namespace LibXR::Print
1069template <Text Source>
1076 [[nodiscard]]
static constexpr const char*
SourceData() {
return Source.Data(); }
1078 [[nodiscard]]
static constexpr size_t SourceSize() {
return Source.Size(); }
1083 return Detail::PrintfCompile::WalkAndLower<Source>(visitor);
Compile-time printf frontend that parses and lowers one source string.
static consteval ErrorType Walk(auto &visitor)
Walks normalized text spans and value fields in source order. / 按源串顺序遍历归一化后的文本片段和字段
static constexpr size_t SourceSize()
Returns the source length without the terminating zero byte. / 返回去掉结尾零字节后的源字符串长度
static constexpr const char * SourceData()
Returns the underlying source bytes without the terminating zero byte. / 返回去掉结尾零字节后的源字符串数据
Length
Supported printf length modifiers after normalization.
Error
Compile-time parse/build failure categories surfaced through static_assert.
constexpr bool UsesDoubleFloatStorage()
Returns whether ordinary printf float conversions lower to double storage. / 判断普通 printf 浮点转换是否降为 dou...
consteval SpecifierDescriptor LookupSpecifier(char token)
Looks up one source conversion character in the descriptor table. / 在描述表中查找一个源级转换字符
consteval FormatArgumentRule LowerRule(const Conversion &conversion)
Lowers one parsed conversion to the compile-time argument matching rule it consumes....
consteval FormatPackKind LowerPackKind(FormatType type)
Maps one semantic runtime type to its packed runtime storage kind. / 将一个语义运行期类型映射到其打包存储类别
constexpr bool TypeEnabled(FeatureGate gate, Length length)
Returns whether one specifier family is enabled under the current feature gates. / 判断某个说明符家族在当前功能开关下是...
consteval FormatField LowerField(const Conversion &conversion)
Lowers one parsed printf conversion into the shared FormatField triple of type, pack,...
constexpr bool LengthAllowed(LengthPolicy policy, Length length)
Returns whether one parsed length modifier is legal for the selected specifier family....
constexpr bool IsUnsignedType(ValueKind type)
Returns whether one frontend semantic family should use unsigned storage/rules. / 判断某个前端语义族是否应使用无符号存储...
constexpr FormatArgumentRule RuleFromTable(const std::array< FormatArgumentRule, length_rule_count > &rules, Length length)
Returns the argument rule selected by one normalized length index. / 返回某个归一化长度索引选中的参数匹配规则
consteval Error ValidateConversion(const Conversion &conversion)
Validates target-dependent lowering constraints after parsing. / 在解析后校验与目标相关的降级约束
consteval bool RuleUses64BitStorage(FormatArgumentRule rule)
Returns whether one argument rule requires 64-bit packed storage on this target. / 判断某个参数规则在当前目标上是否需要...
consteval FormatType LowerType(const Conversion &conversion, FormatArgumentRule rule)
Lowers one parsed printf conversion to its semantic runtime type.
One parsed printf conversion before lowering into the shared format.
bool force_sign
parsed + flag / 已解析的 + 标志
uint8_t precision
parsed precision value / 已解析的精度值
bool alternate
parsed # flag / 已解析的 # 标志
constexpr uint8_t PrecisionByte() const
Returns the shared precision byte, or 0xFF when precision is absent. / 返回共享精度字节;若未显式指定精度则为 0xFF.
size_t arg_index
source argument index consumed by this field / 当前字段消耗的源参数索引
ValueKind type
semantic conversion category / 转换项归一化后的语义类别
bool left_align
parsed - flag / 已解析的 - 标志
uint8_t width
parsed field width / 已解析的字段宽度
Length length
parsed length modifier / 已解析的长度修饰符
bool zero_pad
parsed 0 flag / 已解析的 0 标志
bool has_precision
whether precision was explicitly provided / 是否显式提供了精度
constexpr uint8_t FlagsByte() const
Packs the parsed frontend flags into the shared FormatFlag byte. / 将前端解析出的标志位打包成共享的 FormatFlag 字节
bool space_sign
parsed space-sign flag / 已解析的空格正号标志
bool upper_case
implied uppercase output / 隐含的大写输出标志
bool positional
whether arg_index came from n$ syntax / arg_index 是否来自 n$ 语法
Source-level positional/sequential indexing state.
bool uses_sequential
at least one conversion used implicit sequential order / 至少有一个转换使用了隐式顺序参数
size_t next_index
next sequential argument index / 下一个顺序参数索引
bool uses_positional
at least one conversion used n$ syntax / 至少有一个转换使用了 n$ 语法
Final source-level positional/sequential argument summary.
Error error
first parse/analysis error / 首个解析或分析错误
std::array< size_t, FieldCount > order
field-ordered source argument indexes / 按字段顺序排列的源参数索引
std::array< FormatArgumentInfo, ArgCount > args
source-ordered argument metadata / 按源参数顺序排列的参数元信息
Source-only scratch summary for positional argument analysis.
size_t argument_count
highest referenced argument count / 最高引用到的参数个数
Error error
first parse/analysis error / 首个解析或分析错误
std::array< size_t, MaxFieldCount > order
field-ordered source argument indexes / 按字段顺序排列的源参数索引
size_t field_count
parsed conversion count / 已解析的转换项数量
One source-level printf specifier descriptor.
FeatureGate gate
feature gate controlling this specifier / 控制该说明符的功能开关
ValueKind type
normalized semantic category / 归一化后的语义类别
LengthPolicy length_policy
accepted length family / 允许的长度修饰类别
bool upper_case
whether the specifier implies uppercase output / 说明符是否隐含大写输出
char token
source conversion character / 源格式转换字符
Structural literal wrapper used as the NTTP source for printf formats.