Files
UnrealEngine/Engine/Plugins/Experimental/PlainProps/Source/Private/PlainPropsPrint.cpp
2025-05-18 13:04:45 +08:00

1511 lines
37 KiB
C++

// 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 <charconv>
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<Enumeration T>
void Print(FUtf8Builder& Out, T Value)
{
Out.Append(ToString(Value));
}
template<Arithmetic T> requires (std::is_integral_v<T>)
void Print(FUtf8Builder& Out, T Value)
{
constexpr size_t BufSize = std::numeric_limits<T>::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<Arithmetic T> requires (std::is_floating_point_v<T>)
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<UTF8CHAR>(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<char8_t>(Value));
}
else
{
Out.Append(FUtf8StringView(StringCast<UTF8CHAR>(reinterpret_cast<UTF16CHAR*>(&Value), 1)));
}
}
template<>
void Print(FUtf8Builder& Out, char32_t Value)
{
if (Value <= 127)
{
Print(Out, static_cast<char8_t>(Value));
}
else
{
Out.Append(FUtf8StringView(StringCast<UTF8CHAR>(reinterpret_cast<UTF32CHAR*>(&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<typename IdsType, typename OptionalStructId>
void PrintSchema(FUtf8Builder& Out, const IdsType& Ids, FStructType StructType, OptionalStructId Id)
{
PrintStructFlags(Out, StructType);
if (Id)
{
Ids.AppendString(Out, Id.Get());
}
}
template<typename IdsType, typename OptionalEnumId>
void PrintSchema(FUtf8Builder& Out, const IdsType& Ids, FUnpackedLeafType Leaf, OptionalEnumId Id)
{
if (Id)
{
Ids.AppendString(Out, Id.Get());
}
else
{
Out.Append(ToString(Leaf));
}
}
template<typename IdsType, typename OptionalId>
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>;
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<FMemberType>;
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<typename T>
void AddLeafValue(T Value);
template<typename T>
void AddLeaf(FUtf8StringView Id, T Value) { AddLeafId(Id); AddLeafValue(Value); }
template<typename T>
void AddLeaf(T Value);
void AddComment(FUtf8StringView Comment);
private:
void AppendNewLine();
void AppendIndentation();
void AppendIdentifier(FUtf8StringView Id);
template<typename T>
void AppendValue(T Value);
template<>
void AppendValue(FUtf8StringView Value);
struct FStackInfo
{
bool IsEmpty = true;
bool IsInStruct = true;
};
FUtf8Builder& Text;
TArray<FStackInfo, TInlineAllocator<32>> 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<typename MemberType, typename SchemaType>
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<FStructView> 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<FStructSchemaId>(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<typename T>
void FYamlBuilder::AddLeafValue(T Value)
{
Text.AppendChar(' ');
AppendValue(Value);
IsNewLine = false;
Stack.Last().IsEmpty = false;
}
template<typename T>
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<typename T>
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<FStructView> 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 <int32 BufferSize>
class FPrintId
{
TUtf8StringBuilder<BufferSize> Buffer;
public:
template <typename T>
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<FNameId> EnumNames = MakeConstArrayView(Enum.Footer, Enum.Num);
switch (Enum.Width)
{
case ELeafWidth::B8:
PrintEnumConstants(EnumNames, GetConstants<uint8>(Enum), Enum.FlagMode);
break;
case ELeafWidth::B16:
PrintEnumConstants(EnumNames, GetConstants<uint16>(Enum), Enum.FlagMode);
break;
case ELeafWidth::B32:
PrintEnumConstants(EnumNames, GetConstants<uint32>(Enum), Enum.FlagMode);
break;
case ELeafWidth::B64:
PrintEnumConstants(EnumNames, GetConstants<uint64>(Enum), Enum.FlagMode);
break;
}
TextBuilder.EndRange();
TextBuilder.EndStruct();
}
template<typename IntType>
void FBatchPrinter::PrintEnumConstants(
TConstArrayView<FNameId> EnumNames,
TConstArrayView<IntType> Constants,
bool bFlagMode)
{
uint16 NamesNum = IntCastChecked<uint16>(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<uint8>()); break;
case ELeafWidth::B16: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue<uint16>()); break;
case ELeafWidth::B32: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue<uint32>()); break;
case ELeafWidth::B64: TextBuilder.AddLeafValue(LeafView.AsUnderlyingValue<uint64>()); 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<uint8>()) { TextBuilder.AddLeaf(U); } break;
case ELeafWidth::B16: for (const uint16& U : LeafRange.AsUnderlyingValues<uint16>()) { TextBuilder.AddLeaf(U); } break;
case ELeafWidth::B32: for (const uint32& U : LeafRange.AsUnderlyingValues<uint32>()) { TextBuilder.AddLeaf(U); } break;
case ELeafWidth::B64: for (const uint64& U : LeafRange.AsUnderlyingValues<uint64>()) { 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 <typename CharType, typename RangeCharType>
static void AddUnicodeRangeLeaf(FYamlBuilder& TextBuilder, FOptionalMemberId Id, TRangeView<RangeCharType> Range)
{
check(Range.Num() > 0);
const CharType* Src = reinterpret_cast<const CharType*>(Range.begin());
const int32 SrcLen = IntCastChecked<int32>(Range.Num());
const int32 DstLen = FPlatformString::ConvertedLength<UTF8CHAR>(Src, SrcLen);
TArray<UTF8CHAR, TInlineAllocator<1024>> 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<UTF8CHAR >(TextBuilder, Id, LeafRange.AsUtf8()); break;
case ELeafWidth::B16: AddUnicodeRangeLeaf<UTF16CHAR>(TextBuilder, Id, LeafRange.AsUtf16()); break;
case ELeafWidth::B32: AddUnicodeRangeLeaf<UTF32CHAR>(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<FType> 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<TCHAR>(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<TCHAR>(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<TCHAR>(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<TCHAR>(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<TCHAR>(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<TCHAR>(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<TCHAR>(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<TCHAR>(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