aboutsummaryrefslogtreecommitdiffstats
path: root/tools/enum_parser
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /tools/enum_parser
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'tools/enum_parser')
-rw-r--r--tools/enum_parser/enum_parser/bin/ya.make23
-rw-r--r--tools/enum_parser/enum_parser/main.cpp471
-rw-r--r--tools/enum_parser/enum_parser/stdlib_deps.h5
-rw-r--r--tools/enum_parser/enum_parser/ya.make16
-rw-r--r--tools/enum_parser/enum_serialization_runtime/README.md2
-rw-r--r--tools/enum_parser/enum_serialization_runtime/enum_runtime.cpp100
-rw-r--r--tools/enum_parser/enum_serialization_runtime/enum_runtime.h116
-rw-r--r--tools/enum_parser/enum_serialization_runtime/ya.make13
-rw-r--r--tools/enum_parser/parse_enum/parse_enum.cpp422
-rw-r--r--tools/enum_parser/parse_enum/parse_enum.h78
-rw-r--r--tools/enum_parser/parse_enum/parse_enum_ut.cpp315
-rw-r--r--tools/enum_parser/parse_enum/ut/alias_before_name.h7
-rw-r--r--tools/enum_parser/parse_enum/ut/badcode.h10
-rw-r--r--tools/enum_parser/parse_enum/ut/enums.cpp195
-rw-r--r--tools/enum_parser/parse_enum/ut/enums.h195
-rw-r--r--tools/enum_parser/parse_enum/ut/enums_with_header.h8
-rw-r--r--tools/enum_parser/parse_enum/ut/including_header.h9
-rw-r--r--tools/enum_parser/parse_enum/ut/stringlist.cpp1
-rw-r--r--tools/enum_parser/parse_enum/ut/unbalanced.h4
-rw-r--r--tools/enum_parser/parse_enum/ut/ya.make33
-rw-r--r--tools/enum_parser/parse_enum/ya.make16
-rw-r--r--tools/enum_parser/ya.make6
22 files changed, 2045 insertions, 0 deletions
diff --git a/tools/enum_parser/enum_parser/bin/ya.make b/tools/enum_parser/enum_parser/bin/ya.make
new file mode 100644
index 0000000000..09e47ee1c0
--- /dev/null
+++ b/tools/enum_parser/enum_parser/bin/ya.make
@@ -0,0 +1,23 @@
+OWNER(
+ g:util
+ mvel
+)
+
+PROGRAM(enum_parser)
+
+SRCDIR(
+ tools/enum_parser/enum_parser
+)
+
+SRCS(
+ main.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/build/prebuilt/tools/enum_parser/enum_parser/ya.make.induced_deps)
+
+PEERDIR(
+ library/cpp/getopt/small
+ tools/enum_parser/parse_enum
+)
+
+END()
diff --git a/tools/enum_parser/enum_parser/main.cpp b/tools/enum_parser/enum_parser/main.cpp
new file mode 100644
index 0000000000..0943c69c1d
--- /dev/null
+++ b/tools/enum_parser/enum_parser/main.cpp
@@ -0,0 +1,471 @@
+#include <library/cpp/json/writer/json_value.h>
+#include <library/cpp/json/writer/json.h>
+#include <library/cpp/getopt/small/last_getopt.h>
+
+#include <tools/enum_parser/parse_enum/parse_enum.h>
+
+#include <util/stream/file.h>
+#include <util/stream/output.h>
+#include <util/stream/input.h>
+#include <util/stream/mem.h>
+
+#include <util/charset/wide.h>
+#include <util/string/builder.h>
+#include <util/string/strip.h>
+#include <util/string/cast.h>
+#include <util/string/join.h>
+#include <util/string/subst.h>
+#include <util/generic/map.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/ptr.h>
+#include <util/generic/yexception.h>
+#include <util/generic/maybe.h>
+#include <util/system/fs.h>
+#include <util/folder/path.h>
+
+void WriteHeader(const TString& headerName, IOutputStream& out, IOutputStream* headerOutPtr = nullptr) {
+ out << "// This file was auto-generated. Do not edit!!!\n";
+ out << "#include " << headerName << "\n";
+ out << "#include <tools/enum_parser/enum_serialization_runtime/enum_runtime.h>\n\n";
+ out << "#include <tools/enum_parser/enum_parser/stdlib_deps.h>\n\n";
+ out << "#include <util/generic/typetraits.h>\n";
+ out << "#include <util/generic/singleton.h>\n";
+ out << "#include <util/generic/string.h>\n";
+ out << "#include <util/generic/vector.h>\n";
+ out << "#include <util/generic/map.h>\n";
+ out << "#include <util/generic/serialized_enum.h>\n";
+ out << "#include <util/string/cast.h>\n";
+ out << "#include <util/stream/output.h>\n\n";
+
+ if (headerOutPtr) {
+ auto& outHeader = *headerOutPtr;
+ outHeader << "// This file was auto-generated. Do not edit!!!\n";
+ outHeader << "#pragma once\n\n";
+ outHeader << "#include <util/generic/serialized_enum.h>\n";
+ outHeader << "#include " << headerName << "\n";
+ }
+}
+
+static inline void JsonEscape(TString& s) {
+ SubstGlobal(s, "\\", "\\\\");
+ SubstGlobal(s, "\"", "\\\"");
+ SubstGlobal(s, "\r", "\\r");
+ SubstGlobal(s, "\n", "\\n");
+ SubstGlobal(s, "\t", "\\t");
+}
+
+static inline TString JsonQuote(const TString& s) {
+ TString quoted = s;
+ JsonEscape(quoted);
+ return "\"" + quoted + "\""; // do not use .Quote() here, it performs escaping!
+}
+
+
+/// Simplifed JSON map encoder for generic types
+template<typename T>
+void OutKey(IOutputStream& out, const TString& key, const T& value, bool escape = true) {
+ TString quoted = ToString(value);
+ if (escape) {
+ quoted = JsonQuote(quoted);
+ }
+ out << "\"" << key << "\": " << quoted << ",\n";
+}
+
+/// Simplifed JSON map encoder for TMaybe
+void OutKey(IOutputStream& out, const TString& key, const TMaybe<TString>& value) {
+ TString quoted;
+ if (value) {
+ quoted = JsonQuote(ToString(*value));
+ } else {
+ quoted = "null";
+ }
+ out << "\"" << key << "\": " << quoted << ",\n";
+}
+
+
+/// Simplifed JSON map encoder for bool values
+void OutKey(IOutputStream& out, const TString& key, const bool& value) {
+ out << "\"" << key << "\": " << (value ? "true" : "false") << ",\n";
+}
+
+
+/// Simplifed JSON map encoder for array items
+template<typename T>
+void OutItem(IOutputStream& out, const T& value, bool escape = true) {
+ TString quoted = ToString(value);
+ if (escape) {
+ quoted = JsonQuote(quoted);
+ }
+ out << quoted << ",\n";
+}
+
+/// Cut trailing ",\n" or ","
+static inline void FinishItems(TStringStream& out) {
+ TString& s = out.Str();
+ if (s.EndsWith(",\n")) {
+ s.remove(s.size() - 2, 2);
+ }
+ if (s.EndsWith(",")) {
+ s.pop_back();
+ }
+}
+
+
+static inline void OpenMap(TStringStream& out) {
+ out << "{\n";
+}
+
+static inline void CloseMap(TStringStream& out) {
+ out << "}\n";
+}
+
+static inline void OpenArray(TStringStream& out) {
+ out << "[\n";
+}
+
+static inline void CloseArray(TStringStream& out) {
+ out << "]\n";
+}
+
+static TString WrapStringBuf(const TStringBuf str) {
+ return TString::Join("TStringBuf(\"", str, "\")");
+}
+
+void GenerateEnum(
+ const TEnumParser::TEnum& en,
+ IOutputStream& out,
+ IOutputStream* jsonEnumOut = nullptr,
+ IOutputStream* headerOutPtr = nullptr
+) {
+ TStringStream jEnum;
+ OpenMap(jEnum);
+
+ size_t count = en.Items.size();
+ OutKey(jEnum, "count", count);
+ const TString name = TEnumParser::ScopeStr(en.Scope) + en.CppName;
+ OutKey(jEnum, "full_name", name);
+ OutKey(jEnum, "cpp_name", en.CppName);
+ TStringStream scopeJson;
+ OpenArray(scopeJson);
+ for (const auto& scopeItem : en.Scope) {
+ OutItem(scopeJson, scopeItem);
+ }
+ FinishItems(scopeJson);
+ CloseArray(scopeJson);
+
+ OutKey(jEnum, "scope", scopeJson.Str(), false);
+ OutKey(jEnum, "enum_class", en.EnumClass);
+
+ TEnumParser::TScope outerScope = en.Scope;
+ if (en.EnumClass) {
+ outerScope.push_back(en.CppName);
+ }
+
+ TString outerScopeStr = TEnumParser::ScopeStr(outerScope);
+
+ TString cName = name;
+ SubstGlobal(cName, "::", "");
+
+ out << "// I/O for " << name << "\n";
+
+ TString nsName = "N" + cName + "Private";
+
+ out << "namespace { namespace " << nsName << " {\n";
+
+ TVector<TString> nameInitializerPairs;
+ TVector<TString> valueInitializerPairs;
+ TVector<TString> cppNamesInitializer;
+
+ TStringStream jItems;
+ OpenArray(jItems);
+
+ for (const auto& it : en.Items) {
+ TStringStream jEnumItem;
+ OpenMap(jEnumItem);
+
+ OutKey(jEnumItem, "cpp_name", it.CppName);
+ OutKey(jEnumItem, "value", it.Value);
+ OutKey(jEnumItem, "comment_text", it.CommentText);
+
+ TStringStream jAliases;
+ OpenArray(jAliases);
+
+ TString strValue = it.CppName;
+ if (it.Aliases) {
+ // first alias is main
+ strValue = it.Aliases[0];
+ OutKey(jEnumItem, "str_value", strValue);
+ }
+ nameInitializerPairs.push_back("TNameBufs::EnumStringPair(" + outerScopeStr + it.CppName + ", " + WrapStringBuf(strValue) + ")");
+ cppNamesInitializer.push_back(WrapStringBuf(it.CppName));
+
+ for (const auto& alias : it.Aliases) {
+ valueInitializerPairs.push_back("TNameBufs::EnumStringPair(" + outerScopeStr + it.CppName + ", " + WrapStringBuf(alias) + ")");
+ OutItem(jAliases, alias);
+ }
+ FinishItems(jAliases);
+ CloseArray(jAliases);
+
+ if (!it.Aliases) {
+ valueInitializerPairs.push_back("TNameBufs::EnumStringPair(" + outerScopeStr + it.CppName + ", " + WrapStringBuf(it.CppName) + ")");
+ }
+ OutKey(jEnumItem, "aliases", jAliases.Str(), false);
+
+ FinishItems(jEnumItem);
+ CloseMap(jEnumItem);
+
+ OutItem(jItems, jEnumItem.Str(), false);
+ }
+ FinishItems(jItems);
+ CloseArray(jItems);
+ OutKey(jEnum, "items", jItems.Str(), false);
+
+ auto defineConstArray = [&out, payloadCache = TMap<std::pair<TString, TVector<TString>>, TString>()](const TStringBuf indent, const TStringBuf elementType, const TStringBuf name, const TVector<TString>& items) mutable {
+ if (items.empty()) { // ISO C++ forbids zero-size array
+ out << indent << "static constexpr const TArrayRef<const " << elementType << "> " << name << ";\n";
+ } else {
+ // try to reuse one of the previous payload arrays
+ const auto inserted = payloadCache.emplace(std::make_pair(elementType, items), ToString(name) + "_PAYLOAD");
+ const TString& payloadStorageName = inserted.first->second;
+ if (inserted.second) { // new array content or type
+ out << indent << "static constexpr const " << elementType << " " << payloadStorageName << "[" << items.size() << "]{\n";
+ for (const auto& it : items) {
+ out << indent << " " << it << ",\n";
+ }
+ out << indent << "};\n";
+ }
+ out << indent << "static constexpr const TArrayRef<const " << elementType << "> " << name << "{" << payloadStorageName << "};\n";
+ }
+ out << "\n";
+ };
+
+ out << " class TNameBufs : public ::NEnumSerializationRuntime::TEnumDescription<" << name << "> {\n";
+ out << " public:\n";
+ out << " using TBase = ::NEnumSerializationRuntime::TEnumDescription<" << name << ">;\n\n";
+ out << " inline TNameBufs();\n\n";
+
+ // Instance
+ out << " static inline const TNameBufs& Instance() {\n";
+ out << " return *SingletonWithPriority<TNameBufs, 0>();\n"; // destroy enum serializers last, because it may be used from destructor of another global object
+ out << " }\n";
+ out << " };\n\n";
+
+ // Initialization data
+ defineConstArray(" ", "TNameBufs::TBase::TEnumStringPair", "NAMES_INITIALIZATION_PAIRS", nameInitializerPairs);
+ defineConstArray(" ", "TNameBufs::TBase::TEnumStringPair", "VALUES_INITIALIZATION_PAIRS", valueInitializerPairs);
+ defineConstArray(" ", "TStringBuf", "CPP_NAMES_INITIALIZATION_ARRAY", cppNamesInitializer);
+
+ out << " static constexpr const TNameBufs::TInitializationData ENUM_INITIALIZATION_DATA{\n";
+ out << " NAMES_INITIALIZATION_PAIRS,\n";
+ out << " VALUES_INITIALIZATION_PAIRS,\n";
+ out << " CPP_NAMES_INITIALIZATION_ARRAY,\n";
+ out << " " << WrapStringBuf(outerScopeStr) << ",\n";
+ out << " " << WrapStringBuf(name) << "\n";
+ out << " };\n\n";
+
+ // Constructor
+ out << " inline TNameBufs::TNameBufs()\n";
+ out << " : TBase(ENUM_INITIALIZATION_DATA)\n";
+ out << " {\n";
+ out << " }\n\n";
+
+ out << "}}\n\n";
+
+ if (headerOutPtr) {
+ (*headerOutPtr) << "// I/O for " << name << "\n";
+ }
+
+ // outer ToString
+ if (headerOutPtr) {
+ (*headerOutPtr) << "const TString& ToString(" << name << ");\n";
+ }
+ out << "const TString& ToString(" << name << " x) {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.ToString(x);\n";
+ out << "}\n\n";
+
+ // outer FromString
+ if (headerOutPtr) {
+ (*headerOutPtr) << "bool FromString(const TString& name, " << name << "& ret);\n";
+ }
+ out << "bool FromString(const TString& name, " << name << "& ret) {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.FromString(name, ret);\n";
+ out << "}\n\n";
+
+ // outer FromString
+ if (headerOutPtr) {
+ (*headerOutPtr) << "bool FromString(const TStringBuf& name, " << name << "& ret);\n";
+ }
+ out << "bool FromString(const TStringBuf& name, " << name << "& ret) {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.FromString(name, ret);\n";
+ out << "}\n\n";
+
+ // specialization for internal FromStringImpl
+ out << "template<>\n";
+ out << name << " FromStringImpl<" << name << ">(const char* data, size_t len) {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.FromString(TStringBuf(data, len));\n";
+ out << "}\n\n";
+
+ // specialization for internal TryFromStringImpl
+ out << "template<>\n";
+ out << "bool TryFromStringImpl<" << name << ">(const char* data, size_t len, " << name << "& result) {\n";
+ out << " return FromString(TStringBuf(data, len), result);\n";
+ out << "}\n\n";
+
+ // outer Out
+ out << "template<>\n";
+ out << "void Out<" << name << ">(IOutputStream& os, TTypeTraits<" << name << ">::TFuncParam n) {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.Out(&os, n);\n";
+ out << "}\n\n";
+
+ // specializations for NEnumSerializationRuntime function family
+ out << "namespace NEnumSerializationRuntime {\n";
+ // template<> GetEnumAllValues
+ out << " template<>\n";
+ out << " TMappedArrayView<" << name <<"> GetEnumAllValuesImpl<" << name << ">() {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.AllEnumValues();\n";
+ out << " }\n\n";
+
+ // template<> GetEnumAllNames
+ out << " template<>\n";
+ out << " const TString& GetEnumAllNamesImpl<" << name << ">() {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.AllEnumNames();\n";
+ out << " }\n\n";
+
+ // template<> GetEnumNames<EnumType>
+ out << " template<>\n";
+ out << " TMappedDictView<" << name << ", TString> GetEnumNamesImpl<" << name << ">() {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.EnumNames();\n";
+ out << " }\n\n";
+
+ // template<> GetEnumAllCppNames, see IGNIETFERRO-534
+ out << " template<>\n";
+ out << " const TVector<TString>& GetEnumAllCppNamesImpl<" << name << ">() {\n";
+ out << " const " << nsName << "::TNameBufs& names = " << nsName << "::TNameBufs::Instance();\n";
+ out << " return names.AllEnumCppNames();\n";
+ out << " }\n";
+
+ out << "}\n\n";
+
+ if (headerOutPtr) {
+ // <EnumType>Count
+ auto& outHeader = *headerOutPtr;
+ outHeader << "template <>\n";
+ outHeader << "constexpr size_t GetEnumItemsCount<" << name << ">() {\n";
+ outHeader << " return " << en.Items.size() << ";\n";
+ outHeader << "}\n";
+ }
+
+ FinishItems(jEnum);
+ jEnum << "}\n";
+
+ if (jsonEnumOut) {
+ *jsonEnumOut << jEnum.Str();
+ }
+}
+
+int main(int argc, char** argv) {
+ try {
+ using namespace NLastGetopt;
+ TOpts opts = NLastGetopt::TOpts::Default();
+ opts.AddHelpOption();
+
+ TString outputFileName;
+ TString outputHeaderFileName;
+ TString outputJsonFileName;
+ TString includePath;
+ opts.AddLongOption('o', "output").OptionalArgument("<output-file>").StoreResult(&outputFileName)
+ .Help(
+ "Output generated code to specified file.\n"
+ "When not set, standard output is used."
+ );
+ opts.AddLongOption('h', "header").OptionalArgument("<output-header>").StoreResult(&outputHeaderFileName)
+ .Help(
+ "Generate appropriate header to specified file.\n"
+ "Works only if output file specified."
+ );
+ opts.AddLongOption("include-path").OptionalArgument("<header-path>").StoreResult(&includePath)
+ .Help(
+ "Include input header using this path in angle brackets.\n"
+ "When not set, header basename is used in double quotes."
+ );
+
+ opts.AddLongOption('j', "json-output").OptionalArgument("<json-output>").StoreResult(&outputJsonFileName)
+ .Help(
+ "Generate enum data in JSON format."
+ );
+
+ opts.SetFreeArgsNum(1);
+ opts.SetFreeArgTitle(0, "<input-file>", "Input header file with enum declarations");
+
+ TOptsParseResult res(&opts, argc, argv);
+
+ TVector<TString> freeArgs = res.GetFreeArgs();
+ TString inputFileName = freeArgs[0];
+
+ THolder<IOutputStream> hOut;
+ IOutputStream* out = &Cout;
+
+ THolder<IOutputStream> headerOut;
+
+ THolder<IOutputStream> jsonOut;
+
+
+ if (outputFileName) {
+ NFs::Remove(outputFileName);
+ hOut.Reset(new TFileOutput(outputFileName));
+ out = hOut.Get();
+
+ if (outputHeaderFileName) {
+ headerOut.Reset(new TFileOutput(outputHeaderFileName));
+ }
+
+ if (outputJsonFileName) {
+ jsonOut.Reset(new TFileOutput(outputJsonFileName));
+ }
+ }
+
+ if (!includePath) {
+ includePath = TString() + '"' + TFsPath(inputFileName).Basename() + '"';
+ } else {
+ includePath = TString() + '<' + includePath + '>';
+ }
+
+ TEnumParser parser(inputFileName);
+ WriteHeader(includePath, *out, headerOut.Get());
+
+ TStringStream jEnums;
+ OpenArray(jEnums);
+
+ for (const auto& en : parser.Enums) {
+ if (!en.CppName) {
+ // skip unnamed enum declarations
+ continue;
+ }
+
+ TStringStream jEnum;
+ GenerateEnum(en, *out, &jEnum, headerOut.Get());
+ OutItem(jEnums, jEnum.Str(), false);
+ }
+ FinishItems(jEnums);
+ CloseArray(jEnums);
+
+ if (jsonOut) {
+ *jsonOut << jEnums.Str() << Endl;
+ }
+
+ return 0;
+ } catch (...) {
+ Cerr << CurrentExceptionMessage() << Endl;
+ }
+
+ return 1;
+}
diff --git a/tools/enum_parser/enum_parser/stdlib_deps.h b/tools/enum_parser/enum_parser/stdlib_deps.h
new file mode 100644
index 0000000000..a28e2bf927
--- /dev/null
+++ b/tools/enum_parser/enum_parser/stdlib_deps.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <array>
+#include <initializer_list>
+#include <utility>
diff --git a/tools/enum_parser/enum_parser/ya.make b/tools/enum_parser/enum_parser/ya.make
new file mode 100644
index 0000000000..82bac4e0f7
--- /dev/null
+++ b/tools/enum_parser/enum_parser/ya.make
@@ -0,0 +1,16 @@
+OWNER(
+ g:util
+ mvel
+)
+
+IF (USE_PREBUILT_TOOLS)
+ INCLUDE(${ARCADIA_ROOT}/build/prebuilt/tools/enum_parser/enum_parser/ya.make.prebuilt)
+ENDIF()
+
+IF (NOT PREBUILT)
+ INCLUDE(${ARCADIA_ROOT}/tools/enum_parser/enum_parser/bin/ya.make)
+ENDIF()
+
+RECURSE(
+ bin
+)
diff --git a/tools/enum_parser/enum_serialization_runtime/README.md b/tools/enum_parser/enum_serialization_runtime/README.md
new file mode 100644
index 0000000000..7d2cbae4ce
--- /dev/null
+++ b/tools/enum_parser/enum_serialization_runtime/README.md
@@ -0,0 +1,2 @@
+This library should not be used or referred directly.
+Use `GENERATE_ENUM_SERIALIZATION_WITH_HEADER` and `GENERATE_ENUM_SERIALIZATION` macros instead.
diff --git a/tools/enum_parser/enum_serialization_runtime/enum_runtime.cpp b/tools/enum_parser/enum_serialization_runtime/enum_runtime.cpp
new file mode 100644
index 0000000000..73d38b396a
--- /dev/null
+++ b/tools/enum_parser/enum_serialization_runtime/enum_runtime.cpp
@@ -0,0 +1,100 @@
+#include "enum_runtime.h"
+
+#include <util/generic/map.h>
+#include <util/generic/yexception.h>
+#include <util/stream/output.h>
+
+namespace NEnumSerializationRuntime {
+ template <typename TEnumRepresentationType>
+ [[noreturn]] static void ThrowUndefinedValueException(const TEnumRepresentationType key, const TStringBuf className) {
+ throw yexception() << "Undefined value " << key << " in " << className << ". ";
+ }
+
+ template <typename TEnumRepresentationType>
+ const TString& TEnumDescriptionBase<TEnumRepresentationType>::ToString(TRepresentationType key) const {
+ const auto it = Names.find(key);
+ if (Y_LIKELY(it != Names.end())) {
+ return it->second;
+ }
+ ThrowUndefinedValueException(key, ClassName);
+ }
+
+ template <typename TEnumRepresentationType>
+ std::pair<bool, TEnumRepresentationType> TEnumDescriptionBase<TEnumRepresentationType>::TryFromString(const TStringBuf name) const {
+ const auto it = Values.find(name);
+ if (it != Values.end()) {
+ return {true, it->second};
+ }
+ return {false, TRepresentationType()};
+ }
+
+ [[noreturn]] static void ThrowUndefinedNameException(const TStringBuf name, const TStringBuf className, const TStringBuf allEnumNames) {
+ ythrow yexception() << "Key '" << name << "' not found in enum " << className << ". Valid options are: " << allEnumNames << ". ";
+ }
+
+ template <typename TEnumRepresentationType>
+ auto TEnumDescriptionBase<TEnumRepresentationType>::FromString(const TStringBuf name) const -> TRepresentationType {
+ const auto findResult = TryFromString(name);
+ if (Y_LIKELY(findResult.first)) {
+ return findResult.second;
+ }
+ ThrowUndefinedNameException(name, ClassName, AllEnumNames());
+ }
+
+ template <typename TEnumRepresentationType>
+ void TEnumDescriptionBase<TEnumRepresentationType>::Out(IOutputStream* os, const TRepresentationType key) const {
+ (*os) << this->ToString(key);
+ }
+
+ template <typename TEnumRepresentationType>
+ TEnumDescriptionBase<TEnumRepresentationType>::TEnumDescriptionBase(const TInitializationData& enumInitData)
+ : ClassName(enumInitData.ClassName)
+ {
+ const TArrayRef<const TEnumStringPair>& namesInitializer = enumInitData.NamesInitializer;
+ const TArrayRef<const TEnumStringPair>& valuesInitializer = enumInitData.ValuesInitializer;
+ const TArrayRef<const TStringBuf>& cppNamesInitializer = enumInitData.CppNamesInitializer;
+
+ TMap<TRepresentationType, TString> mapValueToName;
+ TMap<TString, TRepresentationType> mapNameToValue;
+ const bool bijectiveHint = (namesInitializer.data() == valuesInitializer.data() && namesInitializer.size() == valuesInitializer.size());
+ if (bijectiveHint) {
+ for (const TEnumStringPair& it : namesInitializer) {
+ TString name{it.Name};
+ mapValueToName.emplace(it.Key, name);
+ mapNameToValue.emplace(std::move(name), it.Key);
+ }
+ } else {
+ for (const TEnumStringPair& it : namesInitializer) {
+ mapValueToName.emplace(it.Key, TString(it.Name));
+ }
+ for (const TEnumStringPair& it : valuesInitializer) {
+ mapNameToValue.emplace(TString(it.Name), it.Key);
+ }
+ }
+ Names = std::move(mapValueToName);
+ Values = std::move(mapNameToValue);
+
+ AllValues.reserve(Names.size());
+ for (const auto& it : Names) {
+ if (!AllNames.empty()) {
+ AllNames += ", ";
+ }
+ AllNames += TString::Join('\'', it.second, '\'');
+ AllValues.push_back(it.first);
+ }
+
+ AllCppNames.reserve(cppNamesInitializer.size());
+ for (const auto& cn : cppNamesInitializer) {
+ AllCppNames.push_back(TString::Join(enumInitData.CppNamesPrefix, cn));
+ }
+ }
+
+ template <typename TEnumRepresentationType>
+ TEnumDescriptionBase<TEnumRepresentationType>::~TEnumDescriptionBase() = default;
+
+ // explicit instantiation
+ template class TEnumDescriptionBase<int>;
+ template class TEnumDescriptionBase<unsigned>;
+ template class TEnumDescriptionBase<long long>;
+ template class TEnumDescriptionBase<unsigned long long>;
+}
diff --git a/tools/enum_parser/enum_serialization_runtime/enum_runtime.h b/tools/enum_parser/enum_serialization_runtime/enum_runtime.h
new file mode 100644
index 0000000000..f157d43109
--- /dev/null
+++ b/tools/enum_parser/enum_serialization_runtime/enum_runtime.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <util/generic/array_ref.h>
+#include <util/generic/strbuf.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/serialized_enum.h>
+
+#include <utility>
+
+class IOutputStream;
+
+namespace NEnumSerializationRuntime {
+ /// Stores all information about enumeration except its real type
+ template <typename TEnumRepresentationType>
+ class TEnumDescriptionBase {
+ public:
+ using TRepresentationType = TEnumRepresentationType;
+
+ struct TEnumStringPair {
+ const TRepresentationType Key;
+ const TStringBuf Name;
+ };
+
+ /// Refers initialization data stored in constexpr-friendly format
+ struct TInitializationData {
+ const TArrayRef<const TEnumStringPair> NamesInitializer;
+ const TArrayRef<const TEnumStringPair> ValuesInitializer;
+ const TArrayRef<const TStringBuf> CppNamesInitializer;
+ const TStringBuf CppNamesPrefix;
+ const TStringBuf ClassName;
+ };
+
+ public:
+ TEnumDescriptionBase(const TInitializationData& enumInitData);
+ ~TEnumDescriptionBase();
+
+ const TString& ToString(TRepresentationType key) const;
+ std::pair<bool, TRepresentationType> TryFromString(const TStringBuf name) const;
+ TRepresentationType FromString(const TStringBuf name) const;
+ void Out(IOutputStream* os, const TRepresentationType key) const;
+
+ const TString& AllEnumNames() const noexcept {
+ return AllNames;
+ }
+
+ const TVector<TString>& AllEnumCppNames() const noexcept {
+ return AllCppNames;
+ }
+
+ const TMap<TRepresentationType, TString>& TypelessEnumNames() const noexcept {
+ return Names;
+ }
+
+ const TVector<TRepresentationType>& TypelessEnumValues() const noexcept {
+ return AllValues;
+ }
+
+ private:
+ TMap<TRepresentationType, TString> Names;
+ TMap<TString, TRepresentationType> Values;
+ TString AllNames;
+ TVector<TString> AllCppNames;
+ TString ClassName;
+ TVector<TRepresentationType> AllValues;
+ };
+
+ /// Wraps TEnumDescriptionBase and performs on-demand casts
+ template <typename EEnum, typename TEnumRepresentationType = typename NDetail::TSelectEnumRepresentationType<EEnum>::TType>
+ class TEnumDescription: public NDetail::TMappedViewBase<EEnum, TEnumRepresentationType>, private TEnumDescriptionBase<TEnumRepresentationType> {
+ public:
+ using TBase = TEnumDescriptionBase<TEnumRepresentationType>;
+ using TCast = NDetail::TMappedViewBase<EEnum, TEnumRepresentationType>;
+ using TBase::AllEnumCppNames;
+ using TBase::AllEnumNames;
+ using typename TBase::TEnumStringPair;
+ using typename TBase::TRepresentationType;
+ using typename TBase::TInitializationData;
+
+ public:
+ using TBase::TBase;
+
+ const TString& ToString(const EEnum key) const {
+ return TBase::ToString(TCast::CastToRepresentationType(key));
+ }
+
+ bool FromString(const TStringBuf name, EEnum& ret) const {
+ const auto findResult = TBase::TryFromString(name);
+ if (findResult.first) {
+ ret = TCast::CastFromRepresentationType(findResult.second);
+ return true;
+ }
+ return false;
+ }
+
+ EEnum FromString(const TStringBuf name) const {
+ return TCast::CastFromRepresentationType(TBase::FromString(name));
+ }
+
+ TMappedDictView<EEnum, TString> EnumNames() const noexcept {
+ return {TBase::TypelessEnumNames()};
+ }
+
+ TMappedArrayView<EEnum> AllEnumValues() const noexcept {
+ return {TBase::TypelessEnumValues()};
+ }
+
+ void Out(IOutputStream* os, const EEnum key) const {
+ TBase::Out(os, TCast::CastToRepresentationType(key));
+ }
+
+ static constexpr TEnumStringPair EnumStringPair(const EEnum key, const TStringBuf name) noexcept {
+ return {TCast::CastToRepresentationType(key), name};
+ }
+ };
+}
diff --git a/tools/enum_parser/enum_serialization_runtime/ya.make b/tools/enum_parser/enum_serialization_runtime/ya.make
new file mode 100644
index 0000000000..c18e4aee10
--- /dev/null
+++ b/tools/enum_parser/enum_serialization_runtime/ya.make
@@ -0,0 +1,13 @@
+LIBRARY()
+
+OWNER(
+ g:util
+ swarmer
+)
+
+SRCS(
+ enum_runtime.cpp
+)
+
+
+END()
diff --git a/tools/enum_parser/parse_enum/parse_enum.cpp b/tools/enum_parser/parse_enum/parse_enum.cpp
new file mode 100644
index 0000000000..3db0d7a4d9
--- /dev/null
+++ b/tools/enum_parser/parse_enum/parse_enum.cpp
@@ -0,0 +1,422 @@
+#include "parse_enum.h"
+
+#include <library/cpp/cppparser/parser.h>
+
+#include <util/stream/file.h>
+#include <util/stream/output.h>
+#include <util/stream/input.h>
+#include <util/stream/mem.h>
+
+#include <util/charset/wide.h>
+#include <util/string/strip.h>
+#include <util/string/cast.h>
+#include <util/generic/map.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/ptr.h>
+#include <util/generic/yexception.h>
+
+/**
+ * Parse C-style strings inside multiline comments
+ **/
+class TValuesContext: public TCppFullSax {
+public:
+ void DoString(const TText& text) override {
+ Values.push_back(text.Data);
+ }
+
+ ~TValuesContext() override {
+ }
+
+ TVector<TString> Values;
+};
+
+static TVector<TString> ParseEnumValues(const TString& strValues) {
+ TVector<TString> result;
+
+ TValuesContext ctx;
+ TCppSaxParser parser(&ctx);
+ TMemoryInput in(strValues.data(), strValues.size());
+ TransferData(static_cast<IInputStream*>(&in), &parser);
+ parser.Finish();
+ for (const auto& value : ctx.Values) {
+ Y_ENSURE(value.size() >= 2, "Invalid C-style string. ");
+ TString dequoted = value.substr(1, value.size() - 2);
+ // TODO: support C-unescaping
+ result.push_back(dequoted);
+ }
+ return result;
+}
+
+/**
+ * Parse C++ fragment with one enum
+ **/
+class TEnumContext: public TCppFullSax {
+public:
+ typedef TEnumParser::TItem TItem;
+ typedef TEnumParser::TEnum TEnum;
+
+ TEnumContext(TEnum& currentEnum)
+ : CurrentEnum(currentEnum)
+ {
+ }
+
+ ~TEnumContext() override {
+ }
+
+ void AddEnumItem() {
+ if (!CurrentItem.CppName) {
+ // uninitialized element should have no value too
+ Y_ASSERT(!CurrentItem.Value.Defined());
+ return;
+ }
+
+ // enum item C++ name should not be empty
+ Y_ASSERT(CurrentItem.CppName);
+ CurrentItem.NormalizeValue();
+ CurrentEnum.Items.push_back(CurrentItem);
+ CurrentItem.Clear();
+ InEnumState = Begin;
+ }
+
+ template<class T>
+ void AppendValue(const T& text) {
+ // by pg@ advice, do not parse enum value
+ // leave it to C++ compiler to parse/interpret
+
+ if (!CurrentItem.Value)
+ CurrentItem.Value = TString();
+
+ *CurrentItem.Value += text;
+ }
+
+ void DoEnd() override {
+ AddEnumItem();
+ }
+
+ void DoWhiteSpace(const TText& text) override {
+ if (InValue == InEnumState || InValueCall == InEnumState) {
+ AppendValue(text.Data);
+ }
+ }
+
+ void DoSyntax(const TText& text) override {
+ // For some reason, parser sometimes passes chunks like '{};' here,
+ // so we handle each symbol separately.
+ for (const char& sym : text.Data) {
+ if ('{' == sym && InValue != InEnumState && InValueCall != InEnumState) {
+ BodyDetected = true;
+ continue;
+ } else if ('=' == sym && InValueCall != InEnumState) {
+ InEnumState = InValue;
+ continue;
+ } else if (('(' == sym || '{' == sym) && (InValue == InEnumState || InValueCall == InEnumState)) {
+ // there may be constexpr function / constructor / macro call in value part,
+ // handle them appropriately
+ InEnumState = InValueCall;
+ ++BracesBalance;
+ AppendValue(sym);
+ continue;
+ } else if ((')' == sym || '}' == sym) && InValueCall == InEnumState) {
+ if (!--BracesBalance) {
+ InEnumState = InValue;
+ }
+ AppendValue(sym);
+ continue;
+ } else if ((',' == sym || '}' == sym) && InValueCall != InEnumState) {
+ AddEnumItem();
+ continue;
+ } else if (InValue == InEnumState || InValueCall == InEnumState) {
+ AppendValue(sym);
+ }
+ }
+ }
+
+ void DoName(const TText& text) override {
+ if (!BodyDetected) {
+ return;
+ }
+
+ if (InValue == InEnumState || InValueCall == InEnumState) {
+ AppendValue(text.Data);
+ return;
+ }
+
+ CurrentItem.CppName = text.Data;
+ InEnumState = AfterCppName;
+ }
+
+ void DoMultiLineComment(const TText& text) override {
+ Y_ENSURE(text.Data.size() >= 4, "Invalid multiline comment " << text.Data.Quote() << ". ");
+ TString commentText = text.Data.substr(2, text.Data.size() - 4);
+ commentText = StripString(commentText);
+ CurrentItem.CommentText = commentText;
+ CurrentItem.Aliases = ParseEnumValues(commentText);
+
+ if (CurrentItem.Aliases && !CurrentItem.CppName) {
+ // this means we process multiline comment when item name was not set yet.
+ ythrow yexception() << "Are you hit with https://clubs.at.yandex-team.ru/stackoverflow/2603 typo? ";
+ }
+ }
+
+ bool BodyDetected = false;
+ enum EInEnumState {
+ Begin,
+ AfterCppName,
+ InValue,
+ InValueCall,
+ End,
+ };
+ EInEnumState InEnumState = Begin;
+
+ TEnum& CurrentEnum;
+ TItem CurrentItem;
+
+ size_t BracesBalance = 0;
+};
+
+/**
+ * Parse C++ file
+ **/
+class TCppContext: public TCppFullSax {
+public:
+ typedef TEnumParser::TScope TScope;
+ typedef TEnumParser::TItem TItem;
+ typedef TEnumParser::TEnum TEnum;
+ typedef TEnumParser::TEnums TEnums;
+
+ const TString NAMESPACE = "<namespace>";
+ const TString CLASS = "<class>";
+ const TString STRUCT = "<struct>";
+ const TString ENUM = "<enum>";
+ const TString BLOCK = "<block>";
+
+ TCppContext(const char* data, const TString& sourceFileName = TString())
+ : Data(data)
+ , SourceFileName(sourceFileName)
+ {
+ }
+
+ ~TCppContext() override {
+ }
+
+ void DoSyntax(const TText& text) override {
+ // For some reason, parser sometimes passes chunks like '{};' here,
+ // so we handle each symbol separately.
+ const TString& syn = text.Data;
+ if (syn == "::" && InCompositeNamespace) {
+ LastScope += syn;
+ InCompositeNamespace = false;
+ ScopeDeclaration = true;
+ return;
+ }
+ for (size_t i = 0; i < syn.size(); ++i) {
+ if ('{' == syn[i]) {
+ OnEnterScope(text.Offset + i);
+ if (InEnum) {
+ CurrentEnum.BodyDetected = true;
+ }
+ } else if ('}' == syn[i]) {
+ OnLeaveScope(text.Offset + i);
+ } else if (';' == syn[i]) {
+ // Handle SEARCH-1392
+ if (InEnum && !CurrentEnum.BodyDetected) {
+ CurrentEnum.ForwardDeclaration = true;
+ InEnum = false;
+ }
+ }
+ }
+ }
+
+ void DoKeyword(const TText& text) override {
+ if (text.Data == "enum") {
+ Y_ENSURE(!InEnum, "Enums cannot be nested. ");
+ InEnum = true;
+ EnumPos = text.Offset;
+ CurrentEnum.Clear();
+ CurrentEnum.Scope = Scope;
+ ScopeDeclaration = true;
+ NextScopeName = ENUM;
+ //PrintScope();
+ } else if (text.Data == "class") {
+ if (InEnum) {
+ CurrentEnum.EnumClass = true;
+ return;
+ }
+ NextScopeName = CLASS;
+ ScopeDeclaration = true;
+ //PrintScope();
+ } else if (text.Data == "struct") {
+ if (InEnum) {
+ CurrentEnum.EnumClass = true;
+ return;
+ }
+ NextScopeName = STRUCT;
+ ScopeDeclaration = true;
+ //PrintScope();
+ } else if (text.Data == "namespace") {
+ NextScopeName = NAMESPACE;
+ LastScope.clear();
+ ScopeDeclaration = true;
+ //PrintScope();
+ }
+ }
+
+ void DoName(const TText& text) override {
+ if (!ScopeDeclaration) {
+ return;
+ }
+ if (InEnum) {
+ CurrentEnum.CppName = text.Data;
+ } else {
+ if (NextScopeName == NAMESPACE) {
+ InCompositeNamespace = true;
+ LastScope += text.Data;
+ } else {
+ LastScope = text.Data;
+ }
+ }
+ ScopeDeclaration = false;
+ }
+
+ void OnEnterScope(size_t /* offset */) {
+ if (ScopeDeclaration) {
+ // unnamed declaration or typedef
+ ScopeDeclaration = false;
+ }
+ InCompositeNamespace = false;
+ Scope.push_back(LastScope);
+ LastScope.clear();
+ //PrintScope();
+ }
+
+ /// @param offset: terminating curly brace position
+ void OnLeaveScope(size_t offset) {
+ if (!Scope) {
+ size_t contextOffsetBegin = (offset >= 256) ? offset - 256 : 0;
+ TString codeContext = TString(Data + contextOffsetBegin, offset - contextOffsetBegin + 1);
+ ythrow yexception() << "C++ source parse failed: unbalanced scope. Did you miss a closing '}' bracket? "
+ "Context: enum " << CurrentEnum.CppName.Quote() <<
+ " in scope " << TEnumParser::ScopeStr(CurrentEnum.Scope).Quote() << ". Code context:\n... " <<
+ codeContext << " ...";
+ }
+ Scope.pop_back();
+
+ if (InEnum) {
+ Y_ASSERT(offset > EnumPos);
+ InEnum = false;
+ try {
+ ParseEnum(Data + EnumPos, offset - EnumPos + 1);
+ } catch (...) {
+ TString ofFile;
+ if (SourceFileName) {
+ ofFile += " of file ";
+ ofFile += SourceFileName.Quote();
+ }
+ ythrow yexception() << "Failed to parse enum " << CurrentEnum.CppName <<
+ " in scope " << TEnumParser::ScopeStr(CurrentEnum.Scope) << ofFile <<
+ "\n<C++ parser error message>: " << CurrentExceptionMessage();
+ }
+ }
+ //PrintScope();
+ }
+
+ void ParseEnum(const char* data, size_t length) {
+ TEnumContext enumContext(CurrentEnum);
+ TMemoryInput in(data, length);
+ TCppSaxParser parser(&enumContext);
+ TransferData(&in, &parser);
+ parser.Finish();
+ //PrintEnum(CurrentEnum);
+ Enums.push_back(CurrentEnum);
+ }
+
+ // Some debug stuff goes here
+ static void PrintScope(const TScope& scope) {
+ Cerr << "Current scope: " << TEnumParser::ScopeStr(scope) << Endl;
+ }
+
+ void PrintScope() {
+ PrintScope(Scope);
+ }
+
+ void PrintEnum(const TEnum& en) {
+ Cerr << "Enum within scope " << TEnumParser::ScopeStr(en.Scope).Quote() << Endl;
+ for (const auto& item : en.Items) {
+ Cerr << " " << item.CppName;
+ if (item.Value)
+ Cerr << " = " << *item.Value;
+ Cerr << Endl;
+ for (const auto& value : item.Aliases) {
+ Cerr << " " << value << Endl;
+ }
+ }
+ }
+
+ void PrintEnums() {
+ for (const auto& en : Enums)
+ PrintEnum(en);
+ }
+
+public:
+ TScope Scope;
+ TEnums Enums;
+private:
+ const char* const Data;
+ TString SourceFileName;
+
+ bool InEnum = false;
+ bool ScopeDeclaration = false;
+ bool InCompositeNamespace = false;
+ TString NextScopeName = BLOCK;
+ TString LastScope;
+ size_t EnumPos = 0;
+ TEnum CurrentEnum;
+};
+
+
+TEnumParser::TEnumParser(const TString& fileName) {
+ THolder<IInputStream> hIn;
+ IInputStream* in = nullptr;
+ if (fileName != "-") {
+ SourceFileName = fileName;
+ hIn.Reset(new TFileInput(fileName));
+ in = hIn.Get();
+ } else {
+ in = &Cin;
+ }
+ TString contents = in->ReadAll();
+ Parse(contents.data(), contents.size());
+}
+
+TEnumParser::TEnumParser(const char* data, size_t length) {
+ Parse(data, length);
+}
+
+TEnumParser::TEnumParser(IInputStream& in) {
+ TString contents = in.ReadAll();
+ Parse(contents.data(), contents.size());
+}
+
+void TEnumParser::Parse(const char* data, size_t length) {
+ const TStringBuf span(data, length);
+ const bool hasPragmaOnce = span.Contains("#pragma once");
+ const bool isProtobufHeader = span.Contains("// Generated by the protocol buffer compiler");
+ const bool isFlatbuffersHeader = span.Contains("// automatically generated by the FlatBuffers compiler");
+ Y_ENSURE(
+ hasPragmaOnce || isProtobufHeader || isFlatbuffersHeader,
+ "Serialization functions can be generated only for enums in header files, see SEARCH-975. "
+ );
+ TCppContext cppContext(data, SourceFileName);
+ TMemoryInput in(data, length);
+ TCppSaxParser parser(&cppContext);
+ TransferData(&in, &parser);
+ parser.Finish();
+ //cppContext.PrintEnums();
+ // obtain result
+ Enums = cppContext.Enums;
+ if (cppContext.Scope) {
+ cppContext.PrintScope();
+ ythrow yexception() << "Unbalanced scope, something is wrong with enum parser. ";
+ }
+}
diff --git a/tools/enum_parser/parse_enum/parse_enum.h b/tools/enum_parser/parse_enum/parse_enum.h
new file mode 100644
index 0000000000..ef8b512ae4
--- /dev/null
+++ b/tools/enum_parser/parse_enum/parse_enum.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <util/stream/output.h>
+#include <util/stream/input.h>
+#include <util/stream/mem.h>
+#include <util/string/strip.h>
+#include <util/generic/maybe.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+
+class TEnumParser {
+public:
+
+ struct TItem {
+ TMaybe<TString> Value;
+ TString CppName;
+ TVector<TString> Aliases;
+ TString CommentText;
+
+ void Clear() {
+ *this = TItem();
+ }
+
+ void NormalizeValue() {
+ if (!Value)
+ return;
+ StripInPlace(*Value);
+ }
+
+ };
+
+ // vector is to preserve declaration order
+ typedef TVector<TItem> TItems;
+
+ typedef TVector<TString> TScope;
+
+ struct TEnum {
+ TItems Items;
+ TString CppName;
+ TScope Scope;
+ // enum or enum class
+ bool EnumClass = false;
+ bool BodyDetected = false;
+ bool ForwardDeclaration = false;
+
+ void Clear() {
+ *this = TEnum();
+ }
+ };
+
+ typedef TVector<TEnum> TEnums;
+
+ /// Parse results stored here
+ TEnums Enums;
+
+ /// Parse enums from file containing C++ code
+ TEnumParser(const TString& fileName);
+
+ /// Parse enums from memory buffer containing C++ code
+ TEnumParser(const char* data, size_t length);
+
+ /// Parse enums from input stream
+ TEnumParser(IInputStream& in);
+
+ static TString ScopeStr(const TScope& scope) {
+ TString result;
+ for (const TString& name : scope) {
+ result += name;
+ result += "::";
+ }
+ return result;
+ }
+
+private:
+ void Parse(const char* data, size_t length);
+protected:
+ TString SourceFileName;
+};
diff --git a/tools/enum_parser/parse_enum/parse_enum_ut.cpp b/tools/enum_parser/parse_enum/parse_enum_ut.cpp
new file mode 100644
index 0000000000..21ed6a2fc4
--- /dev/null
+++ b/tools/enum_parser/parse_enum/parse_enum_ut.cpp
@@ -0,0 +1,315 @@
+#include <library/cpp/resource/resource.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <tools/enum_parser/parse_enum/parse_enum.h>
+
+typedef TEnumParser::TEnum TEnum;
+typedef TEnumParser::TEnums TEnums;
+typedef TEnumParser::TItems TItems;
+
+Y_UNIT_TEST_SUITE(TEnumParserTest) {
+
+ Y_UNIT_TEST(MainTest) {
+ TString text = NResource::Find("/enums");
+ TMemoryInput input(text.data(), text.size());
+ TEnumParser parser(input);
+ const TEnums& enums = parser.Enums;
+
+ UNIT_ASSERT_VALUES_EQUAL(enums.size(), 16u);
+
+ // check ESimple
+ {
+ const TEnum& e = enums[0];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "ESimple");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Http");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[0].Value.Defined());
+
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "Https");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[1].Value.Defined());
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "ItemCount");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[2].Value.Defined());
+ }
+
+ // ESimpleWithComma
+ {
+ const TEnum& e = enums[1];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "ESimpleWithComma");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 4u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Http");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "3");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 0u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "Http2");
+ UNIT_ASSERT(it[1].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "Http");
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "Https");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[2].Value.Defined());
+
+ UNIT_ASSERT_VALUES_EQUAL(it[3].CppName, "ItemCount");
+ UNIT_ASSERT_VALUES_EQUAL(it[3].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[3].Value.Defined());
+ }
+
+ // check ECustomAliases
+ {
+ const TEnum& e = enums[2];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "ECustomAliases");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "CAHttp");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "3");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases[0], "http");
+
+ UNIT_ASSERT(!it[1].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "CAHttps");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases[0], "https");
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "CAItemCount");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].Aliases.size(), 0u);
+ }
+
+ // check EMultipleAliases
+ {
+ const TEnum& e = enums[3];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EMultipleAliases");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "MAHttp");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "9");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases[0], "http://");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases[1], "secondary");
+ // yes, quoted values are NOT decoded, it is a known (minor) bug
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases[2], "old\\nvalue");
+
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "MAHttps");
+ UNIT_ASSERT(it[1].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "1");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases[0], "https://");
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "MAItemCount");
+ UNIT_ASSERT(!it[2].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(it[2].Aliases.size(), 0u);
+ }
+
+ // check NEnumNamespace::EInNamespace
+ {
+ const TEnum& e = enums[4];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[0], "NEnumNamespace");
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EInNamespace");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Http");
+ UNIT_ASSERT(it[0].Value.Defined());
+ }
+
+ // check NEnumNamespace::TEnumClass::EInClass
+ {
+ const TEnum& e = enums[5];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 2u);
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[0], "NEnumNamespace");
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[1], "TEnumClass");
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EInClass");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Http");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "9");
+
+ UNIT_ASSERT(it[1].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "NEnumNamespace::Https");
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "Https3");
+ UNIT_ASSERT(it[2].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[2].Value, "1 + 2");
+ }
+
+ // check unnamed enum (no code should be generated for it)
+ {
+ const TEnum& e = enums[6];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ }
+
+ // TEXT_WEIGHT
+ {
+ const TEnum& e = enums[7];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "TEXT_WEIGHT");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 5u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "WEIGHT_ZERO");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "-1");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 0u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "WEIGHT_LOW");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[1].Value.Defined());
+
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "WEIGHT_NORMAL");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].Aliases.size(), 0u);
+ UNIT_ASSERT(!it[2].Value.Defined());
+ }
+
+ // EDuplicateKeys
+ {
+ const TEnum& e = enums[8];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EDuplicateKeys");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 5u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Key0");
+ UNIT_ASSERT(it[0].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "0");
+ UNIT_ASSERT_VALUES_EQUAL(it[0].Aliases.size(), 0u);
+
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "Key0Second");
+ UNIT_ASSERT(it[1].Value.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "Key0");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].Aliases.size(), 0u);
+ }
+
+ // EEmpty
+ {
+ const TEnum& e = enums[10];
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 0u);
+ }
+
+ // NComposite::NInner::EInCompositeNamespaceSimple
+ {
+ const TEnum& e = enums[11];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[0], "NComposite::NInner");
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EInCompositeNamespaceSimple");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "one");
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "2") ;
+ }
+
+ // NOuterSimple::NComposite::NMiddle::NInner::NInnerSimple::TEnumClass::EVeryDeep
+ {
+ const TEnum& e = enums[12];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 4u);
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[0], "NOuterSimple");
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[1], "NComposite::NMiddle::NInner");
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[2], "NInnerSimple");
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope[3], "TEnumClass");
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "EVeryDeep");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 2u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Key0");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "Key1");
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "1");
+ }
+
+ // ENonLiteralValues
+ {
+ const TEnum& e = enums[13];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "ENonLiteralValues");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "one");
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "MACRO(1, 2)");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "two");
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "2");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "three");
+ UNIT_ASSERT_VALUES_EQUAL(*it[2].Value, "func(3)");
+ UNIT_ASSERT_VALUES_EQUAL(it[3].CppName, "four");
+ UNIT_ASSERT_VALUES_EQUAL(it[3].Value.Defined(), false);
+ UNIT_ASSERT_VALUES_EQUAL(it[4].CppName, "five");
+ UNIT_ASSERT_VALUES_EQUAL(it[4].Value, "MACRO(MACRO(1, 2), 2)");
+ }
+
+ // NotifyingStatus
+ {
+ const TEnum& e = enums[15];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 0u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "NotifyingStatus");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 4u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "NEW");
+ UNIT_ASSERT_VALUES_EQUAL(*it[0].Value, "0");
+ UNIT_ASSERT_VALUES_EQUAL(it[1].CppName, "FAILED_WILL_RETRY");
+ UNIT_ASSERT_VALUES_EQUAL(*it[1].Value, "1");
+ UNIT_ASSERT_VALUES_EQUAL(it[2].CppName, "FAILED_NO_MORE_TRIALS");
+ UNIT_ASSERT_VALUES_EQUAL(*it[2].Value, "2");
+ UNIT_ASSERT_VALUES_EQUAL(it[3].CppName, "SENT");
+ UNIT_ASSERT_VALUES_EQUAL(*it[3].Value, "3");
+ }
+ }
+
+ Y_UNIT_TEST(BadCodeParseTest) {
+ TString text = NResource::Find("/badcode");
+ TMemoryInput input(text.data(), text.size());
+ TEnumParser parser(input);
+ const TEnums& enums = parser.Enums;
+
+ UNIT_ASSERT_VALUES_EQUAL(enums.size(), 1u);
+
+ // check <anonymous namespace>::ETest correct parsing
+ {
+ const TEnum& e = enums[0];
+ UNIT_ASSERT_VALUES_EQUAL(e.Scope.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(e.CppName, "ETest");
+ const TItems& it = e.Items;
+ UNIT_ASSERT_VALUES_EQUAL(it.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(it[0].CppName, "Http");
+ UNIT_ASSERT(it[0].Value.Defined());
+ }
+
+ }
+
+ Y_UNIT_TEST(UnbalancedCodeParseTest) {
+ // Thanks gotmanov@ for providing this example
+ TString text = NResource::Find("/unbalanced");
+ TMemoryInput input(text.data(), text.size());
+ try {
+ TEnumParser parser(input);
+ UNIT_ASSERT(false);
+ } catch(...) {
+ UNIT_ASSERT(CurrentExceptionMessage().Contains("unbalanced scope. Did you miss a closing"));
+ }
+ }
+
+ Y_UNIT_TEST(AliasBeforeNameTest) {
+ TString text = NResource::Find("/alias_before_name");
+ TMemoryInput input(text.data(), text.size());
+ try {
+ TEnumParser parser(input);
+ UNIT_ASSERT(false);
+ } catch(...) {
+ UNIT_ASSERT(CurrentExceptionMessage().Contains("https://clubs.at.yandex-team.ru/stackoverflow/2603"));
+ }
+ }
+}
diff --git a/tools/enum_parser/parse_enum/ut/alias_before_name.h b/tools/enum_parser/parse_enum/ut/alias_before_name.h
new file mode 100644
index 0000000000..64015c1db6
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/alias_before_name.h
@@ -0,0 +1,7 @@
+#pragma once
+
+// https://clubs.at.yandex-team.ru/stackoverflow/2603 bad example
+enum EStrange {
+ One, /* "one" */
+ Two, /* "two" */
+};
diff --git a/tools/enum_parser/parse_enum/ut/badcode.h b/tools/enum_parser/parse_enum/ut/badcode.h
new file mode 100644
index 0000000000..88448c8eae
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/badcode.h
@@ -0,0 +1,10 @@
+#pragma once
+
+// Anonymous namespaces are meaningless, but should not break our parser
+namespace {
+ enum ETest {
+ Http = 9 /* "http://" "secondary" "old\nvalue" */,
+ Https = 1 /* "https://" */,
+ ETestItemCount,
+ };
+}
diff --git a/tools/enum_parser/parse_enum/ut/enums.cpp b/tools/enum_parser/parse_enum/ut/enums.cpp
new file mode 100644
index 0000000000..a03045855e
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/enums.cpp
@@ -0,0 +1,195 @@
+#include "enums.h"
+#include "enums_with_header.h"
+#include <tools/enum_parser/parse_enum/ut/enums_with_header.h_serialized.h>
+
+#include "including_header.h"
+
+// just to test that generated stuff works
+#include <util/generic/serialized_enum.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/ptr.h>
+#include <util/generic/singleton.h>
+
+
+void FunctionUsingEFwdEnum(EFwdEnum) {
+}
+
+class TEnumSerializationInitializer {
+public:
+ TEnumSerializationInitializer() {
+ UNIT_ASSERT_VALUES_EQUAL(ToString(EDestructionPriorityTest::first), "first");
+ }
+ ~TEnumSerializationInitializer() {
+ UNIT_ASSERT_VALUES_EQUAL(ToString(EDestructionPriorityTest::second), "second");
+ }
+};
+
+class TEnumSerializationInitializerHolder {
+public:
+ TEnumSerializationInitializerHolder() {
+ }
+
+ ~TEnumSerializationInitializerHolder() {
+ }
+
+ void Init() { Ptr.Reset(new TEnumSerializationInitializer); }
+private:
+ THolder<TEnumSerializationInitializer> Ptr;
+};
+
+
+Y_UNIT_TEST_SUITE(TEnumGeneratorTest) {
+
+ template<typename T>
+ void CheckToString(const T& value, const TString& strValue) {
+ UNIT_ASSERT_VALUES_EQUAL(ToString(value), strValue);
+ }
+
+ Y_UNIT_TEST(ToStringTest) {
+ // ESimple
+ CheckToString(Http, "Http");
+ CheckToString(Https, "Https");
+ CheckToString(ItemCount, "ItemCount");
+
+ // ESimpleWithComma
+ CheckToString(ESimpleWithComma::Http, "Http");
+ CheckToString(ESimpleWithComma::Https, "Https");
+ CheckToString(ESimpleWithComma::Http2, "Http"); // Http2 is an alias for Http
+ CheckToString(ESimpleWithComma::ItemCount, "ItemCount");
+
+ // ECustomAliases
+ CheckToString(CAHttp, "http");
+ CheckToString(CAHttps, "https");
+ CheckToString(CAItemCount, "CAItemCount");
+
+ // EMultipleAliases
+ CheckToString(MAHttp, "http://");
+ CheckToString(MAHttps, "https://");
+ CheckToString(MAItemCount, "MAItemCount");
+
+ // EDuplicateKeys
+ CheckToString(Key0, "Key0");
+ CheckToString(Key0Second, "Key0"); // obtain FIRST encountered value with such integer key
+ CheckToString(Key1, "Key1");
+ CheckToString(Key2, "k2");
+ CheckToString(Key3, "k2"); // we CANNOT obtain "k3" here (as Key3 == Key2)
+ }
+
+ template<typename T>
+ void CheckFromString(const TString& strValue, const T& value) {
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(FromString<T>(TStringBuf(strValue))), static_cast<int>(value));
+ }
+
+ template<typename T>
+ void CheckFromStringFail(const TString& strValue) {
+ UNIT_ASSERT_EXCEPTION(FromString<T>(TStringBuf(strValue)), yexception);
+ }
+
+ template<typename T>
+ void CheckTryFromString(const TString& strValue, const T& value) {
+ T x;
+ UNIT_ASSERT_VALUES_EQUAL(TryFromString(TStringBuf(strValue), x), true);
+ UNIT_ASSERT_VALUES_EQUAL(x, value);
+ }
+
+ template<typename T>
+ void CheckTryFromStringFail(const TString& strValue) {
+ T x = T(-666);
+ UNIT_ASSERT_VALUES_EQUAL(TryFromString(TStringBuf(strValue), x), false);
+ UNIT_ASSERT_VALUES_EQUAL(int(x), -666);
+ }
+
+ Y_UNIT_TEST(TryFromStringTest) {
+ // ESimple
+ CheckFromString("Http", Http);
+ CheckFromString("Https", Https);
+ CheckFromString("ItemCount", ItemCount);
+ CheckFromStringFail<ESimple>("ItemC0unt");
+
+ CheckTryFromString("Http", Http);
+ CheckTryFromString("Https", Https);
+ CheckTryFromString("ItemCount", ItemCount);
+ CheckTryFromStringFail<ESimple>("ItemC0unt");
+
+ // ESimpleWithComma
+ CheckTryFromString("Http", ESimpleWithComma::Http);
+ CheckTryFromString("Https", ESimpleWithComma::Https);
+ CheckTryFromString("ItemCount", ESimpleWithComma::ItemCount);
+ CheckTryFromStringFail<ESimpleWithComma>("");
+
+ // ECustomAliases
+ CheckTryFromString("http", CAHttp);
+ CheckTryFromString("https", CAHttps);
+ CheckTryFromString("CAItemCount", CAItemCount);
+
+ // EDuplicateKeys
+ CheckTryFromString("Key0", Key0);
+ CheckTryFromString("Key0Second", Key0Second);
+ CheckTryFromString("Key1", Key1);
+ CheckTryFromString("k2", Key2);
+ CheckTryFromString("k2.1", Key2);
+ CheckTryFromString("k3", Key3);
+ }
+
+ Y_UNIT_TEST(AllNamesValuesTest) {
+ {
+ auto allNames = GetEnumAllCppNames<EDuplicateKeys>();
+ UNIT_ASSERT(!!allNames);
+ UNIT_ASSERT_VALUES_EQUAL(allNames.size(), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(allNames[4], "Key3");
+ }
+ {
+ auto allNames = GetEnumAllCppNames<ESimpleWithComma>();
+ UNIT_ASSERT(!!allNames);
+ UNIT_ASSERT_VALUES_EQUAL(allNames.size(), 4u);
+ UNIT_ASSERT_VALUES_EQUAL(allNames[1], "ESimpleWithComma::Http2");
+ }
+ }
+
+ Y_UNIT_TEST(EnumWithHeaderTest) {
+ UNIT_ASSERT_VALUES_EQUAL(GetEnumItemsCount<EWithHeader>(), 3);
+ }
+
+ Y_UNIT_TEST(AllNamesValuesWithHeaderTest) {
+ {
+ auto allNames = GetEnumAllCppNames<EWithHeader>();
+ UNIT_ASSERT_VALUES_EQUAL(allNames.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(allNames.at(2), "HThree");
+ }
+ {
+ UNIT_ASSERT_VALUES_EQUAL(GetEnumAllNames<EWithHeader>(), "'one', 'HTwo', 'HThree'");
+ }
+ }
+
+ Y_UNIT_TEST(AllValuesTest) {
+ const auto& allNames = GetEnumNames<EWithHeader>();
+ const auto& allValues = GetEnumAllValues<EWithHeader>();
+ UNIT_ASSERT_VALUES_EQUAL(allValues.size(), 3u);
+ UNIT_ASSERT_VALUES_EQUAL(allValues[2], HThree);
+ size_t size = 0;
+ for (const EWithHeader value : GetEnumAllValues<EWithHeader>()) {
+ size += 1;
+ UNIT_ASSERT_VALUES_EQUAL(allNames.contains(value), true);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(size, 3u);
+ }
+
+ Y_UNIT_TEST(EnumNamesTest) {
+ const auto& names = GetEnumNames<EWithHeader>();
+ UNIT_ASSERT_VALUES_EQUAL(names.size(), 3u);
+
+ UNIT_ASSERT(names.contains(HOne));
+ UNIT_ASSERT_VALUES_EQUAL(names.at(HOne), "one");
+
+ UNIT_ASSERT(names.contains(HTwo));
+ UNIT_ASSERT_VALUES_EQUAL(names.at(HTwo), "HTwo");
+
+ UNIT_ASSERT(names.contains(HThree));
+ UNIT_ASSERT_VALUES_EQUAL(names.at(HThree), "HThree");
+ }
+
+ Y_UNIT_TEST(EnumSerializerDestructionPriority) {
+ Singleton<TEnumSerializationInitializerHolder>()->Init();
+ }
+};
diff --git a/tools/enum_parser/parse_enum/ut/enums.h b/tools/enum_parser/parse_enum/ut/enums.h
new file mode 100644
index 0000000000..93d835c78d
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/enums.h
@@ -0,0 +1,195 @@
+#pragma once
+// Sample file for parse_enum unittests
+
+#include <util/generic/fwd.h>
+#include <util/system/compiler.h>
+
+// Test template declarations
+template<class T>
+void Func(T&);
+
+template<>
+void Func(struct ENonDeclared&);
+
+template<class TClass> class TFwdDecl;
+
+// Test in-function class declarations
+void InexistentFunction(struct TFwdStructDecl);
+void InexistentFunction2(struct TFwdStructDecl, class TMegaClass);
+
+
+static inline void Func() {
+ class TLocal {
+ int M;
+ public:
+ void F() {
+ // to shut up clang
+ Y_UNUSED(M);
+ }
+ };
+
+ {
+ // unnamed block
+ }
+}
+
+// Test forward declarations, pt 2
+namespace NTestContainer {
+ struct TStruct;
+}
+
+// Enums
+enum ESimple {
+ Http,
+ Https,
+ ItemCount
+};
+
+enum class ESimpleWithComma {
+ Http = 3,
+ Http2 = Http,
+ Https, // 4
+ ItemCount, // 5
+};
+
+enum ECustomAliases {
+ CAHttp = 3 /* "http" */,
+ CAHttps /* "https" */,
+ CAItemCount,
+};
+
+enum EMultipleAliases {
+ MAHttp = 9 /* "http://" "secondary" "old\nvalue" */,
+ MAHttps = 1 /* "https://" */,
+ MAItemCount,
+};
+
+namespace NEnumNamespace {
+ enum EInNamespace {
+ Http = 9 /* "http://" "secondary" "old\nvalue" */,
+ Https = 1 /* "https://" */,
+ ItemCount /* "real value" */,
+ };
+};
+
+struct TStruct {
+ int M;
+};
+
+namespace NEnumNamespace {
+ class TEnumClass: public TStruct {
+ public:
+ enum EInClass {
+ Http = 9 /* "http://" "secondary" "old\nvalue" */,
+ Https1 = NEnumNamespace::Https /* "https://" */,
+ // holy crap, this will work too:
+ Https3 = 1 /* "https://" */ + 2,
+ };
+ };
+}
+
+enum {
+ One,
+ Two,
+ Three,
+};
+
+struct {
+ int M;
+} SomeStruct;
+
+static inline void f() {
+ (void)(SomeStruct);
+ (void)(f);
+}
+
+// buggy case taken from library/cpp/html/face/parstypes.h
+enum TEXT_WEIGHT {
+ WEIGHT_ZERO=-1,// NOINDEX_RELEV
+ WEIGHT_LOW, // LOW_RELEV
+ WEIGHT_NORMAL, // NORMAL_RELEV
+ WEIGHT_HIGH, // HIGH_RELEV (H1,H2,H3,ADDRESS,CAPTION)
+ WEIGHT_BEST // BEST_RELEV (TITLE)
+};
+
+// enum with duplicate keys
+enum EDuplicateKeys {
+ Key0 = 0,
+ Key0Second = Key0,
+ Key1,
+ Key2 = 3 /* "k2" "k2.1" */,
+ Key3 = 3 /* "k3" */,
+};
+
+enum class EFwdEnum;
+void FunctionUsingEFwdEnum(EFwdEnum);
+enum class EFwdEnum {
+ One,
+ Two
+};
+
+// empty enum (bug found by sankear@)
+enum EEmpty {
+};
+
+namespace NComposite::NInner {
+ enum EInCompositeNamespaceSimple {
+ one,
+ two = 2,
+ three,
+ };
+}
+
+namespace NOuterSimple {
+ namespace NComposite::NMiddle::NInner {
+ namespace NInnerSimple {
+ class TEnumClass {
+ public:
+ enum EVeryDeep {
+ Key0 = 0,
+ Key1 = 1,
+ };
+ };
+ }
+ }
+}
+
+
+constexpr int func(int value) {
+ return value;
+}
+
+#define MACRO(x, y) x
+
+// enum with nonliteral values
+enum ENonLiteralValues {
+ one = MACRO(1, 2),
+ two = 2,
+ three = func(3),
+ four,
+ five = MACRO(MACRO(1, 2), 2),
+};
+
+#undef MACRO
+
+
+enum EDestructionPriorityTest {
+ first,
+ second
+};
+
+
+enum class NotifyingStatus
+{
+ NEW = 0,
+ FAILED_WILL_RETRY = 1,
+ FAILED_NO_MORE_TRIALS = 2,
+ SENT = 3
+};
+
+/*
+ * Still unsupported features:
+ *
+ * a) Anonymous namespaces (it is parsed correctly, though)
+ * b) Enums inside template classes (impossible by design)
+ **/
diff --git a/tools/enum_parser/parse_enum/ut/enums_with_header.h b/tools/enum_parser/parse_enum/ut/enums_with_header.h
new file mode 100644
index 0000000000..26fe5565a9
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/enums_with_header.h
@@ -0,0 +1,8 @@
+#pragma once
+
+enum EWithHeader {
+ HOne /* "one" */,
+ HTwo,
+ HThree,
+};
+
diff --git a/tools/enum_parser/parse_enum/ut/including_header.h b/tools/enum_parser/parse_enum/ut/including_header.h
new file mode 100644
index 0000000000..b3b2a2129f
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/including_header.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <util/generic/serialized_enum.h>
+#include <tools/enum_parser/parse_enum/ut/enums_with_header.h_serialized.h>
+
+int TestEnumWithHeader() {
+ return GetEnumItemsCount<EWithHeader>();
+}
+
diff --git a/tools/enum_parser/parse_enum/ut/stringlist.cpp b/tools/enum_parser/parse_enum/ut/stringlist.cpp
new file mode 100644
index 0000000000..f69d0fc08d
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/stringlist.cpp
@@ -0,0 +1 @@
+"qqqq\nqqqqqq", "test:\\string"
diff --git a/tools/enum_parser/parse_enum/ut/unbalanced.h b/tools/enum_parser/parse_enum/ut/unbalanced.h
new file mode 100644
index 0000000000..9caf54044c
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/unbalanced.h
@@ -0,0 +1,4 @@
+#pragma once
+
+}
+
diff --git a/tools/enum_parser/parse_enum/ut/ya.make b/tools/enum_parser/parse_enum/ut/ya.make
new file mode 100644
index 0000000000..03ace866d4
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ut/ya.make
@@ -0,0 +1,33 @@
+UNITTEST()
+
+OWNER(
+ g:util
+ mvel
+)
+
+PEERDIR(
+ ADDINCL tools/enum_parser/parse_enum
+ library/cpp/resource
+)
+
+SRCDIR(tools/enum_parser/parse_enum)
+
+RESOURCE(
+ enums.h /enums
+ badcode.h /badcode
+ unbalanced.h /unbalanced
+ alias_before_name.h /alias_before_name
+)
+
+# self-test
+GENERATE_ENUM_SERIALIZATION(enums.h)
+
+# test GENERATE_ENUM_SERIALIZATION_WITH_HEADER macro
+GENERATE_ENUM_SERIALIZATION_WITH_HEADER(enums_with_header.h)
+
+SRCS(
+ parse_enum_ut.cpp
+ enums.cpp
+)
+
+END()
diff --git a/tools/enum_parser/parse_enum/ya.make b/tools/enum_parser/parse_enum/ya.make
new file mode 100644
index 0000000000..b8d07c66d2
--- /dev/null
+++ b/tools/enum_parser/parse_enum/ya.make
@@ -0,0 +1,16 @@
+LIBRARY()
+
+OWNER(
+ g:util
+ mvel
+)
+
+SRCS(
+ parse_enum.cpp
+)
+
+PEERDIR(
+ library/cpp/cppparser
+)
+
+END()
diff --git a/tools/enum_parser/ya.make b/tools/enum_parser/ya.make
new file mode 100644
index 0000000000..762b282b3e
--- /dev/null
+++ b/tools/enum_parser/ya.make
@@ -0,0 +1,6 @@
+RECURSE(
+ parse_enum
+ parse_enum/ut
+ enum_parser
+ enum_serialization_runtime
+)