// Copyright Epic Games, Inc. All Rights Reserved. #include "PlainPropsPrint.h" #include "PlainPropsBuild.h" #include "PlainPropsDiff.h" #include "PlainPropsIndex.h" #include "Containers/StringConv.h" #include "Containers/Utf8String.h" #include "PlainPropsInternalFormat.h" #include "PlainPropsInternalPrint.h" #include "PlainPropsInternalRead.h" #include "PlainPropsInternalText.h" #include "Misc/AsciiSet.h" #include "Misc/StringBuilder.h" #include namespace PlainProps { static constexpr bool PrintWithComments = true; ////////////////////////////////////////////////////////////////////////// const FLiterals GLiterals; FAnsiStringView ToString(ERangeSizeType Width) { return GLiterals.Ranges[(uint8)Width]; } FAnsiStringView ToString(FUnpackedLeafType Leaf) { return GLiterals.Leaves[(uint8)Leaf.Type][(uint8)Leaf.Width]; } FAnsiStringView ToString(ELeafWidth Width) { return GLiterals.Widths[(uint8)Width]; } FAnsiStringView ToString(ESchemaFormat Format) { switch (Format) { case ESchemaFormat::InMemoryNames: return "InMemoryNames"; case ESchemaFormat::StableNames: return "StableNames"; } return "Unknown"; } ////////////////////////////////////////////////////////////////////////// void FIdIndexerBase::InitParameterNames() { for (uint32 T = 0; T < 8; ++T) { for (uint32 W = 0; W < 4; ++W) { Leaves[T][W] = InitParameterName(GLiterals.Leaves[T][W]); } } for (uint32 S = 0; S < 9; ++S) { Ranges[S] = InitParameterName(GLiterals.Ranges[S]); } } /////////////////////////////////////////////////////////////////////////////// // Escape the quotation mark (U+0022), backslash (U+005C), // and control characters U+0000 to U+001F (JSON Standard ECMA-404) constexpr FAsciiSet EscapeSet("\\\"" "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"); static inline void EscapeChar(FUtf8Builder& Out, UTF8CHAR Char) { switch (Char) { case '\"': Out.Append("\\\""); break; case '\\': Out.Append("\\\\"); break; case '\b': Out.Append("\\b"); break; case '\f': Out.Append("\\f"); break; case '\n': Out.Append("\\n"); break; case '\r': Out.Append("\\r"); break; case '\t': Out.Append("\\t"); break; default: Out.Appendf(UTF8TEXT("\\u%04x"), uint32(Char)); break; } } template void Print(FUtf8Builder& Out, T Value) { Out.Append(ToString(Value)); } template requires (std::is_integral_v) void Print(FUtf8Builder& Out, T Value) { constexpr size_t BufSize = std::numeric_limits::digits10 + 3; char Buf[BufSize]{}; std::to_chars_result result = std::to_chars(Buf, Buf + BufSize, Value); check(result.ec == std::errc()); Out.Append(Buf); } template requires (std::is_floating_point_v) void Print(FUtf8Builder& Out, T Value) { constexpr size_t BufSize = 32; char Buf[BufSize]{}; std::to_chars_result result = std::to_chars(Buf, Buf + BufSize, Value, std::chars_format::general); check(result.ec == std::errc()); Out.Append(Buf); } template<> void Print(FUtf8Builder& Out, bool Value) { Out.Append(Value ? GLiterals.True : GLiterals.False); } template<> void Print(FUtf8Builder& Out, char8_t Value) { UTF8CHAR Char = static_cast(Value); if (EscapeSet.Contains(Char)) { EscapeChar(Out, Char); } else { Out.AppendChar(Char); } } template<> void Print(FUtf8Builder& Out, char16_t Value) { if (Value <= 127) { Print(Out, static_cast(Value)); } else { Out.Append(FUtf8StringView(StringCast(reinterpret_cast(&Value), 1))); } } template<> void Print(FUtf8Builder& Out, char32_t Value) { if (Value <= 127) { Print(Out, static_cast(Value)); } else { Out.Append(FUtf8StringView(StringCast(reinterpret_cast(&Value), 1))); } } /////////////////////////////////////////////////////////////////////////////// static void PrintRangeType(FUtf8Builder& Out, FRangeType Type) { Out.AppendChar('('); Out.Append(ToString(Type.MaxSize)); Out.AppendChar(')'); } inline void PrintStructFlags(FUtf8Builder& Out, FStructType StructType) { if (StructType.IsDynamic) { Out.Append(GLiterals.Dynamic); } } void PrintSchema(FUtf8Builder& Out, const FBatchIds& Ids, FStructType StructType, FStructSchemaId Id) { PrintStructFlags(Out, StructType); Ids.AppendString(Out, Id); } template void PrintSchema(FUtf8Builder& Out, const IdsType& Ids, FStructType StructType, OptionalStructId Id) { PrintStructFlags(Out, StructType); if (Id) { Ids.AppendString(Out, Id.Get()); } } template void PrintSchema(FUtf8Builder& Out, const IdsType& Ids, FUnpackedLeafType Leaf, OptionalEnumId Id) { if (Id) { Ids.AppendString(Out, Id.Get()); } else { Out.Append(ToString(Leaf)); } } template void PrintInnermostSchema(FUtf8Builder& Out, const IdsType& Ids, FMemberType InnermostType, OptionalId InnerSchema) { if (InnermostType.IsStruct()) { PrintSchema(Out, Ids, InnermostType.AsStruct(), ToOptionalStruct(InnerSchema)); } else { PrintSchema(Out, Ids, InnermostType.AsLeaf(), ToOptionalEnum(InnerSchema)); } } static void PrintSchema(FUtf8Builder& Out, const FBatchIds& Ids, FRangeType Type, FRangeSchema Schema) { PrintInnermostSchema(Out, Ids, GetInnermostType(Schema), Schema.InnermostSchema); PrintRangeType(Out, Type); FMemberType Inner = Schema.ItemType; for (const FMemberType* It = Schema.NestedItemTypes; Inner.IsRange(); Inner = *It++) { PrintRangeType(Out, Inner.AsRange()); } } void PrintMemberSchema(FUtf8Builder& Out, const FIds& Ids, FMemberSchema Member) { PrintInnermostSchema(Out, Ids, Member.GetInnermostType(), Member.InnerSchema); if (Member.Type.IsRange()) { PrintRangeType(Out, Member.Type.AsRange()); for (FMemberType Inner : Member.GetInnerRangeTypes()) { PrintRangeType(Out, Inner.AsRange()); } } } /////////////////////////////////////////////////////////////////////////////// struct FMemberSchemaView { using FMemberTypeRange = TConstArrayView; FMemberType Type; FSchemaBatchId Batch; FOptionalSchemaId InnerSchema; FMemberTypeRange InnerRangeTypes; FMemberType GetInnermostType() const { return InnerRangeTypes.Num() > 0 ? InnerRangeTypes.Last() : Type; } FRangeSchema AsRangeSchema() const { check(Type.IsRange()); return { InnerRangeTypes[0], Batch, InnerSchema, InnerRangeTypes.Num() > 1 ? &InnerRangeTypes[1] : nullptr }; } }; /////////////////////////////////////////////////////////////////////////////// class FStructSchemaReader { public: FStructSchemaReader(const FStructSchema& Schema, FSchemaBatchId InBatch); FType GetStruct() const { return Struct; } bool IsDense() const { return bIsDense; } bool HasSuper() const { return bHasSuper; } uint16 GetVersion() const { return Version; } bool HasMore() const { return MemberIdx < NumMembers; } FOptionalMemberId PeekName() const; // @pre HasMore() EMemberKind PeekKind() const; // @pre HasMore() FMemberType PeekType() const; // @pre HasMore() FStructSchemaHandle GetSuper() const; // @pre HasSuper() FMemberSchemaView GrabMember(); // @pre HasMore() private: const FMemberType* Footer; const FSchemaBatchId Batch; // Needed to resolve schemas const FType Struct; const bool bIsDense : 1; const bool bHasSuper : 1; const bool bUsesSuper : 1; const uint16 Version; const uint32 NumMembers; const uint32 NumRangeTypes; // Number of ranges and nested ranges const uint32 NumInnerSchemas; // Number of static structs and enums uint32 MemberIdx = 0; uint32 RangeTypeIdx = 0; // Types of [nested] ranges uint32 InnerSchemaIdx = 0; // Types of static structs and enums void AdvanceToNextMember() { ++MemberIdx; } using FMemberTypeRange = TConstArrayView; FMemberTypeRange GrabRangeTypes(); FSchemaId GrabInnerSchema(); FOptionalSchemaId GrabLeafSchema(FLeafType Leaf); FOptionalSchemaId GrabStructSchema(FStructType Struct); FOptionalSchemaId GrabRangeSchema(FMemberType InnermostType); const FMemberType* GetMemberTypes() const; const FMemberType* GetRangeTypes() const; const FSchemaId* GetInnerSchemas() const; const FMemberId* GetMemberNames() const; }; /////////////////////////////////////////////////////////////////////////////// class FYamlBuilder { public: FYamlBuilder(FUtf8Builder& InStringBuilder); ~FYamlBuilder(); void BeginDocument(); void EndDocument(); void BeginStruct(FUtf8StringView Id); void BeginStruct(); void EndStruct(); void BeginRange(FUtf8StringView Id); void BeginRange(); void EndRange(); void AddLeafId(FUtf8StringView Id); template void AddLeafValue(T Value); template void AddLeaf(FUtf8StringView Id, T Value) { AddLeafId(Id); AddLeafValue(Value); } template void AddLeaf(T Value); void AddComment(FUtf8StringView Comment); private: void AppendNewLine(); void AppendIndentation(); void AppendIdentifier(FUtf8StringView Id); template void AppendValue(T Value); template<> void AppendValue(FUtf8StringView Value); struct FStackInfo { bool IsEmpty = true; bool IsInStruct = true; }; FUtf8Builder& Text; TArray> Stack; uint32 IndentLevel = 0; bool IsNewLine = true; }; /////////////////////////////////////////////////////////////////////////////// class FMemberPrinter { public: FMemberPrinter(FYamlBuilder& InTextBuilder, const FBatchIds& InIds) : TextBuilder(InTextBuilder) , Ids(InIds) {} void PrintMembers(FStructView StructView); private: void PrintLeaf(FMemberId Id, FLeafView LeafView); void PrintStruct(FOptionalMemberId Id, FStructType StructType, FStructView StructView); void PrintRange(FMemberId Id, FRangeType RangeType, const FRangeView& RangeView); void PrintLeaves(FUnpackedLeafType Leaf, const FLeafRangeView& LeafRange); void PrintStructs(FStructType StructType, const FStructRangeView& StructRange); void PrintRanges(FRangeType RangeType, const FNestedRangeView& NestedRange); void PrintMembersInternal(FStructType StructType, FStructView StructView, const FStructSchema& Schema); void PrintRangeInternal(FRangeType RangeType, const FRangeView& RangeView); bool IsUnicodeString(const FRangeView& RangeView); void PrintUnicodeLeafValue(FLeafView LeafView); void PrintUnicodeRangeAsLeaf(FOptionalMemberId Id, FRangeType RangeType, const FRangeView& RangeView); template void PrintSchemaComment(MemberType Type, SchemaType Schema) { if constexpr (PrintWithComments) { PrintSchema(/* out */ Tmp, Ids, Type, Schema); TextBuilder.AddComment(Tmp); Tmp.Reset(); } } FYamlBuilder& TextBuilder; const FBatchIds& Ids; TUtf8StringBuilder<256> Tmp; }; /////////////////////////////////////////////////////////////////////////////// void PrintYamlBatch(FUtf8Builder& Out, const FBatchIds& Ids, TConstArrayView Objects) { FYamlBuilder YamlBuilder(Out); FBatchPrinter Printer(YamlBuilder, Ids); YamlBuilder.BeginDocument(); Printer.PrintSchemas(); Printer.PrintObjects(Objects); YamlBuilder.EndDocument(); } /////////////////////////////////////////////////////////////////////////////// FStructSchemaReader::FStructSchemaReader(const FStructSchema& Schema, FSchemaBatchId InBatch) : Footer(Schema.Footer) , Batch(InBatch) , Struct(Schema.Type) , bIsDense(Schema.IsDense) , bHasSuper(Schema.Inheritance != ESuper::No) , bUsesSuper(UsesSuper(Schema.Inheritance)) , Version(Schema.Version) , NumMembers(Schema.NumMembers) , NumRangeTypes(Schema.NumRangeTypes) , NumInnerSchemas(Schema.NumInnerSchemas) , InnerSchemaIdx(SkipDeclaredSuperSchema(Schema.Inheritance)) { check(InnerSchemaIdx <= NumInnerSchemas); checkf(NumRangeTypes != 0xFFFFu, TEXT("GrabRangeTypes() doesn't check for wrap-around")); } FOptionalMemberId FStructSchemaReader::PeekName() const { int32 MemberNameIdx = MemberIdx - bUsesSuper; return MemberNameIdx >= 0 ? ToOptional(GetMemberNames()[MemberNameIdx]) : NoId; } EMemberKind FStructSchemaReader::PeekKind() const { return PeekType().GetKind(); } FMemberType FStructSchemaReader::PeekType() const { check(HasMore()); return GetMemberTypes()[MemberIdx]; } FStructSchemaHandle FStructSchemaReader::GetSuper() const { check(HasSuper()); check(NumInnerSchemas > 0); return { static_cast(GetInnerSchemas()[0]), Batch }; } FMemberSchemaView FStructSchemaReader::GrabMember() { check(HasMore()); FMemberType Type = PeekType(); FMemberSchemaView Out{ Type, Batch }; switch (PeekKind()) { case EMemberKind::Leaf: Out.InnerSchema = GrabLeafSchema(Type.AsLeaf()); break; case EMemberKind::Struct: Out.InnerSchema = GrabStructSchema(Type.AsStruct()); break; case EMemberKind::Range: Out.InnerRangeTypes = GrabRangeTypes(); Out.InnerSchema = GrabRangeSchema(Out.InnerRangeTypes.Last()); break; } AdvanceToNextMember(); return Out; } FStructSchemaReader::FMemberTypeRange FStructSchemaReader::GrabRangeTypes() { return GrabInnerRangeTypes(MakeArrayView(GetRangeTypes(), NumRangeTypes), /* in-out */ RangeTypeIdx); } FSchemaId FStructSchemaReader::GrabInnerSchema() { check(InnerSchemaIdx < NumInnerSchemas); uint32 Idx = InnerSchemaIdx++; return GetInnerSchemas()[Idx]; } FOptionalSchemaId FStructSchemaReader::GrabLeafSchema(FLeafType Member) { return Member.Type == ELeafType::Enum ? ToOptional(GrabInnerSchema()) : NoId; } FOptionalSchemaId FStructSchemaReader::GrabStructSchema(FStructType Member) { return Member.IsDynamic ? NoId : ToOptional(GrabInnerSchema()); } FOptionalSchemaId FStructSchemaReader::GrabRangeSchema(FMemberType InnermostType) { check(!InnermostType.IsRange()); return InnermostType.IsStruct() ? GrabStructSchema(InnermostType.AsStruct()) : GrabLeafSchema(InnermostType.AsLeaf()); } const FMemberType* FStructSchemaReader::GetMemberTypes() const { return FStructSchema::GetMemberTypes(Footer); } const FMemberType* FStructSchemaReader::GetRangeTypes() const { return FStructSchema::GetRangeTypes(Footer, NumMembers); } const FSchemaId* FStructSchemaReader::GetInnerSchemas() const { return FStructSchema::GetInnerSchemas(Footer, NumMembers, NumRangeTypes, NumMembers - bUsesSuper); } const FMemberId* FStructSchemaReader::GetMemberNames() const { return FStructSchema::GetMemberNames(Footer, NumMembers, NumRangeTypes); } /////////////////////////////////////////////////////////////////////////////// void FYamlBuilderDeleter::operator()(FYamlBuilder* YamlBuilder) const { delete YamlBuilder; } FYamlBuilderPtr MakeYamlBuilder(FUtf8Builder& StringBuilder) { return FYamlBuilderPtr(new FYamlBuilder(StringBuilder)); } /////////////////////////////////////////////////////////////////////////////// FYamlBuilder::FYamlBuilder(FUtf8Builder& InStringBuilder) : Text(InStringBuilder) { Stack.Emplace(); } FYamlBuilder::~FYamlBuilder() { Stack.Pop(EAllowShrinking::No); check(Stack.IsEmpty()); } void FYamlBuilder::BeginDocument() { Text << "---"; IsNewLine = false; AppendNewLine(); Stack.Emplace(); } void FYamlBuilder::EndDocument() { AppendNewLine(); Text << "..."; Stack.Pop(EAllowShrinking::No); } void FYamlBuilder::BeginStruct(FUtf8StringView Id) { AppendNewLine(); AppendIndentation(); AppendIdentifier(Id); Stack.Last().IsEmpty = false; Stack.Emplace(); ++IndentLevel; } void FYamlBuilder::BeginStruct() { AppendNewLine(); AppendIndentation(); Stack.Last().IsEmpty = false; Stack.Emplace(); ++IndentLevel; } void FYamlBuilder::EndStruct() { --IndentLevel; if (Stack.Last().IsEmpty) { Text << " {}"; IsNewLine = false; } Stack.Pop(EAllowShrinking::No); } void FYamlBuilder::BeginRange(FUtf8StringView Id) { AppendNewLine(); AppendIndentation(); AppendIdentifier(Id); Stack.Last().IsEmpty = false; Stack.Emplace_GetRef().IsInStruct = false; ++IndentLevel; } void FYamlBuilder::BeginRange() { AppendNewLine(); AppendIndentation(); Stack.Last().IsEmpty = false; Stack.Emplace_GetRef().IsInStruct = false; ++IndentLevel; } void FYamlBuilder::EndRange() { if (Stack.Last().IsEmpty) { Text << " []"; IsNewLine = false; } Stack.Pop(EAllowShrinking::No); --IndentLevel; } void FYamlBuilder::AddLeafId(FUtf8StringView Id) { AppendNewLine(); AppendIndentation(); AppendIdentifier(Id); IsNewLine = false; Stack.Last().IsEmpty = false; } template void FYamlBuilder::AddLeafValue(T Value) { Text.AppendChar(' '); AppendValue(Value); IsNewLine = false; Stack.Last().IsEmpty = false; } template void FYamlBuilder::AddLeaf(T Value) { AppendNewLine(); AppendIndentation(); AppendValue(Value); IsNewLine = false; Stack.Last().IsEmpty = false; } void FYamlBuilder::AddComment(FUtf8StringView Comment) { Text << " #" << Comment; AppendNewLine(); } void FYamlBuilder::AppendNewLine() { if (!IsNewLine) { Text << '\n'; IsNewLine = true; } } void FYamlBuilder::AppendIndentation() { for (uint32 I = 0; I < 2*IndentLevel; ++I) { Text.AppendChar(' '); } if (!Stack.Last().IsInStruct) { Text << "- "; } IsNewLine = false; } static void PrintQuotedString(FUtf8Builder& Out, FUtf8StringView Value) { FUtf8StringView Verbatim = FAsciiSet::FindPrefixWithout(Value, EscapeSet | "'"); if (Verbatim.Len() == Value.Len()) { Out << '\'' << Value << '\''; return; } Out << '\"'; while (!Value.IsEmpty()) { Out << Verbatim; Value.RightChopInline(Verbatim.Len()); FUtf8StringView Escape = FAsciiSet::FindPrefixWith(Value, EscapeSet); for (UTF8CHAR Char : Escape) { EscapeChar(Out, Char); } Value.RightChopInline(Escape.Len()); Verbatim = FAsciiSet::FindPrefixWithout(Value, EscapeSet); } Out << '\"'; } void FYamlBuilder::AppendIdentifier(FUtf8StringView Id) { PrintQuotedString(Text, Id); Text.AppendChar(':'); IsNewLine = false; } template void FYamlBuilder::AppendValue(T Value) { Text.AppendChar('\''); Print(Text, Value); Text.AppendChar('\''); } template<> void FYamlBuilder::AppendValue(FUtf8StringView Value) { PrintQuotedString(Text, Value); } /////////////////////////////////////////////////////////////////////////////// FBatchPrinter::FBatchPrinter(FYamlBuilder& InTextBuilder, const FBatchIds& InIds) : TextBuilder(InTextBuilder) , Ids(InIds) {} FBatchPrinter::~FBatchPrinter() {} void FBatchPrinter::PrintSchemas() { TextBuilder.BeginRange(GLiterals.Structs); for (const FStructSchema& Struct : GetStructSchemas(Ids.GetSchemas())) { PrintStructSchema(Struct, Ids.GetBatchId()); } TextBuilder.EndRange(); TextBuilder.BeginRange(GLiterals.Enums); for (const FEnumSchema& EnumSchema : GetEnumSchemas(Ids.GetSchemas())) { PrintEnumSchema(EnumSchema); } TextBuilder.EndRange(); } void FBatchPrinter::PrintObjects(TConstArrayView Objects) { TextBuilder.BeginRange(GLiterals.Objects); for (FStructView Object : Objects) { FMemberPrinter(TextBuilder, Ids).PrintMembers(Object); } TextBuilder.EndRange(); } static void PrintMemberSchema(FUtf8Builder& Out, const FBatchIds& Ids, const FMemberSchemaView& Schema) { switch (Schema.Type.GetKind()) { case EMemberKind::Leaf: PrintSchema(Out, Ids, Schema.Type.AsLeaf(), ToOptionalEnum(Schema.InnerSchema)); break; case EMemberKind::Range: PrintSchema(Out, Ids, Schema.Type.AsRange(), Schema.AsRangeSchema()); break; case EMemberKind::Struct: PrintSchema(Out, Ids, Schema.Type.AsStruct(), ToOptionalStruct(Schema.InnerSchema)); break; } } template class FPrintId { TUtf8StringBuilder Buffer; public: template FPrintId(const FBatchIds& Ids, T Id) { Ids.AppendString(Buffer, Id); } FUtf8StringView operator*() const { return Buffer.ToView(); } }; void FBatchPrinter::PrintStructSchema(const FStructSchema& Struct, FSchemaBatchId BatchId) { FStructSchemaReader Reader(Struct, BatchId); TextBuilder.BeginStruct(*FPrintId<128>(Ids, Reader.GetStruct())); if (uint16 Version = Reader.GetVersion()) { TextBuilder.AddLeaf(GLiterals.Version, Version); } if (Reader.HasSuper()) { const FStructSchema& SuperSchema = Reader.GetSuper().Resolve(); TextBuilder.AddLeaf(GLiterals.DeclaredSuper, *FPrintId<128>(Ids, SuperSchema.Type)); } TextBuilder.BeginRange(GLiterals.Members); TUtf8StringBuilder<256> Buf; while (Reader.HasMore()) { Ids.AppendString(Buf, Reader.PeekName()); TextBuilder.AddLeafId(Buf); Buf.Reset(); PrintMemberSchema(Buf, Ids, Reader.GrabMember()); TextBuilder.AddLeafValue(FUtf8StringView(Buf)); Buf.Reset(); } TextBuilder.EndRange(); TextBuilder.EndStruct(); } void FBatchPrinter::PrintEnumSchema(const FEnumSchema& Enum) { TextBuilder.BeginStruct(*FPrintId<128>(Ids, Enum.Type)); TextBuilder.AddLeaf(GLiterals.FlagMode, !!Enum.FlagMode); TextBuilder.AddLeaf(GLiterals.Width, Enum.Width); TextBuilder.BeginRange(GLiterals.Constants); TConstArrayView EnumNames = MakeConstArrayView(Enum.Footer, Enum.Num); switch (Enum.Width) { case ELeafWidth::B8: PrintEnumConstants(EnumNames, GetConstants(Enum), Enum.FlagMode); break; case ELeafWidth::B16: PrintEnumConstants(EnumNames, GetConstants(Enum), Enum.FlagMode); break; case ELeafWidth::B32: PrintEnumConstants(EnumNames, GetConstants(Enum), Enum.FlagMode); break; case ELeafWidth::B64: PrintEnumConstants(EnumNames, GetConstants(Enum), Enum.FlagMode); break; } TextBuilder.EndRange(); TextBuilder.EndStruct(); } template void FBatchPrinter::PrintEnumConstants( TConstArrayView EnumNames, TConstArrayView Constants, bool bFlagMode) { uint16 NamesNum = IntCastChecked(EnumNames.Num()); if (Constants.Num() > 0) { check(EnumNames.Num() == Constants.Num()); for (uint16 Idx = 0; Idx < NamesNum; ++Idx) { TextBuilder.AddLeaf(*FPrintId<128>(Ids, EnumNames[Idx]), (uint64)Constants[Idx]); } } else if (bFlagMode) { uint64 Value = 1; for (uint16 Idx = 0; Idx < NamesNum; ++Idx) { TextBuilder.AddLeaf(*FPrintId<128>(Ids, EnumNames[Idx]), Value); Value <<= 1; } } else { for (uint16 Idx = 0; Idx < NamesNum; ++Idx) { TextBuilder.AddLeaf(*FPrintId<128>(Ids, EnumNames[Idx]), (uint64)Idx); } } } /////////////////////////////////////////////////////////////////////////////// void FMemberPrinter::PrintMembers(FStructView StructView) { const FStructSchema& Schema = StructView.Schema.Resolve(); TextBuilder.BeginStruct(*FPrintId<128>(Ids, Schema.Type)); PrintMembersInternal({ EMemberKind::Struct }, StructView, Schema); } void FMemberPrinter::PrintLeaf(FMemberId Id, FLeafView LeafView) { TextBuilder.AddLeafId(*FPrintId<128>(Ids, Id)); switch (LeafView.Leaf.Type) { case ELeafType::Bool: TextBuilder.AddLeafValue(LeafView.AsBool()); break; case ELeafType::IntS: switch (LeafView.Leaf.Width) { case ELeafWidth::B8: TextBuilder.AddLeafValue(LeafView.AsS8()); break; case ELeafWidth::B16: TextBuilder.AddLeafValue(LeafView.AsS16()); break; case ELeafWidth::B32: TextBuilder.AddLeafValue(LeafView.AsS32()); break; case ELeafWidth::B64: TextBuilder.AddLeafValue(LeafView.AsS64()); break; } break; case ELeafType::IntU: switch (LeafView.Leaf.Width) { case ELeafWidth::B8: TextBuilder.AddLeafValue(LeafView.AsU8()); break; case ELeafWidth::B16: TextBuilder.AddLeafValue(LeafView.AsU16()); break; case ELeafWidth::B32: TextBuilder.AddLeafValue(LeafView.AsU32()); break; case ELeafWidth::B64: TextBuilder.AddLeafValue(LeafView.AsU64()); break; } break; case ELeafType::Float: if (LeafView.Leaf.Width == ELeafWidth::B32) { TextBuilder.AddLeafValue(LeafView.AsFloat()); } else { check(LeafView.Leaf.Width == ELeafWidth::B64); TextBuilder.AddLeafValue(LeafView.AsDouble()); } break; case ELeafType::Hex: check(LeafView.Leaf.Type != ELeafType::Hex); break; case ELeafType::Enum: switch (LeafView.Leaf.Width) { case ELeafWidth::B8: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue()); break; case ELeafWidth::B16: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue()); break; case ELeafWidth::B32: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue()); break; case ELeafWidth::B64: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue()); break; } break; case ELeafType::Unicode: switch (LeafView.Leaf.Width) { case ELeafWidth::B8: TextBuilder.AddLeafValue(LeafView.AsChar8()); break; case ELeafWidth::B16: TextBuilder.AddLeafValue(LeafView.AsChar16()); break; case ELeafWidth::B32: TextBuilder.AddLeafValue(LeafView.AsChar32()); break; case ELeafWidth::B64: check(false); break; }; } PrintSchemaComment(LeafView.Leaf, LeafView.Enum); } void FMemberPrinter::PrintStruct(FOptionalMemberId MemberId, FStructType StructType, FStructView StructView) { TextBuilder.BeginStruct(*FPrintId<128>(Ids, MemberId)); PrintMembersInternal(StructType, StructView, StructView.Schema.Resolve()); } void FMemberPrinter::PrintRange(FMemberId Id, FRangeType RangeType, const FRangeView& RangeView) { if (IsUnicodeString(RangeView)) { PrintUnicodeRangeAsLeaf(Id, RangeType, RangeView); } else { TextBuilder.BeginRange(*FPrintId<128>(Ids, Id)); PrintRangeInternal(RangeType, RangeView); } } void FMemberPrinter::PrintLeaves(FUnpackedLeafType Leaf, const FLeafRangeView& LeafRange) { switch (Leaf.Type) { case ELeafType::Bool: for (bool b : LeafRange.AsBools()) { TextBuilder.AddLeaf(b); } break; case ELeafType::IntS: switch (Leaf.Width) { case ELeafWidth::B8: for (const int8& I : LeafRange.AsS8s()) { TextBuilder.AddLeaf(I); } break; case ELeafWidth::B16: for (const int16& I : LeafRange.AsS16s()) { TextBuilder.AddLeaf(I); } break; case ELeafWidth::B32: for (const int32& I : LeafRange.AsS32s()) { TextBuilder.AddLeaf(I); } break; case ELeafWidth::B64: for (const int64& I : LeafRange.AsS64s()) { TextBuilder.AddLeaf(I); } break; } break; case ELeafType::IntU: switch (Leaf.Width) { case ELeafWidth::B8: for (const uint8& U : LeafRange.AsU8s()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B16: for (const uint16& U : LeafRange.AsU16s()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B32: for (const uint32& U : LeafRange.AsU32s()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B64: for (const uint64& U : LeafRange.AsU64s()) { TextBuilder.AddLeaf(U); } break; } break; case ELeafType::Float: if (Leaf.Width == ELeafWidth::B32) { for (const float& f : LeafRange.AsFloats()) { TextBuilder.AddLeaf(f); } } else { check(Leaf.Width == ELeafWidth::B64); for (const double& d : LeafRange.AsDoubles()) { TextBuilder.AddLeaf(d); } } break; case ELeafType::Hex: // PP-TEXT: Implement AddLeaf(Hex) check(Leaf.Type != ELeafType::Hex); break; case ELeafType::Enum: switch (Leaf.Width) { case ELeafWidth::B8: for (const uint8& U : LeafRange.AsUnderlyingValues()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B16: for (const uint16& U : LeafRange.AsUnderlyingValues()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B32: for (const uint32& U : LeafRange.AsUnderlyingValues()) { TextBuilder.AddLeaf(U); } break; case ELeafWidth::B64: for (const uint64& U : LeafRange.AsUnderlyingValues()) { TextBuilder.AddLeaf(U); } break; } break; case ELeafType::Unicode: checkf(LeafRange.Num() == 0, TEXT("Should have been handled by PrintUnicodeRangeAsLeaf")); break; } } void FMemberPrinter::PrintStructs(FStructType StructType, const FStructRangeView& StructRange) { for (FStructView StructView : StructRange) { TextBuilder.BeginStruct(); PrintMembersInternal(StructType, StructView, StructView.Schema.Resolve()); } } void FMemberPrinter::PrintRanges(FRangeType RangeType, const FNestedRangeView& NestedRange) { for (FRangeView RangeView : NestedRange) { if (IsUnicodeString(RangeView)) { PrintUnicodeRangeAsLeaf(NoId, RangeType, RangeView); } else { TextBuilder.BeginRange(); PrintRangeInternal(RangeType, RangeView); } } } void FMemberPrinter::PrintMembersInternal(FStructType StructType, FStructView StructView, const FStructSchema& Schema) { FMemberReader It(Schema, StructView.Values, StructView.Schema.Batch); const bool HasMembers = StructType.IsDynamic || It.HasMore(); if (HasMembers) { PrintSchemaComment(StructType, StructView.Schema.Id); } if (StructType.IsDynamic) { TextBuilder.AddLeaf(GLiterals.Dynamic, *FPrintId<128>(Ids, Schema.Type)); } while (It.HasMore()) { FOptionalMemberId Id = It.PeekName(); FMemberType Type = It.PeekType(); switch (Type.GetKind()) { case EMemberKind::Leaf: PrintLeaf(Id.Get(), It.GrabLeaf()); break; case EMemberKind::Struct: PrintStruct(Id, Type.AsStruct(), It.GrabStruct()); break; case EMemberKind::Range: PrintRange(Id.Get(), Type.AsRange(), It.GrabRange()); break; } } TextBuilder.EndStruct(); if (!HasMembers) { PrintSchemaComment(StructType, StructView.Schema.Id); } } void FMemberPrinter::PrintRangeInternal(FRangeType RangeType, const FRangeView& RangeView) { const FRangeSchema& Schema = RangeView.Schema; if (RangeView.Num() > 0) { PrintSchemaComment(RangeType, Schema); } switch (Schema.ItemType.GetKind()) { case EMemberKind::Leaf: PrintLeaves(Schema.ItemType.AsLeaf(), RangeView.AsLeaves()); break; case EMemberKind::Struct: PrintStructs(Schema.ItemType.AsStruct(), RangeView.AsStructs()); break; case EMemberKind::Range: PrintRanges(Schema.ItemType.AsRange(), RangeView.AsRanges()); break; } TextBuilder.EndRange(); if (RangeView.Num() == 0) { PrintSchemaComment(RangeType, Schema); } } bool FMemberPrinter::IsUnicodeString(const FRangeView& RangeView) { FMemberType Type = RangeView.Schema.ItemType; return RangeView.Num() > 0 && Type.IsLeaf() && Type.AsLeaf().Type == ELeafType::Unicode; } template static void AddUnicodeRangeLeaf(FYamlBuilder& TextBuilder, FOptionalMemberId Id, TRangeView Range) { check(Range.Num() > 0); const CharType* Src = reinterpret_cast(Range.begin()); const int32 SrcLen = IntCastChecked(Range.Num()); const int32 DstLen = FPlatformString::ConvertedLength(Src, SrcLen); TArray> Buf; Buf.Reserve(DstLen); UTF8CHAR* Dst = Buf.GetData(); const UTF8CHAR* DstEnd = FPlatformString::Convert(Dst, DstLen, Src, SrcLen); check(DstEnd); check(DstEnd - Dst == DstLen); if (Id) { TextBuilder.AddLeafValue(FUtf8StringView(Dst, DstLen)); } else { TextBuilder.AddLeaf(FUtf8StringView(Dst, DstLen)); } } void FMemberPrinter::PrintUnicodeRangeAsLeaf(FOptionalMemberId Id, FRangeType RangeType, const FRangeView& RangeView) { check(IsUnicodeString(RangeView)); if (Id) { TextBuilder.AddLeafId(*FPrintId<128>(Ids, Id)); } const FLeafRangeView LeafRange = RangeView.AsLeaves(); const FUnpackedLeafType Leaf = RangeView.Schema.ItemType.AsLeaf(); switch (Leaf.Width) { case ELeafWidth::B8: AddUnicodeRangeLeaf(TextBuilder, Id, LeafRange.AsUtf8()); break; case ELeafWidth::B16: AddUnicodeRangeLeaf(TextBuilder, Id, LeafRange.AsUtf16()); break; case ELeafWidth::B32: AddUnicodeRangeLeaf(TextBuilder, Id, LeafRange.AsUtf32()); break; case ELeafWidth::B64: check(false); break; }; PrintSchemaComment(FRangeType(RangeType), RangeView.Schema); } /////////////////////////////////////////////////////////////////////////////// void FIdsBase::AppendString(FUtf8Builder& Out, FMemberId Name) const { AppendString(Out, Name.Id); } void FIdsBase::AppendString(FUtf8Builder& Out, FOptionalMemberId Name) const { if (Name) { AppendString(Out, Name.Get().Id); } else { Out.Append(GLiterals.Super); } } void FIdsBase::AppendString(FUtf8Builder& Out, FScopeId Scope) const { if (Scope.IsFlat()) { AppendString(Out, Scope.AsFlat().Name); } else if (Scope) { FNestedScope Nested = Resolve(Scope.AsNested()); AppendString(Out, Nested.Outer); Out.AppendChar('.'); AppendString(Out, Nested.Inner.Name); } } void FIdsBase::AppendString(FUtf8Builder& Out, FTypenameId Typename) const { if (Typename.IsConcrete()) { AppendString(Out, Typename.AsConcrete().Id); } else { FParametricTypeView ParametricType = Resolve(Typename.AsParametric()); TConstArrayView Parameters = ParametricType.GetParameters(); if (ParametricType.Name) { AppendString(Out, ParametricType.Name.Get().Id); } Out.AppendChar(ParametricType.Name ? '<' : '['); for (FType Parameter : Parameters.LeftChop(1)) { AppendString(Out, Parameter); Out.AppendChar(','); } if (Parameters.Num() > 0) { AppendString(Out, Parameters.Last()); } Out.AppendChar(ParametricType.Name ? '>' : ']'); } } void FIdsBase::AppendString(FUtf8Builder& Out, FType Type) const { if (Type.Scope) { AppendString(Out, Type.Scope); Out.AppendChar('.'); } AppendString(Out, Type.Name); } void FIds::AppendString(FUtf8Builder& Out, FEnumId Name) const { AppendString(Out, Resolve(Name)); } void FIds::AppendString(FUtf8Builder& Out, FStructId Name) const { AppendString(Out, Resolve(Name)); } void FBatchIds::AppendString(FUtf8Builder& Out, FEnumSchemaId Name) const { AppendString(Out, Resolve(Name)); } void FBatchIds::AppendString(FUtf8Builder& Out, FStructSchemaId Name) const { AppendString(Out, Resolve(Name)); } //////////////////////////////////////////////////////////////////////////////// FString FDebugIds::Print(FNameId Name) const { TUtf8StringBuilder<128> Out; if (Name.Idx < Ids.NumNames()) { Ids.AppendString(Out, Name); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } FString FDebugIds::Print(FMemberId Name) const { TUtf8StringBuilder<128> Out; if (Name.Id.Idx < Ids.NumNames()) { Ids.AppendString(Out, Name.Id); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } FString FDebugIds::Print(FOptionalMemberId Name) const { TUtf8StringBuilder<128> Out; if (!Name || Name.Get().Id.Idx < Ids.NumNames()) { Ids.AppendString(Out, Name); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } static const bool IsValidScope(FScopeId Scope, const FIds& Ids) { if (Scope.IsFlat()) { return Scope.AsFlat().Name.Idx < Ids.NumNames(); } else if (Scope) { return Scope.AsNested().Idx < Ids.NumNestedScopes(); } return !Scope; // Unscoped } FString FDebugIds::Print(FScopeId Scope) const { TUtf8StringBuilder<128> Out; if (IsValidScope(Scope, Ids)) { Ids.AppendString(Out, Scope); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } static const bool IsValidTypename(FTypenameId Typename, const FIds& Ids) { if (Typename.IsConcrete()) { return Typename.AsConcrete().Id.Idx < Ids.NumNames(); } return Typename.AsParametric().Idx < Ids.NumNames(); } FString FDebugIds::Print(FTypenameId Typename) const { TUtf8StringBuilder<128> Out; if (IsValidTypename(Typename, Ids)) { Ids.AppendString(Out, Typename); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } FString FDebugIds::Print(FConcreteTypenameId Typename) const { return Print(FTypenameId(Typename)); } FString FDebugIds::Print(FParametricTypeId Typename) const { return Print(FTypenameId(Typename)); } FString FDebugIds::Print(FType Type) const { TUtf8StringBuilder<128> Out; if (IsValidScope(Type.Scope, Ids) && IsValidTypename(Type.Name, Ids)) { Ids.AppendString(Out, Type); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } FString FDebugIds::Print(FEnumId Name) const { TUtf8StringBuilder<128> Out; if (Name.Idx < Ids.NumEnums()) { Ids.AppendString(Out, Name); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } FString FDebugIds::Print(FStructId Name) const { TUtf8StringBuilder<128> Out; if (Name.Idx < Ids.NumStructs()) { Ids.AppendString(Out, Name); } else { Out << GLiterals.Oob; } return FString(FStringView(StringCast(Out.GetData(), Out.Len()))); } /////////////////////////////////////////////////////////////////////////////// void PrintDiff(FUtf8Builder& Out, const FIds& Ids, const FDiffPath& Diff) { check(Diff.Num()); for (FDiffNode Node : ReverseIterate(Diff)) { Ids.AppendString(Out, Node.Name); Out << '.'; } Out.RemoveSuffix(1); Out << ' '; Out << '('; for (FDiffNode Node : ReverseIterate(Diff)) { if (Node.Type.IsStruct()) { Ids.AppendString(Out, Ids.Resolve(Node.Meta.Struct).Name); } else if (Node.Type.IsRange()) { Ids.AppendString(Out, FTypenameId(Node.Meta.Range.GetBindName())); } else if (FOptionalEnumId Enum = Node.Meta.Leaf) { Ids.AppendString(Out, Ids.Resolve(Enum.Get()).Name); } else { Out << ToString(ToLeafType(Node.Type.AsLeaf())); } Out << ' '; } Out.RemoveSuffix(1); Out << ')'; } void PrintDiff(FUtf8Builder& Out, const FBatchIds& Ids, const FReadDiffPath& Diff) { check(Diff.Num()); bool bWasName = false; // print type name for the outermost struct if (Diff.Last().Struct) { Ids.AppendString(Out, Ids.Resolve(Diff.Last().Struct.Get()).Name); bWasName = true; } // print struct members path with range indices for (FReadDiffNode Node : ReverseIterate(Diff)) { if (Node.Name || Node.RangeIdx == ~0u) { if (bWasName) { Out << '.'; } if (!Node.Name) { Out << GLiterals.Super; } else { Ids.AppendString(Out, Node.Name); } bWasName = true; } else if (Node.Type.IsRange()) { Out << "[" << Node.RangeIdx << "]"; bWasName = false; } } } } // namespace PlainProps