aboutsummaryrefslogtreecommitdiff
path: root/src/toml11/toml/source_location.hpp
blob: fa175b5b48da39ea7cb720740aeb552a17ecbd1f (plain)
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//     Copyright Toru Niina 2019.
// Distributed under the MIT License.
#ifndef TOML11_SOURCE_LOCATION_HPP
#define TOML11_SOURCE_LOCATION_HPP
#include <cstdint>
#include <sstream>

#include "region.hpp"

namespace toml
{

// A struct to contain location in a toml file.
// The interface imitates std::experimental::source_location,
// but not completely the same.
//
// It would be constructed by toml::value. It can be used to generate
// user-defined error messages.
//
// - std::uint_least32_t line() const noexcept
//   - returns the line number where the region is on.
// - std::uint_least32_t column() const noexcept
//   - returns the column number where the region starts.
// - std::uint_least32_t region() const noexcept
//   - returns the size of the region.
//
// +-- line()       +-- region of interest (region() == 9)
// v            .---+---.
// 12 | value = "foo bar"
//              ^
//              +-- column()
//
// - std::string const& file_name() const noexcept;
//   - name of the file.
// - std::string const& line_str() const noexcept;
//   - the whole line that contains the region of interest.
//
struct source_location
{
  public:

    source_location()
        : line_num_(1), column_num_(1), region_size_(1),
          file_name_("unknown file"), line_str_("")
    {}

    explicit source_location(const detail::region_base* reg)
        : line_num_(1), column_num_(1), region_size_(1),
          file_name_("unknown file"), line_str_("")
    {
        if(reg)
        {
            if(reg->line_num() != detail::region_base().line_num())
            {
                line_num_ = static_cast<std::uint_least32_t>(
                        std::stoul(reg->line_num()));
            }
            column_num_  = static_cast<std::uint_least32_t>(reg->before() + 1);
            region_size_ = static_cast<std::uint_least32_t>(reg->size());
            file_name_   = reg->name();
            line_str_    = reg->line();
        }
    }

    explicit source_location(const detail::region& reg)
        : line_num_(static_cast<std::uint_least32_t>(std::stoul(reg.line_num()))),
          column_num_(static_cast<std::uint_least32_t>(reg.before() + 1)),
          region_size_(static_cast<std::uint_least32_t>(reg.size())),
          file_name_(reg.name()),
          line_str_ (reg.line())
    {}
    explicit source_location(const detail::location& loc)
        : line_num_(static_cast<std::uint_least32_t>(std::stoul(loc.line_num()))),
          column_num_(static_cast<std::uint_least32_t>(loc.before() + 1)),
          region_size_(static_cast<std::uint_least32_t>(loc.size())),
          file_name_(loc.name()),
          line_str_ (loc.line())
    {}

    ~source_location() = default;
    source_location(source_location const&) = default;
    source_location(source_location &&)     = default;
    source_location& operator=(source_location const&) = default;
    source_location& operator=(source_location &&)     = default;

    std::uint_least32_t line()      const noexcept {return line_num_;}
    std::uint_least32_t column()    const noexcept {return column_num_;}
    std::uint_least32_t region()    const noexcept {return region_size_;}

    std::string const&  file_name() const noexcept {return file_name_;}
    std::string const&  line_str()  const noexcept {return line_str_;}

  private:

    std::uint_least32_t line_num_;
    std::uint_least32_t column_num_;
    std::uint_least32_t region_size_;
    std::string         file_name_;
    std::string         line_str_;
};

namespace detail
{

// internal error message generation.
inline std::string format_underline(const std::string& message,
        const std::vector<std::pair<source_location, std::string>>& loc_com,
        const std::vector<std::string>& helps = {},
        const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED)
{
    std::size_t line_num_width = 0;
    for(const auto& lc : loc_com)
    {
        std::uint_least32_t line = lc.first.line();
        std::size_t        digit = 0;
        while(line != 0)
        {
            line  /= 10;
            digit +=  1;
        }
        line_num_width = (std::max)(line_num_width, digit);
    }
    // 1 is the minimum width
    line_num_width = std::max<std::size_t>(line_num_width, 1);

    std::ostringstream retval;

    if(colorize)
    {
        retval << color::colorize; // turn on ANSI color
    }

    // XXX
    // Here, before `colorize` support, it does not output `[error]` prefix
    // automatically. So some user may output it manually and this change may
    // duplicate the prefix. To avoid it, check the first 7 characters and
    // if it is "[error]", it removes that part from the message shown.
    if(message.size() > 7 && message.substr(0, 7) == "[error]")
    {
        retval << color::bold << color::red << "[error]" << color::reset
               << color::bold << message.substr(7) << color::reset << '\n';
    }
    else
    {
        retval << color::bold << color::red << "[error] " << color::reset
               << color::bold << message << color::reset << '\n';
    }

    const auto format_one_location = [line_num_width]
        (std::ostringstream& oss,
         const source_location& loc, const std::string& comment) -> void
        {
            oss << ' ' << color::bold << color::blue
                << std::setw(static_cast<int>(line_num_width))
                << std::right << loc.line() << " | "  << color::reset
                << loc.line_str() << '\n';

            oss << make_string(line_num_width + 1, ' ')
                << color::bold << color::blue << " | " << color::reset
                << make_string(loc.column()-1 /*1-origin*/, ' ');

            if(loc.region() == 1)
            {
                // invalid
                // ^------
                oss << color::bold << color::red << "^---" << color::reset;
            }
            else
            {
                // invalid
                // ~~~~~~~
                const auto underline_len = (std::min)(
                    static_cast<std::size_t>(loc.region()), loc.line_str().size());
                oss << color::bold << color::red
                    << make_string(underline_len, '~') << color::reset;
            }
            oss << ' ';
            oss << comment;
            return;
        };

    assert(!loc_com.empty());

    // --> example.toml
    //   |
    retval << color::bold << color::blue << " --> " << color::reset
           << loc_com.front().first.file_name() << '\n';
    retval << make_string(line_num_width + 1, ' ')
           << color::bold << color::blue << " |\n"  << color::reset;
    // 1 | key value
    //   |    ^--- missing =
    format_one_location(retval, loc_com.front().first, loc_com.front().second);

    // process the rest of the locations
    for(std::size_t i=1; i<loc_com.size(); ++i)
    {
        const auto& prev = loc_com.at(i-1);
        const auto& curr = loc_com.at(i);

        retval << '\n';
        // if the filenames are the same, print "..."
        if(prev.first.file_name() == curr.first.file_name())
        {
            retval << color::bold << color::blue << " ...\n" << color::reset;
        }
        else // if filename differs, print " --> filename.toml" again
        {
            retval << color::bold << color::blue << " --> " << color::reset
                   << curr.first.file_name() << '\n';
            retval << make_string(line_num_width + 1, ' ')
                   << color::bold << color::blue << " |\n"  << color::reset;
        }

        format_one_location(retval, curr.first, curr.second);
    }

    if(!helps.empty())
    {
        retval << '\n';
        retval << make_string(line_num_width + 1, ' ');
        retval << color::bold << color::blue << " |" << color::reset;
        for(const auto& help : helps)
        {
            retval << color::bold << "\nHint: " << color::reset;
            retval << help;
        }
    }
    return retval.str();
}

} // detail
} // toml
#endif// TOML11_SOURCE_LOCATION_HPP