format_analyser.h 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #pragma once
  2. #include "format_arg.h"
  3. #include <util/generic/strbuf.h>
  4. #include <array>
  5. #include <string_view>
  6. namespace NYT::NDetail {
  7. ////////////////////////////////////////////////////////////////////////////////
  8. struct TFormatAnalyser
  9. {
  10. public:
  11. // TODO(arkady-e1ppa): Until clang-19 consteval functions
  12. // defined out of line produce symbols in rare cases
  13. // causing linker to crash.
  14. template <class... TArgs>
  15. static consteval void ValidateFormat(std::string_view fmt)
  16. {
  17. DoValidateFormat<TArgs...>(fmt);
  18. }
  19. private:
  20. // Non-constexpr function call will terminate compilation.
  21. // Purposefully undefined and non-constexpr/consteval
  22. template <class T>
  23. static void CrashCompilerNotFormattable(std::string_view /*msg*/)
  24. { /*Suppress "internal linkage but undefined" warning*/ }
  25. static void CrashCompilerNotEnoughArguments(std::string_view /*msg*/)
  26. { }
  27. static void CrashCompilerTooManyArguments(std::string_view /*msg*/)
  28. { }
  29. static void CrashCompilerWrongTermination(std::string_view /*msg*/)
  30. { }
  31. static void CrashCompilerMissingTermination(std::string_view /*msg*/)
  32. { }
  33. static void CrashCompilerWrongFlagSpecifier(std::string_view /*msg*/)
  34. { }
  35. static consteval bool Contains(std::string_view sv, char symbol)
  36. {
  37. return sv.find(symbol) != std::string_view::npos;
  38. }
  39. struct TSpecifiers
  40. {
  41. std::string_view Conversion;
  42. std::string_view Flags;
  43. };
  44. template <class TArg>
  45. static consteval auto GetSpecifiers()
  46. {
  47. if constexpr (!CFormattable<TArg>) {
  48. CrashCompilerNotFormattable<TArg>("Your specialization of TFormatArg is broken");
  49. }
  50. return TSpecifiers{
  51. .Conversion = std::string_view{
  52. std::data(TFormatArg<TArg>::ConversionSpecifiers),
  53. std::size(TFormatArg<TArg>::ConversionSpecifiers)},
  54. .Flags = std::string_view{
  55. std::data(TFormatArg<TArg>::FlagSpecifiers),
  56. std::size(TFormatArg<TArg>::FlagSpecifiers)},
  57. };
  58. }
  59. static constexpr char IntroductorySymbol = '%';
  60. template <class... TArgs>
  61. static consteval void DoValidateFormat(std::string_view format)
  62. {
  63. std::array<std::string_view, sizeof...(TArgs)> markers = {};
  64. std::array<TSpecifiers, sizeof...(TArgs)> specifiers{GetSpecifiers<TArgs>()...};
  65. int markerCount = 0;
  66. int currentMarkerStart = -1;
  67. for (int index = 0; index < std::ssize(format); ++index) {
  68. auto symbol = format[index];
  69. // Parse verbatim text.
  70. if (currentMarkerStart == -1) {
  71. if (symbol == IntroductorySymbol) {
  72. // Marker maybe begins.
  73. currentMarkerStart = index;
  74. }
  75. continue;
  76. }
  77. // NB: We check for %% first since
  78. // in order to verify if symbol is a specifier
  79. // we need markerCount to be within range of our
  80. // specifier array.
  81. if (symbol == IntroductorySymbol) {
  82. if (currentMarkerStart + 1 != index) {
  83. // '%a% detected'
  84. CrashCompilerWrongTermination("You may not terminate flag sequence other than %% with \'%\' symbol");
  85. return;
  86. }
  87. // '%%' detected --- skip
  88. currentMarkerStart = -1;
  89. continue;
  90. }
  91. // We are inside of marker.
  92. if (markerCount == std::ssize(markers)) {
  93. // To many markers
  94. CrashCompilerNotEnoughArguments("Number of arguments supplied to format is smaller than the number of flag sequences");
  95. return;
  96. }
  97. if (Contains(specifiers[markerCount].Conversion, symbol)) {
  98. // Marker has finished.
  99. markers[markerCount]
  100. = std::string_view(format.begin() + currentMarkerStart, index - currentMarkerStart + 1);
  101. currentMarkerStart = -1;
  102. ++markerCount;
  103. continue;
  104. }
  105. if (!Contains(specifiers[markerCount].Flags, symbol)) {
  106. CrashCompilerWrongFlagSpecifier("Symbol is not a valid flag specifier; See FlagSpecifiers");
  107. }
  108. }
  109. if (currentMarkerStart != -1) {
  110. // Runaway marker.
  111. CrashCompilerMissingTermination("Unterminated flag sequence detected; Use \'%%\' to type plain %");
  112. return;
  113. }
  114. if (markerCount < std::ssize(markers)) {
  115. // Missing markers.
  116. CrashCompilerTooManyArguments("Number of arguments supplied to format is greater than the number of flag sequences");
  117. return;
  118. }
  119. // TODO(arkady-e1ppa): Consider per-type verification
  120. // of markers.
  121. }
  122. };
  123. ////////////////////////////////////////////////////////////////////////////////
  124. } // namespace NYT::NDetail