aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/string/format_analyser.h
blob: e8287ea8f3839f618d3575bfcd6748c98c0ee0a4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#pragma once

#include "format_arg.h"

#include <util/generic/strbuf.h>

#include <array>
#include <string_view>

namespace NYT::NDetail {

////////////////////////////////////////////////////////////////////////////////

struct TFormatAnalyser
{
public:
    // TODO(arkady-e1ppa): Until clang-19 consteval functions
    // defined out of line produce symbols in rare cases
    // causing linker to crash.
    template <class... TArgs>
    static consteval void ValidateFormat(std::string_view fmt)
    {
        DoValidateFormat<TArgs...>(fmt);
    }

private:
    // Non-constexpr function call will terminate compilation.
    // Purposefully undefined and non-constexpr/consteval
    template <class T>
    static void CrashCompilerNotFormattable(std::string_view /*msg*/)
    { /*Suppress "internal linkage but undefined" warning*/ }
    static void CrashCompilerNotEnoughArguments(std::string_view /*msg*/)
    { }

    static void CrashCompilerTooManyArguments(std::string_view /*msg*/)
    { }

    static void CrashCompilerWrongTermination(std::string_view /*msg*/)
    { }

    static void CrashCompilerMissingTermination(std::string_view /*msg*/)
    { }

    static void CrashCompilerWrongFlagSpecifier(std::string_view /*msg*/)
    { }


    static consteval bool Contains(std::string_view sv, char symbol)
    {
        return sv.find(symbol) != std::string_view::npos;
    }

    struct TSpecifiers
    {
        std::string_view Conversion;
        std::string_view Flags;
    };

    template <class TArg>
    static consteval auto GetSpecifiers()
    {
        if constexpr (!CFormattable<TArg>) {
            CrashCompilerNotFormattable<TArg>("Your specialization of TFormatArg is broken");
        }

        return TSpecifiers{
            .Conversion = std::string_view{
                std::data(TFormatArg<TArg>::ConversionSpecifiers),
                std::size(TFormatArg<TArg>::ConversionSpecifiers)},
            .Flags = std::string_view{
                std::data(TFormatArg<TArg>::FlagSpecifiers),
                std::size(TFormatArg<TArg>::FlagSpecifiers)},
        };
    }

    static constexpr char IntroductorySymbol = '%';

    template <class... TArgs>
    static consteval void DoValidateFormat(std::string_view format)
    {
        std::array<std::string_view, sizeof...(TArgs)> markers = {};
        std::array<TSpecifiers, sizeof...(TArgs)> specifiers{GetSpecifiers<TArgs>()...};

        int markerCount = 0;
        int currentMarkerStart = -1;

        for (int index = 0; index < std::ssize(format); ++index) {
            auto symbol = format[index];

            // Parse verbatim text.
            if (currentMarkerStart == -1) {
                if (symbol == IntroductorySymbol) {
                    // Marker maybe begins.
                    currentMarkerStart = index;
                }
                continue;
            }

            // NB: We check for %% first since
            // in order to verify if symbol is a specifier
            // we need markerCount to be within range of our
            // specifier array.
            if (symbol == IntroductorySymbol) {
                if (currentMarkerStart + 1 != index) {
                    // '%a% detected'
                    CrashCompilerWrongTermination("You may not terminate flag sequence other than %% with \'%\' symbol");
                    return;
                }
                // '%%' detected --- skip
                currentMarkerStart = -1;
                continue;
            }

            // We are inside of marker.
            if (markerCount == std::ssize(markers)) {
                // To many markers
                CrashCompilerNotEnoughArguments("Number of arguments supplied to format is smaller than the number of flag sequences");
                return;
            }

            if (Contains(specifiers[markerCount].Conversion, symbol)) {
                // Marker has finished.

                markers[markerCount]
                    = std::string_view(format.begin() + currentMarkerStart, index - currentMarkerStart + 1);
                currentMarkerStart = -1;
                ++markerCount;

                continue;
            }

            if (!Contains(specifiers[markerCount].Flags, symbol)) {
                CrashCompilerWrongFlagSpecifier("Symbol is not a valid flag specifier; See FlagSpecifiers");
            }
        }

        if (currentMarkerStart != -1) {
            // Runaway marker.
            CrashCompilerMissingTermination("Unterminated flag sequence detected; Use \'%%\' to type plain %");
            return;
        }

        if (markerCount < std::ssize(markers)) {
            // Missing markers.
            CrashCompilerTooManyArguments("Number of arguments supplied to format is greater than the number of flag sequences");
            return;
        }

        // TODO(arkady-e1ppa): Consider per-type verification
        // of markers.
    }
};

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT::NDetail