diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -22,6 +22,7 @@ #include "llvm/Support/SMLoc.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YamlSerializer.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -123,11 +124,6 @@ // static void bitset(IO &io, T &value); }; -/// Describe which type of quotes should be used when quoting is necessary. -/// Some non-printable characters need to be double-quoted, while some others -/// are fine with simple-quoting, and some don't need any quoting. -enum class QuotingType { None, Single, Double }; - /// This class should be specialized by type that requires custom conversion /// to/from a yaml scalar. For example: /// @@ -667,82 +663,6 @@ S.equals("false") || S.equals("False") || S.equals("FALSE"); } -// 5.1. Character Set -// The allowed character range explicitly excludes the C0 control block #x0-#x1F -// (except for TAB #x9, LF #xA, and CR #xD which are allowed), DEL #x7F, the C1 -// control block #x80-#x9F (except for NEL #x85 which is allowed), the surrogate -// block #xD800-#xDFFF, #xFFFE, and #xFFFF. -inline QuotingType needsQuotes(StringRef S) { - if (S.empty()) - return QuotingType::Single; - - QuotingType MaxQuotingNeeded = QuotingType::None; - if (isSpace(static_cast(S.front())) || - isSpace(static_cast(S.back()))) - MaxQuotingNeeded = QuotingType::Single; - if (isNull(S)) - MaxQuotingNeeded = QuotingType::Single; - if (isBool(S)) - MaxQuotingNeeded = QuotingType::Single; - if (isNumeric(S)) - MaxQuotingNeeded = QuotingType::Single; - - // 7.3.3 Plain Style - // Plain scalars must not begin with most indicators, as this would cause - // ambiguity with other YAML constructs. - if (std::strchr(R"(-?:\,[]{}#&*!|>'"%@`)", S[0]) != nullptr) - MaxQuotingNeeded = QuotingType::Single; - - for (unsigned char C : S) { - // Alphanum is safe. - if (isAlnum(C)) - continue; - - switch (C) { - // Safe scalar characters. - case '_': - case '-': - case '^': - case '.': - case ',': - case ' ': - // TAB (0x9) is allowed in unquoted strings. - case 0x9: - continue; - // LF(0xA) and CR(0xD) may delimit values and so require at least single - // quotes. LLVM YAML parser cannot handle single quoted multiline so use - // double quoting to produce valid YAML. - case 0xA: - case 0xD: - return QuotingType::Double; - // DEL (0x7F) are excluded from the allowed character range. - case 0x7F: - return QuotingType::Double; - // Forward slash is allowed to be unquoted, but we quote it anyway. We have - // many tests that use FileCheck against YAML output, and this output often - // contains paths. If we quote backslashes but not forward slashes then - // paths will come out either quoted or unquoted depending on which platform - // the test is run on, making FileCheck comparisons difficult. - case '/': - default: { - // C0 control block (0x0 - 0x1F) is excluded from the allowed character - // range. - if (C <= 0x1F) - return QuotingType::Double; - - // Always double quote UTF-8. - if ((C & 0x80) != 0) - return QuotingType::Double; - - // The character is not safe, at least simple quoting needed. - MaxQuotingNeeded = QuotingType::Single; - } - } - } - - return MaxQuotingNeeded; -} - template struct missingTraits : public std::integral_constant StateStack; - int Column = 0; - int ColumnAtFlowStart = 0; - int ColumnAtMapFlowStart = 0; - bool NeedBitValueComma = false; - bool NeedFlowSequenceComma = false; + OutputStream Out; bool EnumerationMatchFound = false; bool WriteDefaultValues = false; - StringRef Padding; - StringRef PaddingBeforeContainer; }; template diff --git a/llvm/include/llvm/Support/YamlSerializer.h b/llvm/include/llvm/Support/YamlSerializer.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/YamlSerializer.h @@ -0,0 +1,460 @@ +//===- YAMLParser.h - Simple YAML parser ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_YAMLSERIALIZER_H +#define LLVM_SUPPORT_YAMLSERIALIZER_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include +#include + +namespace llvm { +class raw_ostream; + +namespace yaml { + +/// Describe which type of quotes should be used when quoting is necessary. +/// Some non-printable characters need to be double-quoted, while some others +/// are fine with simple-quoting, and some don't need any quoting. +enum class QuotingType { None, Single, Double }; + +// 5.1. Character Set +// The allowed character range explicitly excludes the C0 control block #x0-#x1F +// (except for TAB #x9, LF #xA, and CR #xD which are allowed), DEL #x7F, the C1 +// control block #x80-#x9F (except for NEL #x85 which is allowed), the surrogate +// block #xD800-#xDFFF, #xFFFE, and #xFFFF. +QuotingType needsQuotes(StringRef S); + +class OutputStream { + // Friend the yaml::Output so it can access the private enter/exit methods. + // Other consumers should use the Builder API as its much less error-prone + friend class Output; + +public: + template struct Serializer { + // static StringRef serialize(const T&); + }; + + template + struct IsArrayLike : public std::false_type {}; + + template + struct IsStringMapLike : public std::false_type {}; + + template struct MapAccessor { + // static StringRef getKey(const MapElement&); + // static const TYPE &getValue(const MapElement&); + }; + +private: + template struct MakeVoid { + typedef void type; + }; + template using void_t = typename MakeVoid::type; + + template + struct HasSerializer : public std::false_type {}; + template + struct HasSerializer::serialize)>> + : public std::true_type {}; + + template + struct HasMapAccessor : public std::false_type {}; + + template + struct HasMapAccessor::getKey)>, + void_t::getValue)>> + : public std::true_type {}; + + template class EmitterBase { + std::string Anchor; + + protected: + EmitterBase(OutputStream &Out) : Out(&Out) {} + ~EmitterBase() { assert(Anchor.empty() && "Last anchor not emitted"); } + OutputStream *Out; + + template void emitSequence(const T &Values, bool Flow) { + assert(Out); + auto Seq = Out->buildSequence(Flow); + for (const auto &Item : Values) + Seq.emit(Item); + } + + template void emitMap(const T &Map, bool Flow) { + assert(Out); + auto SubMap = Out->buildMap(Flow); + for (const auto &Item : Map) + SubMap.emit(MapAccessor::getKey(Item), + MapAccessor::getValue(Item)); + } + + void emitAnchor(bool AnchorAllowed) { + if (!Anchor.empty()) { + assert(AnchorAllowed && "Anchor cannot be emitted here"); + Out->outputAnchor(Anchor); + Anchor.clear(); + } + } + + public: + /// Adds an anchor tag to the next item outputted. + CRTP &anchor(StringRef Str) { + assert(Anchor.empty() && "Last anchor not emitted"); + Anchor.assign(Str.begin(), Str.size()); + return *static_cast(this); + } + }; + +public: + OutputStream(raw_ostream &Underlying, int WrapColumn = 70); + ~OutputStream(); + class LLVM_NODISCARD MapBuilder : public EmitterBase { + friend class OutputStream; + MapBuilder(OutputStream &Out); + + class LLVM_NODISCARD EmitSingleCtx { + MapBuilder &Parent; + + public: + EmitSingleCtx(MapBuilder &Parent, StringRef Key, + bool AnchorAllowed = true) + : Parent(Parent) { + assert(Parent.Out && "Map already disposed"); + Parent.Out->emitMapKey(Key); + Parent.emitAnchor(AnchorAllowed); + } + ~EmitSingleCtx() { Parent.Out->advanceMap(); } + }; + + public: + ~MapBuilder(); + + MapBuilder &emit(StringRef Key, + llvm::function_ref BuildValue); + + template + MapBuilder &emit(StringRef Key, const llvm::Optional &Value, + ExtraArgs &&...Args) { + if (Value) + emit(Key, *Value, std::forward(Args)...); + return *this; + } + + template + std::enable_if_t::value, MapBuilder &> + emit(StringRef Key, const T &Value) { + EmitSingleCtx RAII(*this, Key); + Out->outputScalar(Serializer::serialize(Value), QuotingType::None); + return *this; + } + + template + std::enable_if_t::value, MapBuilder &> + emit(StringRef Key, const T &Values, bool EmitEmpty, bool Flow) { + if (!Values.empty() || EmitEmpty) { + EmitSingleCtx RAII(*this, Key); + emitSequence(Values, Flow); + } + return *this; + } + + template + std::enable_if_t::value, MapBuilder &> + emit(StringRef Key, const T &Map, bool EmitEmpty, bool Flow) { + if (EmitEmpty || !Map.empty()) { + EmitSingleCtx RAII(*this, Key); + emitMap(Map, Flow); + } + return *this; + } + + MapBuilder &emit(StringRef Key, StringRef Value); + + MapBuilder &emit(StringRef Key, StringRef Value, + QuotingType MustQuoteValue); + + MapBuilder &emitAlias(StringRef Key, StringRef Alias); + + void close(); + }; + + class LLVM_NODISCARD SequenceBuilder : public EmitterBase { + private: + friend class OutputStream; + SequenceBuilder(OutputStream &Out); + + class LLVM_NODISCARD EmitSingleCtx { + SequenceBuilder &Parent; + + public: + EmitSingleCtx(SequenceBuilder &Parent, bool AnchorAllowed = true) + : Parent(Parent) { + assert(Parent.Out && "Sequence already disposed"); + Parent.Out->preEmitSequence(); + Parent.emitAnchor(AnchorAllowed); + } + ~EmitSingleCtx() { Parent.Out->advanceSequence(); } + }; + + public: + ~SequenceBuilder(); + SequenceBuilder &emit(llvm::function_ref EmitValue); + + SequenceBuilder &emit(StringRef Item); + + template + std::enable_if_t::value, SequenceBuilder &> + emit(const T &Map, bool Flow) { + EmitSingleCtx RAII(*this); + emitMap(Map, Flow); + return *this; + } + + template + std::enable_if_t::value, MapBuilder &> emit(const T &Values, + bool Flow) { + EmitSingleCtx RAII(*this); + emitSequence(Values, Flow); + return *this; + } + + template + std::enable_if_t::value, SequenceBuilder &> + emit(const T &Value) { + EmitSingleCtx RAII(*this); + Out->outputScalar(Serializer::serialize(Value), QuotingType::None); + return *this; + } + + SequenceBuilder &emit(StringRef Item, QuotingType MustQuote); + + SequenceBuilder &emitAlias(StringRef Alias); + + void close(); + }; + + class LLVM_NODISCARD BitsetBuilder { + friend class OutputStream; + BitsetBuilder(OutputStream &Out); + OutputStream *Out; + + public: + ~BitsetBuilder(); + + BitsetBuilder &emit(StringRef Str); + + void close(); + }; + + MapBuilder buildMap(bool Flow) { + enterMap(Flow); + return MapBuilder(*this); + } + + SequenceBuilder buildSequence(bool Flow) { + enterSequence(Flow); + return SequenceBuilder(*this); + } + + BitsetBuilder buildBitset() { + enterBitset(); + return BitsetBuilder(*this); + } + + void newDocument(); + void endDocuments(); + +private: + void enterMap(bool IsFlow); + void emitMapKey(StringRef Key); + void advanceMap(); + void exitMap(); + + void enterSequence(bool IsFlow); + void preEmitSequence(); + void advanceSequence(); + void exitSequence(); + + void enterBitset(); + void emitBitset(StringRef Str); + void exitBitset(); + +public: + void outputScalar(StringRef Str); + void outputScalar(StringRef Str, QuotingType Quotes); + void outputBlockScalar(StringRef Str); + void outputTag(StringRef Tag); + void outputScalarTag(StringRef Tag); + void outputAnchor(StringRef Anchor); + void outputAlias(StringRef Alias); + bool canElideEmptySequence() const; + +private: + enum InState : unsigned char { + inSeqFirstElement, + inFlowSeqFirstElement, + inSeqOtherElement, + inFlowSeqOtherElement, + + inMapFirstKey, + inFlowMapFirstKey, + inMapOtherKey, + inFlowMapOtherKey, + + inBitsetFirst, + inBitsetOther + + }; + + class State { + unsigned Type : 4; + unsigned Column : 28; + + public: + State(InState State) : Type(State) { + // As we flow after we create the state, we have to set column afterwards. + } + InState getState() const { return static_cast(Type); } + void setState(InState NewState) { Type = NewState; } + unsigned getColumn() const { return Column; } + void setColumn(unsigned NewColumn) { Column = NewColumn; } + }; + + static constexpr bool inSeqAnyElement(InState State) { + return State == inSeqFirstElement || State == inSeqOtherElement; + } + + static constexpr bool inFlowSeqAnyElement(InState State) { + return State == inFlowSeqFirstElement || State == inFlowSeqOtherElement; + } + + static constexpr bool inMapAnyKey(InState State) { + return State == inMapFirstKey || State == inMapOtherKey; + } + + static constexpr bool inFlowMapAnyKey(InState State) { + return State == inFlowMapFirstKey || State == inFlowMapOtherKey; + } + + static constexpr bool inAnySequence(InState State) { + return State <= inFlowSeqOtherElement; + } + + static constexpr bool inAnyMap(InState State) { + return State >= inMapFirstKey && State <= inFlowMapOtherKey; + } + + static constexpr bool inBitset(InState State) { + return State >= inBitsetFirst; + } + + void output(StringRef S); + void outputUpToEndOfLine(StringRef S); + void newLineCheck(bool EmptySequence = false); + void outputSpaces(unsigned NumSpaces); + void outputNewLine(); + void wrapFlow(); + + raw_ostream &Out; + SmallVector StateStack; + SmallVector ContainerPaddingStack; + unsigned WrapColumn : 28; + unsigned IsDocEmpty : 1; + unsigned CanEmitValues : 1; + unsigned InDocument : 1; + unsigned HasFirstDoc : 1; + unsigned Column; + StringRef Padding; +}; + +template <> struct OutputStream::Serializer { + static StringRef serialize(bool Value) { return Value ? "true" : "false"; } +}; + +template +struct OutputStream::Serializer< + T, std::enable_if_t::value && + std::is_arithmetic()>> { + static std::string serialize(T Value) { return std::to_string(Value); } +}; +} // namespace yaml +} // namespace llvm + +namespace std { +template class vector; +template class array; +template +class map; +template +class unordered_map; +} // namespace std + +namespace llvm { +template class ArrayRef; +template class StringMap; +template class StringMapEntry; +namespace yaml { +// Enable builders to emit Array like containers as Sequences +template +struct OutputStream::IsArrayLike> + : public std::true_type {}; + +template +struct OutputStream::IsArrayLike> : public std::true_type {}; + +template +struct OutputStream::IsArrayLike> : public std::true_type {}; + +template +struct OutputStream::IsArrayLike> : public std::true_type {}; + +template +struct OutputStream::IsArrayLike> : public std::true_type {}; + +// Enable builders to emit StringMap like containers as Maps. + +template +struct OutputStream::MapAccessor> { + static StringRef getKey(const StringMapEntry &Entry) { + return Entry.getKey(); + } + static const T &getValue(const StringMapEntry &Entry) { + return Entry.getValue(); + } +}; + +template +struct OutputStream::MapAccessor> { + static StringRef getKey(const std::pair &Entry) { + return Entry.first; + } + static const Value &getValue(const std::pair &Entry) { + return Entry.second; + } +}; + +template +struct OutputStream::MapAccessor< + std::unordered_map> { + static StringRef getKey(const std::pair &Entry) { + return Entry.first; + } + static const Value &getValue(const std::pair &Entry) { + return Entry.second; + } +}; + +} // namespace yaml +} // namespace llvm + +#endif // LLVM_SUPPORT_YAMLSERIALIZER_H diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -226,6 +226,7 @@ WithColor.cpp X86TargetParser.cpp YAMLParser.cpp + YAMLSerializer.cpp YAMLTraits.cpp raw_os_ostream.cpp raw_ostream.cpp diff --git a/llvm/lib/Support/YAMLSerializer.cpp b/llvm/lib/Support/YAMLSerializer.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Support/YAMLSerializer.cpp @@ -0,0 +1,593 @@ +//===- YAMLSerializer.cpp - Simple YAML Serializer ------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements a YAML serializer. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/YamlSerializer.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +#if NDEBUG +#define DEBUGONLY(X) \ + do { \ + } while (false) +#else +#define DEBUGONLY(X) \ + do { \ + X; \ + } while (false) +#endif + +using namespace llvm; +using namespace yaml; + +QuotingType llvm::yaml::needsQuotes(StringRef S) { + if (S.empty()) + return QuotingType::Single; + + QuotingType MaxQuotingNeeded = QuotingType::None; + if (isSpace(static_cast(S.front())) || + isSpace(static_cast(S.back()))) + MaxQuotingNeeded = QuotingType::Single; + if (isNull(S)) + MaxQuotingNeeded = QuotingType::Single; + if (isBool(S)) + MaxQuotingNeeded = QuotingType::Single; + if (isNumeric(S)) + MaxQuotingNeeded = QuotingType::Single; + + // 7.3.3 Plain Style + // Plain scalars must not begin with most indicators, as this would cause + // ambiguity with other YAML constructs. + if (std::strchr(R"(-?:\,[]{}#&*!|>'"%@`)", S[0]) != nullptr) + MaxQuotingNeeded = QuotingType::Single; + + for (unsigned char C : S) { + // Alphanum is safe. + if (isAlnum(C)) + continue; + + switch (C) { + // Safe scalar characters. + case '_': + case '-': + case '^': + case '.': + case ',': + case ' ': + // TAB (0x9) is allowed in unquoted strings. + case 0x9: + continue; + // LF(0xA) and CR(0xD) may delimit values and so require at least single + // quotes. LLVM YAML parser cannot handle single quoted multiline so use + // double quoting to produce valid YAML. + case 0xA: + case 0xD: + return QuotingType::Double; + // DEL (0x7F) are excluded from the allowed character range. + case 0x7F: + return QuotingType::Double; + // Forward slash is allowed to be unquoted, but we quote it anyway. We have + // many tests that use FileCheck against YAML output, and this output often + // contains paths. If we quote backslashes but not forward slashes then + // paths will come out either quoted or unquoted depending on which platform + // the test is run on, making FileCheck comparisons difficult. + case '/': + default: { + // C0 control block (0x0 - 0x1F) is excluded from the allowed character + // range. + if (C <= 0x1F) + return QuotingType::Double; + + // Always double quote UTF-8. + if ((C & 0x80) != 0) + return QuotingType::Double; + + // The character is not safe, at least simple quoting needed. + MaxQuotingNeeded = QuotingType::Single; + } + } + } + + return MaxQuotingNeeded; +} + +OutputStream::OutputStream(raw_ostream &Underlying, int WrapColumn) + : Out(Underlying), WrapColumn(WrapColumn), +#ifndef NDEBUG + IsDocEmpty(true), CanEmitValues(true), +#endif + InDocument(false), HasFirstDoc(false), Column(0) { + // Silence any warnings about unused private variables + (void)IsDocEmpty; + (void)CanEmitValues; +} + +OutputStream::~OutputStream() { + if (InDocument) + endDocuments(); +} + +bool OutputStream::canElideEmptySequence() const { + // Normally, with an optional key/value where the value is an empty sequence, + // the whole key/value can be not written. But, that produces wrong yaml + // if the key/value is the only thing in the map and the map is used in + // a sequence. This detects if the this sequence is the first key/value + // in map that itself is embedded in a sequence. + if (StateStack.size() < 2) + return true; + if (StateStack.back().getState() != inMapFirstKey) + return true; + return !inSeqAnyElement(StateStack[StateStack.size() - 2].getState()); +} + +void OutputStream::output(StringRef s) { + Column += s.size(); + Out << s; + DEBUGONLY(IsDocEmpty = false); +} + +void OutputStream::outputUpToEndOfLine(StringRef s) { + output(s); + if (StateStack.empty() || + (!inFlowSeqAnyElement(StateStack.back().getState()) && + !inFlowMapAnyKey(StateStack.back().getState()) && + !inBitset(StateStack.back().getState()))) + Padding = "\n"; +} + +void OutputStream::outputNewLine() { + Out << "\n"; + Column = 0; +} + +// if seq at top, indent as if map, then add "- " +// if seq in middle, use "- " if firstKey, else use " " +// +void OutputStream::newLineCheck(bool EmptySequence) { + if (Padding != "\n") { + output(Padding); + Padding = {}; + return; + } + outputNewLine(); + Padding = {}; + + if (StateStack.size() == 0 || EmptySequence) + return; + + unsigned Indent = StateStack.size() - 1; + bool OutputDash = false; + + if (inSeqAnyElement(StateStack.back().getState())) { + OutputDash = true; + } else if ((StateStack.size() > 1) && + ((StateStack.back().getState() == inMapFirstKey) || + inFlowSeqAnyElement(StateStack.back().getState()) || + (StateStack.back().getState() == inFlowMapFirstKey)) && + inSeqAnyElement(StateStack[StateStack.size() - 2].getState())) { + --Indent; + OutputDash = true; + } + + outputSpaces(Indent * 2); + if (OutputDash) { + output("- "); + } +} + +void OutputStream::wrapFlow() { + if (WrapColumn && Column > WrapColumn) { + output("\n"); + unsigned Spaces = StateStack.back().getColumn() + 2; + outputSpaces(Spaces); + Column = Spaces; + } +} + +void OutputStream::outputSpaces(unsigned NumSpaces) { + constexpr StringLiteral Spaces = " "; + while (NumSpaces > Spaces.size()) { + output(Spaces); + NumSpaces -= Spaces.size(); + } + if (NumSpaces) + output(Spaces.take_front(NumSpaces)); +} + +void OutputStream::outputScalar(StringRef Str) { + outputScalar(Str, needsQuotes(Str)); +} + +void OutputStream::outputScalar(StringRef S, QuotingType MustQuote) { + assert(CanEmitValues); + DEBUGONLY(CanEmitValues = false); + newLineCheck(); + if (S.empty()) { + // Print '' for the empty string because leaving the field empty is not + // allowed. + outputUpToEndOfLine("''"); + return; + } + if (MustQuote == QuotingType::None) { + // Only quote if we must. + outputUpToEndOfLine(S); + return; + } + + const char *const Quote = MustQuote == QuotingType::Single ? "'" : "\""; + output(Quote); // Starting quote. + + // When using double-quoted strings (and only in that case), non-printable + // characters may be present, and will be escaped using a variety of + // unicode-scalar and special short-form escapes. This is handled in + // yaml::escape. + if (MustQuote == QuotingType::Double) { + output(yaml::escape(S, /* EscapePrintable= */ false)); + outputUpToEndOfLine(Quote); + return; + } + + unsigned i = 0; + unsigned j = 0; + unsigned End = S.size(); + const char *Base = S.data(); + + // When using single-quoted strings, any single quote ' must be doubled to be + // escaped. + while (j < End) { + if (S[j] == '\'') { // Escape quotes. + output(StringRef(&Base[i], j - i)); // "flush". + output(StringLiteral("''")); // Print it as '' + i = j + 1; + } + ++j; + } + output(StringRef(&Base[i], j - i)); + outputUpToEndOfLine(Quote); // Ending quote. +} + +void OutputStream::outputBlockScalar(StringRef S) { + assert(CanEmitValues); + if (!StateStack.empty()) + newLineCheck(); + output(" |"); + outputNewLine(); + + unsigned Indent = StateStack.empty() ? 1 : StateStack.size(); + + auto Buffer = MemoryBuffer::getMemBuffer(S, "", false); + for (line_iterator Lines(*Buffer, false); !Lines.is_at_end(); ++Lines) { + outputSpaces(Indent * 2); + output(*Lines); + outputNewLine(); + } + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::outputScalarTag(StringRef Tag) { + if (Tag.empty()) + return; + newLineCheck(); + output(Tag); + output(" "); +} + +void OutputStream::outputTag(StringRef Tag) { + // If this tag is being written inside a sequence we should write the start + // of the sequence before writing the tag, otherwise the tag won't be + // attached to the element in the sequence, but rather the sequence itself. + bool OuterSequenceElement = + StateStack.size() > 1 + ? inAnySequence(StateStack[StateStack.size() - 2].getState()) + : false; + if (OuterSequenceElement && StateStack.back().getState() == inMapFirstKey) { + newLineCheck(); + } else { + output(" "); + } + output(Tag); + if (OuterSequenceElement) { + // If we're writing the tag during the first element of a map, the tag + // takes the place of the first element in the sequence. + if (StateStack.back().getState() == inMapFirstKey) + StateStack.back().setState(inMapOtherKey); + // Tags inside maps in sequences should act as keys in the map from a + // formatting perspective, so we always want a newline in a sequence. + Padding = "\n"; + } +} + +void OutputStream::outputAlias(StringRef Alias) { + assert(CanEmitValues); + DEBUGONLY(CanEmitValues = false); + newLineCheck(); + output("*"); + outputUpToEndOfLine(Alias); +} + +void OutputStream::outputAnchor(StringRef Anchor) { + newLineCheck(); + output("&"); + output(Anchor); + Padding = " "; +} + +void OutputStream::newDocument() { + assert(StateStack.empty() && + "Cannot create new document while state is non-empty"); + assert(IsDocEmpty || !CanEmitValues); + if (HasFirstDoc) + outputUpToEndOfLine("\n---"); + else { + outputUpToEndOfLine("---"); + HasFirstDoc = true; + } + InDocument = true; + DEBUGONLY(IsDocEmpty = true); + DEBUGONLY(CanEmitValues = true); +} + +void OutputStream::endDocuments() { + assert(StateStack.empty() && "Cannot end documents while state is non-empty"); + assert(IsDocEmpty == CanEmitValues); + outputUpToEndOfLine("\n...\n"); + InDocument = false; + HasFirstDoc = false; + DEBUGONLY(IsDocEmpty = true); + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::enterMap(bool IsFlow) { + assert(CanEmitValues); + if (IsFlow) { + StateStack.push_back(inFlowMapFirstKey); + newLineCheck(); + StateStack.back().setColumn(Column); + output("{ "); + } else { + StateStack.push_back(inMapFirstKey); + ContainerPaddingStack.push_back(Padding); + Padding = "\n"; + } + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::emitMapKey(StringRef Key) { + assert(!CanEmitValues); + if (inMapAnyKey(StateStack.back().getState())) { + newLineCheck(); + output(Key); + output(":"); + constexpr StringLiteral Spaces = " "; + Padding = (Key.size() < Spaces.size()) ? Spaces.drop_front(Key.size()) + : Spaces.take_back(); + } else { + assert(inFlowMapAnyKey(StateStack.back().getState())); + if (StateStack.back().getState() == inFlowMapOtherKey) + output(", "); + wrapFlow(); + output(Key); + output(": "); + } + DEBUGONLY(CanEmitValues = true); +} + +void OutputStream::advanceMap() { + assert(inAnyMap(StateStack.back().getState())); + assert(!CanEmitValues && "Waiting for value to be emitted"); + StateStack.back().setState( + static_cast(StateStack.back().getState() | 2U)); + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::exitMap() { + assert(!CanEmitValues && "Waiting for value to be emitted"); + assert(inAnyMap(StateStack.back().getState())); + if (inFlowMapAnyKey(StateStack.back().getState())) { + StateStack.pop_back(); + outputUpToEndOfLine(" }"); + } else { + assert(inMapAnyKey(StateStack.back().getState())); + // If we did not map anything, we should explicitly emit an empty map + if (StateStack.back().getState() == inMapFirstKey) { + Padding = ContainerPaddingStack.pop_back_val(); + newLineCheck(); + output("{}"); + Padding = "\n"; + } + StateStack.pop_back(); + } +} + +void OutputStream::enterSequence(bool IsFlow) { + assert(CanEmitValues); + if (IsFlow) { + StateStack.push_back(inFlowSeqFirstElement); + newLineCheck(); + StateStack.back().setColumn(Column); + output("[ "); + } else { + StateStack.emplace_back(inSeqFirstElement); + ContainerPaddingStack.push_back(Padding); + Padding = "\n"; + } + // Prevent emitting values until preEmitSequence is called. + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::preEmitSequence() { + assert(inAnySequence(StateStack.back().getState())); + assert(!CanEmitValues); + if (inFlowSeqAnyElement(StateStack.back().getState())) { + if (StateStack.back().getState() == inFlowSeqOtherElement) + output(", "); + wrapFlow(); + } + DEBUGONLY(CanEmitValues = true); +} + +void OutputStream::advanceSequence() { + assert(inAnySequence(StateStack.back().getState())); + assert(!CanEmitValues && "Waiting for sequence value to be emitted"); + StateStack.back().setState( + static_cast(StateStack.back().getState() | 2U)); +} + +void OutputStream::exitSequence() { + assert(!CanEmitValues && "Waiting for sequence value to be emitted"); + if (inFlowSeqAnyElement(StateStack.back().getState())) { + StateStack.pop_back(); + outputUpToEndOfLine(" ]"); + } else { + assert(inSeqAnyElement(StateStack.back().getState())); + // If we did not emit anything, we should explicitly emit an empty + // sequence + if (StateStack.back().getState() == inSeqFirstElement) { + Padding = ContainerPaddingStack.pop_back_val(); + newLineCheck(/*EmptySequence=*/true); + output("[]"); + Padding = "\n"; + } + StateStack.pop_back(); + } + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::enterBitset() { + assert(CanEmitValues); + newLineCheck(); + StateStack.emplace_back(inBitsetFirst).setColumn(Column); + output("[ "); + // Not technically correct, but all bitset output should go through emitBitset + // we can ignore that logic in there. + DEBUGONLY(CanEmitValues = false); +} + +void OutputStream::emitBitset(StringRef Str) { + assert(!CanEmitValues); + if (StateStack.back().getState() == inBitsetOther) + output(", "); + else { + assert(StateStack.back().getState() == inBitsetFirst); + StateStack.back().setState(inBitsetOther); + } + // FIXME: we should wrap bitsets before outputting as they are essentially + // restrictive flow sequences. + output(Str); +} + +void OutputStream::exitBitset() { + assert(!CanEmitValues); + assert(inBitset(StateStack.back().getState())); + StateStack.pop_back(); + outputUpToEndOfLine(" ]"); +} + +OutputStream::MapBuilder::~MapBuilder() { + if (Out) + Out->exitMap(); +} + +OutputStream::MapBuilder::MapBuilder(OutputStream &Out) : EmitterBase(Out) {} + +OutputStream::MapBuilder &OutputStream::MapBuilder::emit( + StringRef Key, llvm::function_ref BuildValue) { + EmitSingleCtx RAII(*this, Key); + BuildValue(*Out); + return *this; +} + +OutputStream::MapBuilder &OutputStream::MapBuilder::emit(StringRef Key, + StringRef Value) { + return emit(Key, Value, needsQuotes(Value)); +} + +OutputStream::MapBuilder & +OutputStream::MapBuilder::emit(StringRef Key, StringRef Value, + QuotingType MustQuoteValue) { + EmitSingleCtx RAII(*this, Key); + Out->outputScalar(Value, MustQuoteValue); + return *this; +} + +OutputStream::MapBuilder &OutputStream::MapBuilder::emitAlias(StringRef Key, + StringRef Alias) { + EmitSingleCtx RAII(*this, Key, /*AnchorAllowed=*/false); + Out->outputAlias(Alias); + return *this; +} + +void OutputStream::MapBuilder::close() { + assert(Out && "Map already disposed"); + Out->exitMap(); + Out = nullptr; +} + +OutputStream::SequenceBuilder::SequenceBuilder(OutputStream &Out) + : EmitterBase(Out) {} + +OutputStream::SequenceBuilder::~SequenceBuilder() { + if (Out) + Out->exitSequence(); +} + +OutputStream::SequenceBuilder &OutputStream::SequenceBuilder::emit( + llvm::function_ref EmitValue) { + EmitSingleCtx RAII(*this); + EmitValue(*Out); + return *this; +} + +OutputStream::SequenceBuilder & +OutputStream::SequenceBuilder::emit(StringRef Item) { + return emit(Item, needsQuotes(Item)); +} + +OutputStream::SequenceBuilder & +OutputStream::SequenceBuilder::emit(StringRef Item, QuotingType MustQuote) { + EmitSingleCtx RAII(*this); + Out->outputScalar(Item, MustQuote); + return *this; +} + +OutputStream::SequenceBuilder & +OutputStream::SequenceBuilder::emitAlias(StringRef Alias) { + EmitSingleCtx RAII(*this, /*AnchorAllowed=*/false); + Out->outputAlias(Alias); + return *this; +} + +void OutputStream::SequenceBuilder::close() { + assert(Out && "Sequence already disposed"); + Out->exitSequence(); + Out = nullptr; +} + +OutputStream::BitsetBuilder::BitsetBuilder(OutputStream &Out) : Out(&Out) {} + +OutputStream::BitsetBuilder::~BitsetBuilder() { + if (Out) + Out->exitBitset(); +} + +OutputStream::BitsetBuilder &OutputStream::BitsetBuilder::emit(StringRef Str) { + assert(Out && "Bitset already disposed"); + Out->emitBitset(Str); + return *this; +} + +void OutputStream::BitsetBuilder::close() { + assert(Out && "Bitset already disposed"); + Out->exitBitset(); + Out = nullptr; +} diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp --- a/llvm/lib/Support/YAMLTraits.cpp +++ b/llvm/lib/Support/YAMLTraits.cpp @@ -465,7 +465,7 @@ //===----------------------------------------------------------------------===// Output::Output(raw_ostream &yout, void *context, int WrapColumn) - : IO(context), Out(yout), WrapColumn(WrapColumn) {} + : IO(context), Out(yout, WrapColumn) {} Output::~Output() = default; @@ -473,53 +473,15 @@ return true; } -void Output::beginMapping() { - StateStack.push_back(inMapFirstKey); - PaddingBeforeContainer = Padding; - Padding = "\n"; -} +void Output::beginMapping() { Out.enterMap(false); } bool Output::mapTag(StringRef Tag, bool Use) { - if (Use) { - // If this tag is being written inside a sequence we should write the start - // of the sequence before writing the tag, otherwise the tag won't be - // attached to the element in the sequence, but rather the sequence itself. - bool SequenceElement = false; - if (StateStack.size() > 1) { - auto &E = StateStack[StateStack.size() - 2]; - SequenceElement = inSeqAnyElement(E) || inFlowSeqAnyElement(E); - } - if (SequenceElement && StateStack.back() == inMapFirstKey) { - newLineCheck(); - } else { - output(" "); - } - output(Tag); - if (SequenceElement) { - // If we're writing the tag during the first element of a map, the tag - // takes the place of the first element in the sequence. - if (StateStack.back() == inMapFirstKey) { - StateStack.pop_back(); - StateStack.push_back(inMapOtherKey); - } - // Tags inside maps in sequences should act as keys in the map from a - // formatting perspective, so we always want a newline in a sequence. - Padding = "\n"; - } - } + if (Use) + Out.outputTag(Tag); return Use; } -void Output::endMapping() { - // If we did not map anything, we should explicitly emit an empty map - if (StateStack.back() == inMapFirstKey) { - Padding = PaddingBeforeContainer; - newLineCheck(); - output("{}"); - Padding = "\n"; - } - StateStack.pop_back(); -} +void Output::endMapping() { Out.exitMap(); } std::vector Output::keys() { report_fatal_error("invalid call"); @@ -530,121 +492,59 @@ UseDefault = false; SaveInfo = nullptr; if (Required || !SameAsDefault || WriteDefaultValues) { - auto State = StateStack.back(); - if (State == inFlowMapFirstKey || State == inFlowMapOtherKey) { - flowKey(Key); - } else { - newLineCheck(); - paddedKey(Key); - } + Out.emitMapKey(Key); return true; } return false; } -void Output::postflightKey(void *) { - if (StateStack.back() == inMapFirstKey) { - StateStack.pop_back(); - StateStack.push_back(inMapOtherKey); - } else if (StateStack.back() == inFlowMapFirstKey) { - StateStack.pop_back(); - StateStack.push_back(inFlowMapOtherKey); - } -} +void Output::postflightKey(void *) { Out.advanceMap(); } -void Output::beginFlowMapping() { - StateStack.push_back(inFlowMapFirstKey); - newLineCheck(); - ColumnAtMapFlowStart = Column; - output("{ "); -} +void Output::beginFlowMapping() { Out.enterMap(true); } -void Output::endFlowMapping() { - StateStack.pop_back(); - outputUpToEndOfLine(" }"); -} +void Output::endFlowMapping() { Out.exitMap(); } -void Output::beginDocuments() { - outputUpToEndOfLine("---"); -} +void Output::beginDocuments() {} bool Output::preflightDocument(unsigned index) { - if (index > 0) - outputUpToEndOfLine("\n---"); + Out.newDocument(); return true; } void Output::postflightDocument() { } -void Output::endDocuments() { - output("\n...\n"); -} +void Output::endDocuments() { Out.endDocuments(); } unsigned Output::beginSequence() { - StateStack.push_back(inSeqFirstElement); - PaddingBeforeContainer = Padding; - Padding = "\n"; + Out.enterSequence(false); return 0; } -void Output::endSequence() { - // If we did not emit anything, we should explicitly emit an empty sequence - if (StateStack.back() == inSeqFirstElement) { - Padding = PaddingBeforeContainer; - newLineCheck(/*EmptySequence=*/true); - output("[]"); - Padding = "\n"; - } - StateStack.pop_back(); -} +void Output::endSequence() { Out.exitSequence(); } bool Output::preflightElement(unsigned, void *&SaveInfo) { + Out.preEmitSequence(); SaveInfo = nullptr; return true; } -void Output::postflightElement(void *) { - if (StateStack.back() == inSeqFirstElement) { - StateStack.pop_back(); - StateStack.push_back(inSeqOtherElement); - } else if (StateStack.back() == inFlowSeqFirstElement) { - StateStack.pop_back(); - StateStack.push_back(inFlowSeqOtherElement); - } -} +void Output::postflightElement(void *) { Out.advanceSequence(); } unsigned Output::beginFlowSequence() { - StateStack.push_back(inFlowSeqFirstElement); - newLineCheck(); - ColumnAtFlowStart = Column; - output("[ "); - NeedFlowSequenceComma = false; + Out.enterSequence(true); return 0; } -void Output::endFlowSequence() { - StateStack.pop_back(); - outputUpToEndOfLine(" ]"); -} +void Output::endFlowSequence() { Out.exitSequence(); } bool Output::preflightFlowElement(unsigned, void *&SaveInfo) { - if (NeedFlowSequenceComma) - output(", "); - if (WrapColumn && Column > WrapColumn) { - output("\n"); - for (int i = 0; i < ColumnAtFlowStart; ++i) - output(" "); - Column = ColumnAtFlowStart; - output(" "); - } + Out.preEmitSequence(); SaveInfo = nullptr; return true; } -void Output::postflightFlowElement(void *) { - NeedFlowSequenceComma = true; -} +void Output::postflightFlowElement(void *) { Out.advanceSequence(); } void Output::beginEnumScalar() { EnumerationMatchFound = false; @@ -652,8 +552,7 @@ bool Output::matchEnumScalar(const char *Str, bool Match) { if (Match && !EnumerationMatchFound) { - newLineCheck(); - outputUpToEndOfLine(Str); + Out.outputScalar(Str, QuotingType::None); EnumerationMatchFound = true; } return false; @@ -672,211 +571,35 @@ } bool Output::beginBitSetScalar(bool &DoClear) { - newLineCheck(); - output("[ "); - NeedBitValueComma = false; + Out.enterBitset(); DoClear = false; return true; } bool Output::bitSetMatch(const char *Str, bool Matches) { if (Matches) { - if (NeedBitValueComma) - output(", "); - output(Str); - NeedBitValueComma = true; + Out.emitBitset(Str); } return false; } -void Output::endBitSetScalar() { - outputUpToEndOfLine(" ]"); -} +void Output::endBitSetScalar() { Out.exitBitset(); } void Output::scalarString(StringRef &S, QuotingType MustQuote) { - newLineCheck(); - if (S.empty()) { - // Print '' for the empty string because leaving the field empty is not - // allowed. - outputUpToEndOfLine("''"); - return; - } - if (MustQuote == QuotingType::None) { - // Only quote if we must. - outputUpToEndOfLine(S); - return; - } - - const char *const Quote = MustQuote == QuotingType::Single ? "'" : "\""; - output(Quote); // Starting quote. - - // When using double-quoted strings (and only in that case), non-printable characters may be - // present, and will be escaped using a variety of unicode-scalar and special short-form - // escapes. This is handled in yaml::escape. - if (MustQuote == QuotingType::Double) { - output(yaml::escape(S, /* EscapePrintable= */ false)); - outputUpToEndOfLine(Quote); - return; - } - - unsigned i = 0; - unsigned j = 0; - unsigned End = S.size(); - const char *Base = S.data(); - - // When using single-quoted strings, any single quote ' must be doubled to be escaped. - while (j < End) { - if (S[j] == '\'') { // Escape quotes. - output(StringRef(&Base[i], j - i)); // "flush". - output(StringLiteral("''")); // Print it as '' - i = j + 1; - } - ++j; - } - output(StringRef(&Base[i], j - i)); - outputUpToEndOfLine(Quote); // Ending quote. + Out.outputScalar(S, MustQuote); } -void Output::blockScalarString(StringRef &S) { - if (!StateStack.empty()) - newLineCheck(); - output(" |"); - outputNewLine(); - - unsigned Indent = StateStack.empty() ? 1 : StateStack.size(); - - auto Buffer = MemoryBuffer::getMemBuffer(S, "", false); - for (line_iterator Lines(*Buffer, false); !Lines.is_at_end(); ++Lines) { - for (unsigned I = 0; I < Indent; ++I) { - output(" "); - } - output(*Lines); - outputNewLine(); - } -} +void Output::blockScalarString(StringRef &S) { Out.outputBlockScalar(S); } -void Output::scalarTag(std::string &Tag) { - if (Tag.empty()) - return; - newLineCheck(); - output(Tag); - output(" "); -} +void Output::scalarTag(std::string &Tag) { Out.outputScalarTag(Tag); } void Output::setError(const Twine &message) { } -bool Output::canElideEmptySequence() { - // Normally, with an optional key/value where the value is an empty sequence, - // the whole key/value can be not written. But, that produces wrong yaml - // if the key/value is the only thing in the map and the map is used in - // a sequence. This detects if the this sequence is the first key/value - // in map that itself is embedded in a sequence. - if (StateStack.size() < 2) - return true; - if (StateStack.back() != inMapFirstKey) - return true; - return !inSeqAnyElement(StateStack[StateStack.size() - 2]); -} - -void Output::output(StringRef s) { - Column += s.size(); - Out << s; -} - -void Output::outputUpToEndOfLine(StringRef s) { - output(s); - if (StateStack.empty() || (!inFlowSeqAnyElement(StateStack.back()) && - !inFlowMapAnyKey(StateStack.back()))) - Padding = "\n"; -} - -void Output::outputNewLine() { - Out << "\n"; - Column = 0; -} - -// if seq at top, indent as if map, then add "- " -// if seq in middle, use "- " if firstKey, else use " " -// - -void Output::newLineCheck(bool EmptySequence) { - if (Padding != "\n") { - output(Padding); - Padding = {}; - return; - } - outputNewLine(); - Padding = {}; - - if (StateStack.size() == 0 || EmptySequence) - return; - - unsigned Indent = StateStack.size() - 1; - bool OutputDash = false; - - if (StateStack.back() == inSeqFirstElement || - StateStack.back() == inSeqOtherElement) { - OutputDash = true; - } else if ((StateStack.size() > 1) && - ((StateStack.back() == inMapFirstKey) || - inFlowSeqAnyElement(StateStack.back()) || - (StateStack.back() == inFlowMapFirstKey)) && - inSeqAnyElement(StateStack[StateStack.size() - 2])) { - --Indent; - OutputDash = true; - } - - for (unsigned i = 0; i < Indent; ++i) { - output(" "); - } - if (OutputDash) { - output("- "); - } -} - -void Output::paddedKey(StringRef key) { - output(key); - output(":"); - const char *spaces = " "; - if (key.size() < strlen(spaces)) - Padding = &spaces[key.size()]; - else - Padding = " "; -} - -void Output::flowKey(StringRef Key) { - if (StateStack.back() == inFlowMapOtherKey) - output(", "); - if (WrapColumn && Column > WrapColumn) { - output("\n"); - for (int I = 0; I < ColumnAtMapFlowStart; ++I) - output(" "); - Column = ColumnAtMapFlowStart; - output(" "); - } - output(Key); - output(": "); -} +bool Output::canElideEmptySequence() { return Out.canElideEmptySequence(); } NodeKind Output::getNodeKind() { report_fatal_error("invalid call"); } -bool Output::inSeqAnyElement(InState State) { - return State == inSeqFirstElement || State == inSeqOtherElement; -} - -bool Output::inFlowSeqAnyElement(InState State) { - return State == inFlowSeqFirstElement || State == inFlowSeqOtherElement; -} - -bool Output::inMapAnyKey(InState State) { - return State == inMapFirstKey || State == inMapOtherKey; -} - -bool Output::inFlowMapAnyKey(InState State) { - return State == inFlowMapFirstKey || State == inFlowMapOtherKey; -} - //===----------------------------------------------------------------------===// // traits for built-in types //===----------------------------------------------------------------------===// diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -97,6 +97,7 @@ WithColorTest.cpp YAMLIOTest.cpp YAMLParserTest.cpp + YAMLSerializerTest.cpp buffer_ostream_test.cpp formatted_raw_ostream_test.cpp raw_fd_stream_test.cpp diff --git a/llvm/unittests/Support/YAMLSerializerTest.cpp b/llvm/unittests/Support/YAMLSerializerTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/YAMLSerializerTest.cpp @@ -0,0 +1,167 @@ +//===- unittest/Support/YAMLSerializerTest --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/YamlSerializer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include + +namespace llvm { +using yaml::OutputStream; + +static void eatDiagnostic(const SMDiagnostic &, void *) {} + +static bool isParseable(StringRef Input) { + SourceMgr Mgr; + Mgr.setDiagHandler(eatDiagnostic); + yaml::Stream InStream(Input, Mgr); + InStream.skip(); + return !InStream.failed(); +} + +TEST(YAMLSerializer, Builders) { + std::string Output; + llvm::raw_string_ostream Stream(Output); + { + yaml::OutputStream Yaml(Stream); + Yaml.newDocument(); + auto BuildMap = Yaml.buildMap(/*Flow=*/false); + BuildMap.emit("ScalarTest", "Value"); + BuildMap.emit("FlowMap", [](OutputStream &O) { + O.buildMap(/*Flow=*/true) + .emit("X", "5", yaml::QuotingType::None) + .emit("Y", "4", yaml::QuotingType::Single) + .emit("Z", "3", yaml::QuotingType::Double); + }); + BuildMap.emit("FlowMapFormats", [](OutputStream &O) { + O.buildMap(true).emit("X", 5).emit("Y", 4.3).emit("Z", false); + }); + BuildMap.emit("MapTest", [](OutputStream &O) { + O.buildMap(/*Flow=*/false) + .emit("FlowFirst", + [](OutputStream &O) { + O.buildSequence(/*Flow=*/true) + .emit("Item1") + .emit("Item2") + .emit("Item3"); + }) + .emit("NonFlow", + [](OutputStream &O) { + O.buildSequence(/*Flow=*/false) + .emit("ItemA") + .emit("ItemB") + .emit("ItemC"); + }) + .emit("Bitset", [](OutputStream &O) { + O.buildBitset().emit("Bits1").emit("Bits2").emit("Bits3"); + }); + }); + + SmallVector Ints = {1, 2, 3, 4}; + std::vector Strings = {"Hello", "World", "Goodbye"}; + SmallVectorImpl &IntsRef = Ints; + BuildMap.emit("SVInts", Ints, false, true); + BuildMap.emit("VStrings", Strings, false, true); + BuildMap.emit("SVIRefs", IntsRef, false, true); + llvm::StringMap MapInts{{"Hello", 1}, {"World", 2}, {"Goodbye", 3}}; + std::map MapFloats{{"Float1", 1}, {"Float2", 2}}; + BuildMap.anchor("Ints").emit("MappedTest", MapInts, true, true); + BuildMap.emit("MapFloats", MapFloats, false, false); + BuildMap.anchor("Ints2").emit("MappedTest2", [](OutputStream &O) { + O.buildMap(false).emit("Hello", 3).emit("Goodbye", 4); + }); + BuildMap.emitAlias("Aliased", "Ints"); + BuildMap.emit("SequenceAlias", [](OutputStream &O) { + O.buildSequence(true).emitAlias("Ints2").emit("Hello").emitAlias("Ints"); + }); + } + Stream.flush(); + EXPECT_EQ(R"(--- +ScalarTest: Value +FlowMap: { X: 5, Y: '4', Z: "3" } +FlowMapFormats: { X: 5, Y: 4.300000, Z: false } +MapTest: + FlowFirst: [ Item1, Item2, Item3 ] + NonFlow: + - ItemA + - ItemB + - ItemC + Bitset: [ Bits1, Bits2, Bits3 ] +SVInts: [ 1, 2, 3, 4 ] +VStrings: [ Hello, World, Goodbye ] +SVIRefs: [ 1, 2, 3, 4 ] +MappedTest: &Ints { World: 2, Goodbye: 3, Hello: 1 } +MapFloats: + Float1: 1.000000 + Float2: 2.000000 +MappedTest2: &Ints2 + Hello: 3 + Goodbye: 4 +Aliased: *Ints +SequenceAlias: [ *Ints2, Hello, *Ints ] +... +)", + Output); + EXPECT_TRUE(isParseable(Output)); +} + +TEST(YAMLSerializer, UnoderedMap) { + // Testing an unorder_map of strings isn't trivial as the output order is + // implementation defined. So we need to parse the output back into something + // matchable + std::unordered_map Items{ + {"Item1", "Hello"}, {"Item2", "World"}, {"Item3", "123"}}; + std::string Output; + llvm::raw_string_ostream Stream(Output); + { + yaml::OutputStream Yaml(Stream); + Yaml.newDocument(); + Yaml.buildSequence(false).emit(Items, false); + } + StringRef Result(Output); + EXPECT_TRUE(Result.consume_front("---\n")); + EXPECT_TRUE(Result.consume_back("\n...\n")); + llvm::SmallVector Split; + Result.split(Split, '\n'); + // First item in the map has a "- " at the start; + auto Res = makeMutableArrayRef(Split); + EXPECT_TRUE(Res.front().consume_front("- ")); + // Rest have 2 spaces + for (auto &Item : Res.drop_front()) { + EXPECT_TRUE(Item.consume_front(" ")); + } + + EXPECT_THAT(Split, testing::UnorderedElementsAre( + testing::Eq("Item1: Hello"), + testing::Eq("Item2: World"), + testing::Eq("Item3: '123'"))); +} + +TEST(YAMLSerializer, WriteVector) { + std::vector Values = {1, 2, 3, 4, 5}; + std::string Output; + llvm::raw_string_ostream Stream(Output); + { + yaml::OutputStream Yaml(Stream); + auto Seq = Yaml.buildSequence(true); + for (auto Item : Values) + Seq.emit(std::to_string(Item), yaml::QuotingType::None); + } + Stream.flush(); + EXPECT_EQ(Output, R"([ 1, 2, 3, 4, 5 ])"); + EXPECT_TRUE(isParseable(Output)); +} +} // namespace llvm