format_analyser.h 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  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. static consteval bool Contains(std::string_view sv, char symbol)
  21. {
  22. return sv.find(symbol) != std::string_view::npos;
  23. }
  24. struct TSpecifiers
  25. {
  26. std::string_view Conversion;
  27. std::string_view Flags;
  28. };
  29. template <class TArg>
  30. static consteval auto GetSpecifiers()
  31. {
  32. return TSpecifiers{
  33. .Conversion = std::string_view{
  34. std::data(TFormatArg<TArg>::ConversionSpecifiers),
  35. std::size(TFormatArg<TArg>::ConversionSpecifiers)},
  36. .Flags = std::string_view{
  37. std::data(TFormatArg<TArg>::FlagSpecifiers),
  38. std::size(TFormatArg<TArg>::FlagSpecifiers)},
  39. };
  40. }
  41. static constexpr char IntroductorySymbol = '%';
  42. template <class... TArgs>
  43. static consteval void DoValidateFormat(std::string_view format)
  44. {
  45. std::array<std::string_view, sizeof...(TArgs)> markers = {};
  46. std::array<TSpecifiers, sizeof...(TArgs)> specifiers{GetSpecifiers<TArgs>()...};
  47. int markerCount = 0;
  48. int currentMarkerStart = -1;
  49. for (int index = 0; index < std::ssize(format); ++index) {
  50. auto symbol = format[index];
  51. // Parse verbatim text.
  52. if (currentMarkerStart == -1) {
  53. if (symbol == IntroductorySymbol) {
  54. // Marker maybe begins.
  55. currentMarkerStart = index;
  56. }
  57. continue;
  58. }
  59. // NB: We check for %% first since
  60. // in order to verify if symbol is a specifier
  61. // we need markerCount to be within range of our
  62. // specifier array.
  63. if (symbol == IntroductorySymbol) {
  64. if (currentMarkerStart + 1 != index) {
  65. // '%a% detected'
  66. throw "You may not terminate flag sequence other than %% with \'%\' symbol";
  67. }
  68. // '%%' detected --- skip
  69. currentMarkerStart = -1;
  70. continue;
  71. }
  72. // We are inside of marker.
  73. if (markerCount == std::ssize(markers)) {
  74. // Too many markers
  75. throw "Number of arguments supplied to format is smaller than the number of flag sequences";
  76. }
  77. if (Contains(specifiers[markerCount].Conversion, symbol)) {
  78. // Marker has finished.
  79. markers[markerCount]
  80. = std::string_view(format.begin() + currentMarkerStart, index - currentMarkerStart + 1);
  81. currentMarkerStart = -1;
  82. ++markerCount;
  83. continue;
  84. }
  85. if (!Contains(specifiers[markerCount].Flags, symbol)) {
  86. throw "Symbol is not a valid flag specifier; See FlagSpecifiers";
  87. }
  88. }
  89. if (currentMarkerStart != -1) {
  90. // Runaway marker.
  91. throw "Unterminated flag sequence detected; Use \'%%\' to type plain %";
  92. return;
  93. }
  94. if (markerCount < std::ssize(markers)) {
  95. // Missing markers.
  96. throw "Number of arguments supplied to format is greater than the number of flag sequences";
  97. }
  98. // TODO(arkady-e1ppa): Consider per-type verification
  99. // of markers.
  100. }
  101. };
  102. ////////////////////////////////////////////////////////////////////////////////
  103. } // namespace NYT::NDetail