// Copyright Toru Niina 2019. // Distributed under the MIT License. #ifndef TOML11_SOURCE_LOCATION_HPP #define TOML11_SOURCE_LOCATION_HPP #include #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::stoul(reg->line_num())); } column_num_ = static_cast(reg->before() + 1); region_size_ = static_cast(reg->size()); file_name_ = reg->name(); line_str_ = reg->line(); } } explicit source_location(const detail::region& reg) : line_num_(static_cast(std::stoul(reg.line_num()))), column_num_(static_cast(reg.before() + 1)), region_size_(static_cast(reg.size())), file_name_(reg.name()), line_str_ (reg.line()) {} explicit source_location(const detail::location& loc) : line_num_(static_cast(std::stoul(loc.line_num()))), column_num_(static_cast(loc.before() + 1)), region_size_(static_cast(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>& loc_com, const std::vector& 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(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(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(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 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