From f5c71f4fcc7d370ecdd4f197eec51b33d6f93171 Mon Sep 17 00:00:00 2001 From: Timur Golubovich Date: Thu, 27 Mar 2025 20:04:08 +0300 Subject: [PATCH] [llvm][Support] Add YAMLGenerateSchema for producing YAML schemas Introduced a way for generating schema for validating input YAML. This can be useful to see the full structure of the input YAML file for different llvm based tools that use existing YAML parser, for example clang-format, clang-tidy e.t.c. This commit also can be useful for yaml-language-server. --- .../include/llvm/Support/YAMLGenerateSchema.h | 400 ++++++++++++++++++ llvm/include/llvm/Support/YAMLTraits.h | 71 ++-- llvm/lib/Support/CMakeLists.txt | 1 + llvm/lib/Support/YAMLGenerateSchema.cpp | 285 +++++++++++++ llvm/lib/Support/YAMLTraits.cpp | 12 +- llvm/unittests/Support/CMakeLists.txt | 1 + .../Support/YAMLGenerateSchemaTest.cpp | 125 ++++++ .../gn/secondary/llvm/lib/Support/BUILD.gn | 1 + 8 files changed, 865 insertions(+), 31 deletions(-) create mode 100644 llvm/include/llvm/Support/YAMLGenerateSchema.h create mode 100644 llvm/lib/Support/YAMLGenerateSchema.cpp create mode 100644 llvm/unittests/Support/YAMLGenerateSchemaTest.cpp diff --git a/llvm/include/llvm/Support/YAMLGenerateSchema.h b/llvm/include/llvm/Support/YAMLGenerateSchema.h new file mode 100644 index 0000000000000..7b4781a3dc18f --- /dev/null +++ b/llvm/include/llvm/Support/YAMLGenerateSchema.h @@ -0,0 +1,400 @@ +//===----------------------------------------------------------------------===// +// +// 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_YAMLGENERATE_SCHEMA_H +#define LLVM_SUPPORT_YAMLGENERATE_SCHEMA_H + +#include "llvm/Support/Casting.h" +#include "llvm/Support/YAMLTraits.h" + +namespace llvm { + +namespace json { +class Value; +} + +namespace yaml { + +class GenerateSchema : public IO { +public: + GenerateSchema(raw_ostream &RO); + ~GenerateSchema() override = default; + + IOKind getKind() const override; + bool outputting() const override; + bool mapTag(StringRef, bool) override; + void beginMapping() override; + void endMapping() override; + bool preflightKey(StringRef, bool, bool, bool &, void *&) override; + void postflightKey(void *) override; + std::vector keys() override; + void beginFlowMapping() override; + void endFlowMapping() override; + unsigned beginSequence() override; + void endSequence() override; + bool preflightElement(unsigned, void *&) override; + void postflightElement(void *) override; + unsigned beginFlowSequence() override; + bool preflightFlowElement(unsigned, void *&) override; + void postflightFlowElement(void *) override; + void endFlowSequence() override; + void beginEnumScalar() override; + bool matchEnumScalar(StringRef, bool) override; + bool matchEnumFallback() override; + void endEnumScalar() override; + bool beginBitSetScalar(bool &) override; + bool bitSetMatch(StringRef, bool) override; + void endBitSetScalar() override; + void scalarString(StringRef &, QuotingType) override; + void blockScalarString(StringRef &) override; + void scalarTag(std::string &) override; + NodeKind getNodeKind() override; + void setError(const Twine &message) override; + std::error_code error() override; + bool canElideEmptySequence() override; + + bool preflightDocument(); + void postflightDocument(); + + class SchemaNode { + public: + virtual json::Value toJSON() const = 0; + + virtual ~SchemaNode() = default; + }; + + enum class PropertyKind : uint8_t { + UserDefined, + Properties, + AdditionalProperties, + Required, + Optional, + Type, + Enum, + Items, + FlowStyle, + }; + + class SchemaProperty : public SchemaNode { + StringRef Name; + PropertyKind Kind; + + public: + SchemaProperty(StringRef Name, PropertyKind Kind) + : Name(Name), Kind(Kind) {} + + PropertyKind getKind() const { return Kind; } + + StringRef getName() const { return Name; } + }; + + class Schema; + + class UserDefinedProperty final : public SchemaProperty { + Schema *Value; + + public: + UserDefinedProperty(StringRef Name, Schema *Value) + : SchemaProperty(Name, PropertyKind::UserDefined), Value(Value) {} + + Schema *getSchema() const { return Value; } + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::UserDefined; + } + }; + + class PropertiesProperty final : public SchemaProperty, + SmallVector { + public: + using BaseVector = SmallVector; + + PropertiesProperty() + : SchemaProperty("properties", PropertyKind::Properties) {} + + using BaseVector::begin; + using BaseVector::emplace_back; + using BaseVector::end; + using BaseVector::size; + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Properties; + } + }; + + class AdditionalPropertiesProperty final : public SchemaProperty { + Schema *Value; + + public: + AdditionalPropertiesProperty(Schema *Value = nullptr) + : SchemaProperty("additionalProperties", + PropertyKind::AdditionalProperties), + Value(Value) {} + + Schema *getSchema() const { return Value; } + + void setSchema(Schema *S) { Value = S; } + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::AdditionalProperties; + } + }; + + class RequiredProperty final : public SchemaProperty, + SmallVector { + public: + using BaseVector = SmallVector; + + RequiredProperty() : SchemaProperty("required", PropertyKind::Required) {} + + using BaseVector::begin; + using BaseVector::emplace_back; + using BaseVector::end; + using BaseVector::size; + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Required; + } + }; + + class OptionalProperty final : public SchemaProperty, + SmallVector { + public: + using BaseVector = SmallVector; + + OptionalProperty() : SchemaProperty("optional", PropertyKind::Optional) {} + + using BaseVector::begin; + using BaseVector::emplace_back; + using BaseVector::end; + using BaseVector::size; + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Optional; + } + }; + + class TypeProperty final : public SchemaProperty { + StringRef Value; + + public: + TypeProperty(StringRef Value) + : SchemaProperty("type", PropertyKind::Type), Value(Value) {} + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Type; + } + }; + + class EnumProperty final : public SchemaProperty, SmallVector { + public: + using BaseVector = SmallVector; + + EnumProperty() : SchemaProperty("enum", PropertyKind::Enum) {} + + using BaseVector::begin; + using BaseVector::emplace_back; + using BaseVector::end; + using BaseVector::size; + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Enum; + } + }; + + class ItemsProperty final : public SchemaProperty { + Schema *Value; + + public: + ItemsProperty(Schema *Value = nullptr) + : SchemaProperty("items", PropertyKind::Items), Value(Value) {} + + Schema *getSchema() const { return Value; } + + void setSchema(Schema *S) { Value = S; } + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::Items; + } + }; + + enum class FlowStyle : bool { + Block, + Flow, + }; + + class FlowStyleProperty final : public SchemaProperty { + FlowStyle Style; + + public: + FlowStyleProperty(FlowStyle Style = FlowStyle::Block) + : SchemaProperty("flowStyle", PropertyKind::FlowStyle), Style(Style) {} + + void setStyle(FlowStyle S) { Style = S; } + + FlowStyle getStyle() const { return Style; } + + json::Value toJSON() const override; + + static bool classof(const SchemaProperty *Property) { + return Property->getKind() == PropertyKind::FlowStyle; + } + }; + + class Schema final : public SchemaNode, SmallVector { + public: + using BaseVector = SmallVector; + + Schema() = default; + + using BaseVector::begin; + using BaseVector::emplace_back; + using BaseVector::end; + using BaseVector::size; + + json::Value toJSON() const override; + }; + +private: + std::vector> SchemaNodes; + SmallVector Schemas; + raw_ostream &RO; + SchemaNode *Root = nullptr; + + template + PropertyType *createProperty(PropertyArgs &&...Args) { + std::unique_ptr UPtr = + std::make_unique(std::forward(Args)...); + PropertyType *Ptr = UPtr.get(); + SchemaNodes.emplace_back(std::move(UPtr)); + return Ptr; + } + + template + PropertyType *getOrCreateProperty(Schema &S, PropertyArgs... Args) { + auto Found = std::find_if(S.begin(), S.end(), [](SchemaProperty *Property) { + return isa(Property); + }); + if (Found != S.end()) { + return cast(*Found); + } + PropertyType *Created = + createProperty(std::forward(Args)...); + S.emplace_back(Created); + return Created; + } + + Schema *createSchema() { + std::unique_ptr UPtr = std::make_unique(); + Schema *Ptr = UPtr.get(); + SchemaNodes.emplace_back(std::move(UPtr)); + return Ptr; + } + + Schema *getTopSchema() const { + return Schemas.empty() ? nullptr : Schemas.back(); + } +}; + +// Define non-member operator<< so that Output can stream out document list. +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &DocList) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, DocumentListTraits::element(Gen, DocList, 0), true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Define non-member operator<< so that Output can stream out a map. +template +inline std::enable_if_t::value, + GenerateSchema &> +operator<<(GenerateSchema &Gen, T &Map) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, Map, true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Define non-member operator<< so that Output can stream out a sequence. +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &Seq) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, Seq, true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Define non-member operator<< so that Output can stream out a block scalar. +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &Val) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, Val, true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Define non-member operator<< so that Output can stream out a string map. +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &Val) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, Val, true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Define non-member operator<< so that Output can stream out a polymorphic +// type. +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &Val) { + EmptyContext Ctx; + Gen.preflightDocument(); + yamlize(Gen, Val, true, Ctx); + Gen.postflightDocument(); + return Gen; +} + +// Provide better error message about types missing a trait specialization +template +inline std::enable_if_t::value, GenerateSchema &> +operator<<(GenerateSchema &Gen, T &seq) { + char missing_yaml_trait_for_type[sizeof(MissingTrait)]; + return Gen; +} + +} // namespace yaml + +} // namespace llvm + +#endif // LLVM_SUPPORT_YAMLGENERATE_SCHEMA_H diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h index 3d36f41ca1a04..435323ef9ffbd 100644 --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -683,12 +683,19 @@ struct unvalidatedMappingTraits !has_MappingValidateTraits::value> { }; +enum class IOKind : uint8_t { + Outputting, + Inputting, + GeneratingSchema, +}; + // Base class for Input and Output. class LLVM_ABI IO { public: IO(void *Ctxt = nullptr); virtual ~IO(); + virtual IOKind getKind() const = 0; virtual bool outputting() const = 0; virtual unsigned beginSequence() = 0; @@ -732,7 +739,8 @@ class LLVM_ABI IO { virtual void setAllowUnknownKeys(bool Allow); template void enumCase(T &Val, StringRef Str, const T ConstVal) { - if (matchEnumScalar(Str, outputting() && Val == ConstVal)) { + if (matchEnumScalar(Str, + getKind() == IOKind::Outputting && Val == ConstVal)) { Val = ConstVal; } } @@ -740,7 +748,8 @@ class LLVM_ABI IO { // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF template void enumCase(T &Val, StringRef Str, const uint32_t ConstVal) { - if (matchEnumScalar(Str, outputting() && Val == static_cast(ConstVal))) { + if (matchEnumScalar(Str, getKind() == IOKind::Outputting && + Val == static_cast(ConstVal))) { Val = ConstVal; } } @@ -757,7 +766,8 @@ class LLVM_ABI IO { template void bitSetCase(T &Val, StringRef Str, const T ConstVal) { - if (bitSetMatch(Str, outputting() && (Val & ConstVal) == ConstVal)) { + if (bitSetMatch(Str, getKind() == IOKind::Outputting && + (Val & ConstVal) == ConstVal)) { Val = static_cast(Val | ConstVal); } } @@ -765,21 +775,24 @@ class LLVM_ABI IO { // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF template void bitSetCase(T &Val, StringRef Str, const uint32_t ConstVal) { - if (bitSetMatch(Str, outputting() && (Val & ConstVal) == ConstVal)) { + if (bitSetMatch(Str, getKind() == IOKind::Outputting && + (Val & ConstVal) == ConstVal)) { Val = static_cast(Val | ConstVal); } } template void maskedBitSetCase(T &Val, StringRef Str, T ConstVal, T Mask) { - if (bitSetMatch(Str, outputting() && (Val & Mask) == ConstVal)) + if (bitSetMatch(Str, getKind() == IOKind::Outputting && + (Val & Mask) == ConstVal)) Val = Val | ConstVal; } template void maskedBitSetCase(T &Val, StringRef Str, uint32_t ConstVal, uint32_t Mask) { - if (bitSetMatch(Str, outputting() && (Val & Mask) == ConstVal)) + if (bitSetMatch(Str, getKind() == IOKind::Outputting && + (Val & Mask) == ConstVal)) Val = Val | ConstVal; } @@ -844,7 +857,8 @@ class LLVM_ABI IO { bool Required, Context &Ctx) { void *SaveInfo; bool UseDefault; - const bool sameAsDefault = outputting() && Val == DefaultValue; + const bool sameAsDefault = + (getKind() == IOKind::Outputting) && Val == DefaultValue; if (this->preflightKey(Key, Required, sameAsDefault, UseDefault, SaveInfo)) { yamlize(*this, Val, Required, Ctx); @@ -905,7 +919,7 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { template std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { SmallString<128> Storage; raw_svector_ostream Buffer(Storage); ScalarTraits::output(Val, io.getContext(), Buffer); @@ -924,7 +938,7 @@ std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, template std::enable_if_t::value, void> yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) { - if (YamlIO.outputting()) { + if (YamlIO.getKind() == IOKind::Outputting) { std::string Storage; raw_string_ostream Buffer(Storage); BlockScalarTraits::output(Val, YamlIO.getContext(), Buffer); @@ -943,7 +957,7 @@ yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) { template std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { std::string ScalarStorage, TagStorage; raw_string_ostream ScalarBuffer(ScalarStorage), TagBuffer(TagStorage); TaggedScalarTraits::output(Val, io.getContext(), ScalarBuffer, @@ -985,7 +999,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) { io.beginFlowMapping(); else io.beginMapping(); - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { std::string Err = detail::doValidate(io, Val, Ctx); if (!Err.empty()) { errs() << Err << "\n"; @@ -993,7 +1007,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) { } } detail::doMapping(io, Val, Ctx); - if (!io.outputting()) { + if (io.getKind() == IOKind::Inputting) { std::string Err = detail::doValidate(io, Val, Ctx); if (!Err.empty()) io.setError(Err); @@ -1007,7 +1021,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) { template bool yamlizeMappingEnumInput(IO &io, T &Val) { if constexpr (has_MappingEnumInputTraits::value) { - if (io.outputting()) + if (io.getKind() == IOKind::Outputting) return false; io.beginEnumScalar(); @@ -1038,7 +1052,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) { template std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { io.beginMapping(); CustomMappingTraits::output(io, Val); io.endMapping(); @@ -1053,8 +1067,9 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { template std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { - switch (io.outputting() ? PolymorphicTraits::getKind(Val) - : io.getNodeKind()) { + switch (io.getKind() == IOKind::Outputting + ? PolymorphicTraits::getKind(Val) + : io.getNodeKind()) { case NodeKind::Scalar: return yamlize(io, PolymorphicTraits::getAsScalar(Val), true, Ctx); case NodeKind::Map: @@ -1075,7 +1090,9 @@ std::enable_if_t::value, void> yamlize(IO &io, T &Seq, bool, Context &Ctx) { if (has_FlowTraits>::value) { unsigned incnt = io.beginFlowSequence(); - unsigned count = io.outputting() ? SequenceTraits::size(io, Seq) : incnt; + unsigned count = io.getKind() == IOKind::Outputting + ? SequenceTraits::size(io, Seq) + : incnt; for (unsigned i = 0; i < count; ++i) { void *SaveInfo; if (io.preflightFlowElement(i, SaveInfo)) { @@ -1086,7 +1103,9 @@ yamlize(IO &io, T &Seq, bool, Context &Ctx) { io.endFlowSequence(); } else { unsigned incnt = io.beginSequence(); - unsigned count = io.outputting() ? SequenceTraits::size(io, Seq) : incnt; + unsigned count = io.getKind() == IOKind::Outputting + ? SequenceTraits::size(io, Seq) + : incnt; for (unsigned i = 0; i < count; ++i) { void *SaveInfo; if (io.preflightElement(i, SaveInfo)) { @@ -1239,7 +1258,7 @@ struct ScalarBitSetTraits< template struct MappingNormalization { MappingNormalization(IO &i_o, TFinal &Obj) : io(i_o), BufPtr(nullptr), Result(Obj) { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { BufPtr = new (&Buffer) TNorm(io, Obj); } else { BufPtr = new (&Buffer) TNorm(io); @@ -1247,7 +1266,7 @@ template struct MappingNormalization { } ~MappingNormalization() { - if (!io.outputting()) { + if (io.getKind() != IOKind::Outputting) { Result = BufPtr->denormalize(io); } BufPtr->~TNorm(); @@ -1269,7 +1288,7 @@ template struct MappingNormalization { template struct MappingNormalizationHeap { MappingNormalizationHeap(IO &i_o, TFinal &Obj, BumpPtrAllocator *allocator) : io(i_o), Result(Obj) { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { BufPtr = new (&Buffer) TNorm(io, Obj); } else if (allocator) { BufPtr = allocator->Allocate(); @@ -1280,7 +1299,7 @@ template struct MappingNormalizationHeap { } ~MappingNormalizationHeap() { - if (io.outputting()) { + if (io.getKind() == IOKind::Outputting) { BufPtr->~TNorm(); } else { Result = BufPtr->denormalize(io); @@ -1327,6 +1346,7 @@ class LLVM_ABI Input : public IO { std::error_code error() override; private: + IOKind getKind() const override; bool outputting() const override; bool mapTag(StringRef, bool) override; void beginMapping() override; @@ -1478,6 +1498,7 @@ class LLVM_ABI Output : public IO { /// anyway. void setWriteDefaultValues(bool Write) { WriteDefaultValues = Write; } + IOKind getKind() const override; bool outputting() const override; bool mapTag(StringRef, bool) override; void beginMapping() override; @@ -1563,8 +1584,8 @@ void IO::processKeyWithDefault(StringRef Key, std::optional &Val, assert(!DefaultValue && "std::optional shouldn't have a value!"); void *SaveInfo; bool UseDefault = true; - const bool sameAsDefault = outputting() && !Val; - if (!outputting() && !Val) + const bool sameAsDefault = (getKind() == IOKind::Outputting) && !Val; + if (getKind() != IOKind::Outputting && !Val) Val = T(); if (Val && this->preflightKey(Key, Required, sameAsDefault, UseDefault, SaveInfo)) { @@ -1574,7 +1595,7 @@ void IO::processKeyWithDefault(StringRef Key, std::optional &Val, // was requested, i.e. the DefaultValue will be assigned. The DefaultValue // is usually None. bool IsNone = false; - if (!outputting()) + if (getKind() == IOKind::Inputting) if (const auto *Node = dyn_cast(((Input *)this)->getCurrentNode())) // We use rtrim to ignore possible white spaces that might exist when a diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 671a5fe941cef..6efb8c29249d6 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -282,6 +282,7 @@ add_llvm_component_library(LLVMSupport WithColor.cpp YAMLParser.cpp YAMLTraits.cpp + YAMLGenerateSchema.cpp raw_os_ostream.cpp raw_ostream.cpp raw_ostream_proxy.cpp diff --git a/llvm/lib/Support/YAMLGenerateSchema.cpp b/llvm/lib/Support/YAMLGenerateSchema.cpp new file mode 100644 index 0000000000000..bb10f84fdec5d --- /dev/null +++ b/llvm/lib/Support/YAMLGenerateSchema.cpp @@ -0,0 +1,285 @@ +//===----------------------------------------------------------------------===// +// +// 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/YAMLGenerateSchema.h" +#include "llvm/Support/JSON.h" + +using namespace llvm; +using namespace yaml; + +//===----------------------------------------------------------------------===// +// GenerateSchema +//===----------------------------------------------------------------------===// + +GenerateSchema::GenerateSchema(raw_ostream &RO) : RO(RO) {} + +IOKind GenerateSchema::getKind() const { return IOKind::GeneratingSchema; } + +bool GenerateSchema::outputting() const { return false; } + +bool GenerateSchema::mapTag(StringRef, bool) { return false; } + +void GenerateSchema::beginMapping() { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("object"); + Top->emplace_back(Type); + FlowStyleProperty *FlowStyle = createProperty(); + Top->emplace_back(FlowStyle); +} + +void GenerateSchema::endMapping() {} + +bool GenerateSchema::preflightKey(StringRef Key, bool Required, + bool SameAsDefault, bool &UseDefault, + void *&SaveInfo) { + Schema *Top = getTopSchema(); + assert(Top); + if (Key == "additionalProperties") { + AdditionalPropertiesProperty *Additional = + getOrCreateProperty(*Top); + Schema *S = createSchema(); + Additional->setSchema(S); + Schemas.push_back(S); + return true; + } + + if (Required) { + RequiredProperty *Req = getOrCreateProperty(*Top); + Req->emplace_back(Key); + } else { + OptionalProperty *Opt = getOrCreateProperty(*Top); + Opt->emplace_back(Key); + } + PropertiesProperty *Properties = + getOrCreateProperty(*Top); + Schema *S = createSchema(); + UserDefinedProperty *UserDefined = + createProperty(Key, S); + Properties->emplace_back(UserDefined); + Schemas.push_back(S); + return true; +} + +void GenerateSchema::postflightKey(void *) { + assert(!Schemas.empty()); + Schemas.pop_back(); +} + +std::vector GenerateSchema::keys() { return {}; } + +void GenerateSchema::beginFlowMapping() { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("object"); + Top->emplace_back(Type); + FlowStyleProperty *FlowStyle = + createProperty(FlowStyle::Flow); + Top->emplace_back(FlowStyle); +} + +void GenerateSchema::endFlowMapping() {} + +unsigned GenerateSchema::beginSequence() { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("array"); + Top->emplace_back(Type); + ItemsProperty *Items = createProperty(); + Top->emplace_back(Items); + FlowStyleProperty *FlowStyle = createProperty(); + Top->emplace_back(FlowStyle); + return 1; +} + +void GenerateSchema::endSequence() {} + +bool GenerateSchema::preflightElement(unsigned, void *&) { + Schema *Top = getTopSchema(); + assert(Top); + Schema *S = createSchema(); + ItemsProperty *Items = getOrCreateProperty(*Top); + Items->setSchema(S); + Schemas.push_back(S); + return true; +} + +void GenerateSchema::postflightElement(void *) { + assert(!Schemas.empty()); + Schemas.pop_back(); +} + +unsigned GenerateSchema::beginFlowSequence() { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("array"); + Top->emplace_back(Type); + ItemsProperty *Items = createProperty(); + Top->emplace_back(Items); + FlowStyleProperty *FlowStyle = + createProperty(FlowStyle::Flow); + Top->emplace_back(FlowStyle); + return 1; +} + +bool GenerateSchema::preflightFlowElement(unsigned Arg1, void *&Arg2) { + return preflightElement(Arg1, Arg2); +} + +void GenerateSchema::postflightFlowElement(void *Arg1) { + postflightElement(Arg1); +} + +void GenerateSchema::endFlowSequence() { endSequence(); } + +void GenerateSchema::beginEnumScalar() { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("string"); + Top->emplace_back(Type); + EnumProperty *Enum = createProperty(); + Top->emplace_back(Enum); +} + +bool GenerateSchema::matchEnumScalar(StringRef Val, bool) { + Schema *Top = getTopSchema(); + assert(Top); + EnumProperty *Enum = getOrCreateProperty(*Top); + Enum->emplace_back(Val); + return false; +} + +bool GenerateSchema::matchEnumFallback() { return false; } + +void GenerateSchema::endEnumScalar() {} + +bool GenerateSchema::beginBitSetScalar(bool &) { + beginEnumScalar(); + return true; +} + +bool GenerateSchema::bitSetMatch(StringRef Val, bool Arg) { + return matchEnumScalar(Val, Arg); +} + +void GenerateSchema::endBitSetScalar() { endEnumScalar(); } + +void GenerateSchema::scalarString(StringRef &Val, QuotingType) { + Schema *Top = getTopSchema(); + assert(Top); + TypeProperty *Type = createProperty("string"); + Top->emplace_back(Type); +} + +void GenerateSchema::blockScalarString(StringRef &S) { + scalarString(S, QuotingType::None); +} + +void GenerateSchema::scalarTag(std::string &val) {} + +NodeKind GenerateSchema::getNodeKind() { report_fatal_error("invalid call"); } + +void GenerateSchema::setError(const Twine &) {} + +std::error_code GenerateSchema::error() { return {}; } + +bool GenerateSchema::canElideEmptySequence() { return false; } + +// These are only used by operator<<. They could be private +// if that templated operator could be made a friend. + +bool GenerateSchema::preflightDocument() { + Schema *S = createSchema(); + Root = S; + Schemas.push_back(S); + return true; +} + +void GenerateSchema::postflightDocument() { + assert(!Schemas.empty()); + Schemas.pop_back(); + json::Value JSONValue = Root->toJSON(); + RO << llvm::formatv("{0:2}\n", JSONValue); +} + +json::Value GenerateSchema::UserDefinedProperty::toJSON() const { + return Value->toJSON(); +} + +json::Value GenerateSchema::PropertiesProperty::toJSON() const { + json::Object JSONObject; + for (UserDefinedProperty *Property : *this) { + json::Value JSONValue = Property->toJSON(); + JSONObject.try_emplace(Property->getName().data(), std::move(JSONValue)); + } + return JSONObject; +} + +json::Value GenerateSchema::AdditionalPropertiesProperty::toJSON() const { + return Value->toJSON(); +} + +json::Value GenerateSchema::RequiredProperty::toJSON() const { + json::Array JSONArray; + for (StringRef Value : *this) { + json::Value JSONValue(Value.data()); + JSONArray.emplace_back(std::move(JSONValue)); + } + return JSONArray; +} + +json::Value GenerateSchema::OptionalProperty::toJSON() const { + json::Array JSONArray; + for (StringRef Value : *this) { + json::Value JSONValue(Value.data()); + JSONArray.emplace_back(std::move(JSONValue)); + } + return JSONArray; +} + +json::Value GenerateSchema::TypeProperty::toJSON() const { + json::Value JSONValue(Value.data()); + return JSONValue; +} + +json::Value GenerateSchema::EnumProperty::toJSON() const { + json::Array JSONArray; + for (StringRef Value : *this) { + json::Value JSONValue(Value.data()); + JSONArray.emplace_back(std::move(JSONValue)); + } + return JSONArray; +} + +json::Value GenerateSchema::ItemsProperty::toJSON() const { + return Value->toJSON(); +} + +json::Value GenerateSchema::FlowStyleProperty::toJSON() const { + StringRef Value; + switch (Style) { + case FlowStyle::Block: + Value = "block"; + break; + case FlowStyle::Flow: + Value = "flow"; + break; + } + json::Value JSONValue(Value.data()); + return JSONValue; +} + +json::Value GenerateSchema::Schema::toJSON() const { + json::Object JSONObject; + for (SchemaProperty *Value : *this) { + json::Value JSONValue = Value->toJSON(); + StringRef Key = Value->getName(); + JSONObject.try_emplace(Key.data(), std::move(JSONValue)); + } + return JSONObject; +} diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp index 95a41eafdf5e4..6a0920efdbbcb 100644 --- a/llvm/lib/Support/YAMLTraits.cpp +++ b/llvm/lib/Support/YAMLTraits.cpp @@ -74,9 +74,9 @@ Input::~Input() = default; std::error_code Input::error() { return EC; } -bool Input::outputting() const { - return false; -} +IOKind Input::getKind() const { return IOKind::Inputting; } + +bool Input::outputting() const { return false; } bool Input::setCurrentDocument() { if (DocIterator != Strm->end()) { @@ -485,9 +485,9 @@ Output::Output(raw_ostream &yout, void *context, int WrapColumn) Output::~Output() = default; -bool Output::outputting() const { - return true; -} +IOKind Output::getKind() const { return IOKind::Outputting; } + +bool Output::outputting() const { return true; } void Output::beginMapping() { StateStack.push_back(inMapFirstKey); diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index 21f10eb610f11..850661de95264 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -112,6 +112,7 @@ add_llvm_unittest(SupportTests VirtualOutputFileTest.cpp WithColorTest.cpp YAMLIOTest.cpp + YAMLGenerateSchemaTest.cpp YAMLParserTest.cpp buffer_ostream_test.cpp formatted_raw_ostream_test.cpp diff --git a/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp new file mode 100644 index 0000000000000..82443c5745c9f --- /dev/null +++ b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// 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/YAMLGenerateSchema.h" +#include "llvm/Support/YAMLTraits.h" +#include +#include +#include + +#include "gtest/gtest.h" + +using namespace llvm; + +enum class ColorTy { + White, + Black, + Blue, +}; + +struct Baby { + std::string Name; + ColorTy Color; +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(Baby) + +struct Animal { + std::string Name; + std::optional Age; + std::vector Babies; +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(Animal) + +namespace llvm { +namespace yaml { + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &io, ColorTy &value) { + io.enumCase(value, "white", ColorTy::White); + io.enumCase(value, "black", ColorTy::Black); + io.enumCase(value, "blue", ColorTy::Blue); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, Baby &info) { + io.mapRequired("name", info.Name); + io.mapRequired("color", info.Color); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, Animal &info) { + io.mapRequired("name", info.Name); + io.mapOptional("age", info.Age); + io.mapOptional("babies", info.Babies); + } +}; + +} // namespace yaml +} // namespace llvm + +TEST(ObjectYAMLGenerateSchema, SimpleSchema) { + std::string String; + raw_string_ostream OS(String); + std::vector Animals; + yaml::GenerateSchema Gen(OS); + Gen << Animals; + StringRef YAMLSchema = R"({ + "flowStyle": "block", + "items": { + "flowStyle": "block", + "optional": [ + "age", + "babies" + ], + "properties": { + "age": { + "type": "string" + }, + "babies": { + "flowStyle": "block", + "items": { + "flowStyle": "block", + "properties": { + "color": { + "enum": [ + "white", + "black", + "blue" + ], + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "color" + ], + "type": "object" + }, + "type": "array" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" +} +)"; + EXPECT_EQ(String.c_str(), YAMLSchema.str()); +} diff --git a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn index df9ddf91f2c49..dd3932751a194 100644 --- a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn +++ b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn @@ -178,6 +178,7 @@ static_library("Support") { "WithColor.cpp", "YAMLParser.cpp", "YAMLTraits.cpp", + "YAMLGenerateSchema.cpp", "Z3Solver.cpp", "circular_raw_ostream.cpp", "raw_os_ostream.cpp",