User:Space Mission
From cppreference.com
Contents |
Today is 2025/04/30 yesterday's tomorrow.
Update routinely (A reminder):
(check/fill/synchronize the following feature-test-macro pages)
- cpp/feature test#Library features
- cpp/feature test#Example
- cpp/utility/feature test
- cpp/symbol index/macro
- cpp/compiler support/23
TODO
- add new range adaptors pages...
- gather new names (C++17/20/23) for GeSHi highlighter/links generator
Links useful for editing
- WikiMedia: Variables
- WikiMedia: Parser Functions
- WikiMedia: Advanced templates
- WikiMedia: Enchantment
- WikiMedia: Syntax Highlight
- Common HTML entities
- Unicode symbols
A note on syntax highlight
cppreference uses GeSHi syntax highlighter, the currently used version of which does not recognize most of new (C++17/20/23) keywords. Nonetheless, there is a possibility to make them highlighted (in the source-blocks) by editing the 'on-server' file that is actually a copy of the geshi/cpp.php. The new keywords (e.g. char8_t, consteval, etc.) should be added into 'KEYWORDS' => array of that file.
Some of my examples
|
Utilities
Not to be lost:
- FTM difference finder (draft vs Symbol Index).
- Compiler Features Dump.
- FTM table generator for cpp/utility/feature test:
| v1. Feature-test macros extractor / table generator (cpp/utility/feature test); Source: online C++ draft (HTML) |
|---|
//!
//! \abstract This program downloads the "Language support library macros"
//! [page](https://eel.is/c++draft/version.syn), parses it, and then generates the
//! wiki-source for the [page](https://en.cppreference.com/w/cpp/utility/feature_test).
//!
//! \usage: just compile & run, the wiki-page will be sent to terminal.
//!
//! \dependencies: the `curl` program, C++20.
//!
//! \author: (c) 2021. Space Mission. For cppreference.com internal usage.
//! \license: [CC-BY-SA](https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA).
//!
//! \news:
//! 2021-06-12 : initial release (used an array that contains the plain text copied from
//! : the eel page).
//! 2021-12-19 : added options and means to generate the whole wiki-page instead of only
//! : the wiki-table. Note that this requires updates if the original
//! : [page](cppreference.com/w/cpp/utility/feature_test) was changed.
//! 2021-12-20 : (optimizations) e.g. the output is generated w/o a temporary vector.
//! 2021-12-21 : added "curl" downloading of the source html file from
//! : [the site](https://eel.is/c++draft/version.syn).
//! 2021-12-23 : added data extractor from the html using std::regex library.
//! 2021-12-27 : added more test.
//! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless,
//! : everything worked due to try_local_file_if_download_failed == true.
//! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the
//! : table generation; placed the macros counter in the table's footer.
//! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules.
//!
//! TODO: add "freestanding" support, see P2198R4 (Freestanding Feature-Test Macros)
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
using namespace std::literals;
/**
* @brief global options
*/
struct option {
//
//! A command to download the source html page. This string will be appended with the
//! target file and then executed by std::system( command / tmp-dir / file-name );
//!
static inline constexpr auto command{
"curl https://eel.is/c++draft/version.syn --silent -o "sv};
//
//! Do not fail if the source html file was not loaded (due to Internet problems) in
//! this session, but still exists locally (left from a previous session).
//! Useful mostly for debugging.
static inline constexpr bool try_local_file_if_download_failed {false};
//
//! Remove downloaded source html file (only if it was downloaded in this session).
static inline constexpr bool remove_downloaded_file_at_exit {false};
//
//! Print additional info, such as success/failure.
static inline constexpr bool verbose {false};
//
//! Test mode only.
static inline constexpr bool enable_tests {false};
};
//!
//! \brief A downloader: gets the source file from the Internet.
//!
class SourceDownloader final {
std::string downloaded_file_name_;
public:
SourceDownloader() = default;
SourceDownloader(SourceDownloader const&) = default;
SourceDownloader& operator=(SourceDownloader const&) = default;
~SourceDownloader() { cleanup(); }
public:
[[nodiscard]] bool load();
[[nodiscard]] const std::string& file_name() const noexcept {
return downloaded_file_name_;
}
void cleanup();
};
/**
* @brief Loads source html file from the "https://eel.is/c++draft/".
* @return return true if the file was downloaded successfully.
*/
inline bool SourceDownloader::load() {
constexpr auto html {"eel_cpp_header_version.html"sv};
downloaded_file_name_ = std::filesystem::temp_directory_path() / html;
const std::string command {option::command.data() + downloaded_file_name_};
if constexpr (option::verbose) {
std::cout << "Downloading with: [" << command << "]\n";
std::cout << "Destination file: " << downloaded_file_name_ << '\n';
}
if (const int ret_code {std::system(command.data())}; ret_code != 0) {
if constexpr (option::verbose) {
std::cout << "Can't download the file: error #" << ret_code << '\n';
// TODO: maybe decipher the error return code.
}
return false;
}
if constexpr (option::verbose)
std::cout << "OK. The file was downloaded successfully.\n";
return true;
}
/**
* @brief Reset file and (conditionally) remove downloaded html source file.
*/
inline void SourceDownloader::cleanup() {
if constexpr (option::remove_downloaded_file_at_exit) {
if (!downloaded_file_name_.empty()) {
std::filesystem::remove(downloaded_file_name_);
downloaded_file_name_.clear();
}
}
}
/**
* @brief class FeatureTestTable.
* Extracts data from a given string, that must be a part of
* [table entry](https://eel.is/c++draft/version.syn) html page
* and generates the page [or (conditionally) only the table]:
* [page](https://en.cppreference.com/w/cpp/utility/feature_test)
*/
class FeatureTestTable {
public:
FeatureTestTable() = default;
public:
[[nodiscard]] std::string generate_entry(std::string_view source);
private:
[[nodiscard]] bool parse_line(std::string_view source);
public:
static bool generate();
public:
[[nodiscard]] static bool self_test();
private:
[[nodiscard]] static std::string_view table_head();
[[nodiscard]] static std::string table_tail(unsigned);
private:
using string_view_vector = std::vector<std::string_view>;
private:
string_view_vector headers_; // e.g. {"vector", "type_traits"}
std::string_view macro_; // e.g. "__cpp_lib_any"
std::string_view date_; // e.g. "201606L"
};
/**
* @brief Parses the source line and sets internal values for macro_, date_, headers_
* @param[in] source - The source html line
* @exception std::logic_error - throws if the source html has a wrong format.
* @return true - if line was parsed successfully.
*/
inline bool FeatureTestTable::parse_line(std::string_view source) {
headers_.clear();
macro_ = date_ = "";
const char* suffix;
std::cmatch m;
constexpr std::regex::flag_type flags{std::regex_constants::ECMAScript |
std::regex_constants::optimize};
/* Obtain macro (e.g. "__cpp_lib_byte") and date (e.g. "201603L")
* HTML example:
<span class='preprocessordirective'>#define</span> <span id='lib:__cpp_lib_byte'>...
...<span class='literal'>202011L</span>...
*/
static const std::regex re_get_macro_and_date{
"'lib:(__cpp_lib_[_a-z0-9]{3,50})'.*'literal'>(20[1-4][0-9]{3}L)",
flags
};
if (!std::regex_search(source.data(), m, re_get_macro_and_date) or m.size() != 3)
return false;
macro_ = std::string_view(m[1].first, m[1].second - m[1].first);
date_ = std::string_view(m[2].first, m[2].second - m[2].first);
suffix = m.suffix().first; // contains the tail with at least one header
if (macro_.length() < "__cpp_lib_xxx"sv.length() or
date_.length() != (/*a sample:*/ "202002L"sv).length())
return false;
/* Obtain header(s) in the cycle.
* HTML example:
...<span id='headerref:<string_view>___'>...
*/
static const std::regex re_find_header{
R"regex((?:headerref:<)([_a-z\d]+(?:\.h)?)>)regex", flags};
for (; std::regex_search(suffix, m, re_find_header) and m.size() == 2;
suffix = m.suffix().first) // contains the tail with zero or more headers
headers_.emplace_back(m[1].first, m[1].second - m[1].first);
return true;
}
/**
* @brief Performs self test and returns.
*
* @return `true` if self-test passed.
*/
inline bool FeatureTestTable::self_test() {
FeatureTestTable tab;
constexpr auto fake_html =
"<span class='preprocessordirective'>#define</span> <span id='lib:__cpp_lib_byte'>..."
"...<span class='literal'>202011L</span>..."
"...<span id='headerref:<string>___'>..."
"...<span id='headerref:<string_view>___'>..."sv;
return tab.parse_line(fake_html) and tab.macro_ == "__cpp_lib_byte" and
tab.date_ == "202011L" and tab.headers_.size() == 2 and
tab.headers_[0] == "string" and tab.headers_[1] == "string_view";
}
/**
* @brief Generates the header of the table.
*/
inline std::string_view FeatureTestTable::table_head() {
return
R"--(
{| class="wikitable sortable"
|-
! Macro name
! Value
! Header
)--"sv;
}
/**
* @brief Generates the footer of the table.
*/
inline std::string FeatureTestTable::table_tail(unsigned entry_count) {
return
"|-\n"
"! colspan=\"3\" | Total number of macros: " + std::to_string(entry_count) + "\n"
"|}\n";
}
/**
* @brief Generates cppreference table entry.
* @param source - source string to parse.
* @return non-empty table entry string, if success. An empty string otherwise.
*/
inline std::string FeatureTestTable::generate_entry(std::string_view source) {
if (!parse_line(source))
return {};
/* cppreference table entry sample:
|-
| {{tt|__cpp_lib_byte}}
| 201603L
| {{header|atomic}} {{header|filesystem}} ...
*/
std::ostringstream str("", std::ios_base::ate);
str << "|-\n"
"| {{tt|" << macro_ << "}}\n"
"| " << date_ << "\n"
"|";
for (auto const& header : headers_) {
str << " {{header|" << header << "}}";
}
return str.str();
}
// TODO: ? implement input_iterator interface for SourceFileReader:
// begin(), end() [i.e. sentinel] operator*, and operator++.
class SourceFileReader {
public:
SourceFileReader(SourceFileReader const&) = delete;
SourceFileReader& operator=(SourceFileReader const&) = delete;
explicit SourceFileReader(std::string_view const file_name);
[[nodiscard]] bool is_open() const { return ok_; }
[[nodiscard]] bool goto_next_line();
[[nodiscard]] std::string_view get_line() const { return cur_; }
operator std::ifstream& () noexcept { return file_; }
private:
std::ifstream file_;
std::string cur_;
std::string next_;
bool ok_{false};
};
/**
* @brief Opens the source html file.
* Sets the success-flag that should be checked with is_open().
* @param file_name - the source html file name.
*/
inline SourceFileReader::SourceFileReader(std::string_view const file_name) {
const auto file = std::filesystem::path{file_name};
auto ec = std::error_code{};
if (!std::filesystem::exists(file, ec)) {
std::cerr << "ERROR: source html file not found: " << file << '\n';
return;
}
constexpr auto min_file_size = std::uintmax_t{90'000}; // bytes
if (auto size = std::uintmax_t{0};
(size = std::filesystem::file_size(file, ec)) < min_file_size) {
std::cerr << "ERROR: source html file: " << file << '\n'
<< " is too small, size = " << size << " bytes;\n"
<< " expected size >= " << min_file_size << " bytes\n";
return;
}
file_.open(file_name.data());
if (not(ok_ = file_.is_open())) {
std::cerr << "ERROR: can't open the source html file: " << file << '\n';
return;
}
}
/**
* @brief Go to the next line, if it is available.
* @exception may throw std::logic_error in case of wrong file format
* @return true, if next line is available
*/
inline bool SourceFileReader::goto_next_line() {
if (not ok_)
return false;
constexpr auto significant {"preprocessordirective"sv};
if (cur_.empty()) { // This is a first run. Find the next valid line.
while ((ok_ = !!std::getline(file_, cur_)) && cur_.find(significant) == ""sv.npos)
;
if (not ok_)
throw std::logic_error("Invalid file format.");
} else { // This is not the first run. Thus next_ contains the begin.
cur_ = std::move(next_);
}
// Find the next begin (or EOF), appending the dependent lines.
while ((ok_ = !!std::getline(file_, next_)) && next_.find(significant) == ""sv.npos) {
cur_ += next_;
}
return true;
}
/**
* @brief Generates/prints the table with standard library feature-testing macros.
* @note The format of the output is the wiki-media language. The result is meant to
* be copied to [page](https://en.cppreference.com/w/cpp/utility/feature_test).
*/
inline bool FeatureTestTable::generate() {
SourceDownloader html;
if (not html.load()) {
if constexpr (option::try_local_file_if_download_failed) {
if constexpr (option::verbose)
std::cerr << "Trying to use local source file: " << html.file_name()
<< "\n\n";
} else {
return false;
}
}
auto lines {SourceFileReader{html.file_name()}};
if (not lines.is_open()) {
return false;
}
std::cout << FeatureTestTable::table_head();
auto entry_count {0U};
try {
for (FeatureTestTable table; lines.goto_next_line(); ) {
const std::string line{ table.generate_entry(lines.get_line()) };
if (not line.empty()) {
std::cout << line << '\n';
++entry_count;
}
}
} catch (std::logic_error const& ex) {
if constexpr (option::verbose)
std::cerr << "ERROR: " << ex.what() << '\n';
return false;
}
std::cout << FeatureTestTable::table_tail(entry_count) << '\n';
// Some sanity checks:
constexpr auto min_entries {179u}; // as per 2023-02-13
if (entry_count < min_entries) {
std::cerr << "\nWARNING: Not enough entries! Expected at least " <<
min_entries << '\n';
}
return true;
}
/**
* @brief Performs self test and returns.
*
* @return `true` if self-tests passed.
*/
inline bool self_tests() {
if constexpr (not option::enable_tests)
return true;
bool ok = FeatureTestTable::self_test();
if (ok) {
if constexpr (option::verbose)
std::cerr << "OK. Tests passed.\n";
} else {
std::cerr << "TESTS FAILED.\n";
}
return ok;
}
int main() {
return self_tests() and FeatureTestTable::generate() ? EXIT_SUCCESS : EXIT_FAILURE;
}
|
| v2. Feature-test macros extractor / table generator (cpp/utility/feature_test); Source: github C++ draft (TEX) |
|---|
//! \abstract This program downloads the "Language support library macros"
//! [page](https://github.com/cplusplus/draft/raw/main/source/support.tex) (used to be
//! [page](https://eel.is/c++draft/version.syn)), parses it, and then generates MediaWiki
//! tables for the [page](https://en.cppreference.com/w/cpp/utility/feature_test).
//!
//! \usage: just compile & run, the MediaWiki tables will be sent to the terminal.
//!
//! \dependencies: [curl](https://en.wikipedia.org/wiki/CURL), C++23.
//!
//! \author: (c) 2021-2025. Space Mission. For cppreference.com internal usage.
//! \license: [CC-BY-SA](https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA).
//!
//! \news:
//! 2021-06-12 : initial release (used an array that contains the plain text copied from
//! : the eel page).
//! 2021-12-19 : added options and means to generate the whole wiki-page instead of only
//! : the wiki-table. Note that this requires updates if the original
//! : [page](cppreference.com/w/cpp/utility/feature_test) was changed.
//! 2021-12-20 : (optimizations) e.g. the output is generated w/o a temporary vector.
//! 2021-12-21 : added "curl" downloading of the source HTML file from
//! : [the site](https://eel.is/c++draft/version.syn).
//! 2021-12-23 : added data extractor from the HTML using std::regex library.
//! 2021-12-27 : added more test.
//! 2022-01-29 : fixed SourceDownloader::load() always returned `false`; nonetheless,
//! : everything worked due to try_local_file_if_download_failed == true.
//! 2023-02-27 : removed page's (volatile) header/footer generating code; left only the
//! : table generation; placed the macros counter in the table's footer.
//! 2023-04-05 : added support of macros w/o headers, e.g., __cpp_lib_modules.
//! 2024-07-21 : source switching: the source site now is github C++draft repo; the file
//! : format to parse is "TeX"; added "freestanding" support.
//! 2024-07-22 : table style: added 4th ("freestanding") column; added FTM links.
//! 2024-12-30 : table style: (minor) the header uses multi-line column description style.
//! 2025-04-28 : added generation of the second ("hardening") table.
//! 2025-04-29 : added a quirk for __cpp_lib_freestanding_operator_new.
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
using namespace std::literals;
/**
* @brief global options
*/
struct option {
//! Do not fail if the source file was not loaded (due to Internet problems) in
//! this session, but still exists locally (left from a previous session).
static inline constexpr bool try_local_file_if_download_failed{true};
//! Remove downloaded source file (only if it was downloaded in this session).
static inline constexpr bool remove_downloaded_file_at_exit{false};
//! Print log and additional info, e.g. success/failure.
static inline constexpr bool verbose{false};
//! Test mode only.
static inline constexpr bool enable_self_tests{false};
};
//!
//! \brief The source file downloader that gets the source file from the Internet.
//!
class SourceDownloader final {
std::string file_name_;
public:
SourceDownloader() = default;
SourceDownloader(SourceDownloader const&) = default;
SourceDownloader& operator=(SourceDownloader const&) = default;
~SourceDownloader();
public:
[[nodiscard]] bool load();
[[nodiscard]] const std::string& file_name() const noexcept { return file_name_; }
};
/**
* @brief Loads source TEX file from the C++-draft github repo.
* @return true, if the file was downloaded successfully.
*/
inline bool SourceDownloader::load() {
file_name_ = std::filesystem::temp_directory_path() / "cxx_draft__support.tex";
const std::string command{
"curl -LJ https://github.com/cplusplus/draft/raw/main/source/support.tex "s +
(option::verbose ? "-o "s : "--silent -o "s) + file_name_};
if constexpr (option::verbose) {
std::cout << "Downloading with: [" << command << "]\n"
"Destination file: " << file_name_ << '\n';
}
if (const int ret_code{std::system(command.data())}; ret_code) {
if constexpr (option::verbose) {
std::cout << "Can't download the file: error #" << ret_code << '\n';
// TODO: maybe decipher the error return code.
}
return false;
}
if constexpr (option::verbose) {
std::cout << "OK. The file was downloaded successfully.\n";
}
return true;
}
/**
* @brief Conditionally remove downloaded source file.
*/
inline SourceDownloader::~SourceDownloader() {
if constexpr (option::remove_downloaded_file_at_exit)
if (!file_name_.empty())
std::filesystem::remove(file_name_);
}
/**
* @brief class FtmTableEntry.
*
* Extracts data from a given string, that must be a part of
* [source page](https://github.com/cplusplus/draft/raw/main/source/support.tex)
* tex page; generates the page [or (conditionally) only the table]:
* [target page](https://en.cppreference.com/w/cpp/utility/feature_test)
*/
class FtmTableEntry {
public:
FtmTableEntry() = default;
public:
[[nodiscard]] std::string generate_entry(std::string_view source);
void clear();
private:
[[nodiscard]] bool parse_line(std::string_view source);
public:
static bool generate();
public:
[[nodiscard]] static bool self_test();
private:
[[nodiscard]] static std::string_view table_head() noexcept;
[[nodiscard]] static std::string table_tail(unsigned);
private:
std::vector<std::string_view> headers_; // e.g. {"vector", "type_traits"}
std::string macro_; // e.g. "__cpp_lib_any"
std::string_view date_; // e.g. "201606L"
bool free_{}; // true, if "freestanding"
};
/**
* @brief Parses the source line and sets internal values for:
*/
inline void FtmTableEntry::clear() {
headers_.clear();
macro_.clear();
date_ = std::string_view{};
free_ = false;
}
/**
* @brief Parses the source line and sets internal values for:
* macro_, date_, headers_, free_.
* @param source, the TEX source line.
* @exception std::logic_error, if the source line has a wrong format.
* @return true, if line was parsed successfully.
*/
inline bool FtmTableEntry::parse_line(std::string_view source) {
clear();
std::cmatch m;
constexpr std::regex::flag_type flags{std::regex_constants::ECMAScript |
std::regex_constants::optimize};
// A quirk for non-regular __cpp_lib_freestanding_operator_new:
if (source.contains("#define @\\defnlibxname{cpp_lib_freestanding_operator_new}@"sv)) {
macro_ = "__cpp_lib_freestanding_operator_new", date_ = "202306L"sv, free_ = true;
return true;
}
// A fake example:
// #define @\defnlibxname{cpp_lib_atomic}@ 201911L // freestanding, also in
// \libheader{atomic}, \libheader{memory}
if (static const std::regex re_ftm_and_date{
R"FTM(#define @\\defnlibxname\{(cpp_lib_[_a-z0-9]{3,50})\}@)FTM"
R"DATE([ ]+(20[1-4][0-9]{3}L)[ ]+// )DATE",
flags};
!std::regex_search(source.data(), m, re_ftm_and_date) or m.size() != 3)
return false;
macro_ = "__" + std::string(m[1].first, m[1].second - m[1].first);
date_ = std::string_view(m[2].first, m[2].second - m[2].first);
// sanity check:
if (macro_.length() < "__cpp_lib_xxx"sv.length() or
date_.length() != (/*sample:*/"202002L"sv).length())
return false;
source.remove_prefix(m.suffix().first - source.data()); // the tail
constexpr auto free{"freestanding"sv};
// Extract "freestanding" flag, if any.
if (const auto pos{source.substr(0, free.length() + 8).find(free)}; pos != ""sv.npos) {
source.remove_prefix(pos + free.length());
free_ = true;
}
// Obtain header(s) in cycle.
// TEX example: ... also in \libheader{atomic}, \libheader{memory} ...
static const std::regex re_header{
R"RE(\libheader\{([a-z][_a-z\d\.]+)\})RE", flags};
while (std::regex_search(source.data(), m, re_header) and m.size() == 2) {
headers_.emplace_back(m[1].first, m[1].second - m[1].first);
source.remove_prefix(m.suffix().first - source.data()); // update the tail
}
return true;
}
/**
* @brief Performs self test and returns.
*
* @return true, if self-test has passed.
*/
inline bool FtmTableEntry::self_test() {
FtmTableEntry tab;
if constexpr (1) {
tab.clear();
constexpr auto fake_tex =
"#define @\\defnlibxname{cpp_lib_atomic_lock}@ "
"201907L // also in \\libheader{atomic}"sv;
if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_atomic_lock" and
tab.date_ == "201907L" and tab.free_ == false and
tab.headers_.size() == 1 and tab.headers_[0] == "atomic")) {
std::cerr << "TESTS #1 FAILED.\n";
return false;
}
}
if constexpr (1) {
tab.clear();
constexpr auto fake_tex =
"#define @\\defnlibxname{cpp_lib_char8_t}@ 201907L // freestanding, "
"also in \\libheader{atomic}, \\libheader{filesystem}, "
"// \\libheader{istream}, \\libheader{limits}"sv;
if (!(tab.parse_line(fake_tex) and tab.macro_ == "__cpp_lib_char8_t" and
tab.date_ == "201907L" and tab.free_ and
tab.headers_.size() == 4 and
tab.headers_[0] == "atomic" and tab.headers_[1] == "filesystem" and
tab.headers_[2] == "istream" and tab.headers_[3] == "limits")) {
std::cerr << "TESTS #2 FAILED.\n";
return false;
}
}
if constexpr (option::verbose)
std::cerr << "OK. Tests passed.\n";
return true;
}
/**
* @brief Generates the header of the table.
*/
inline std::string_view FtmTableEntry::table_head() noexcept {
return
R"--(
{|class="wikitable sortable" style="font-size: 100%;"
|-
!Macro name
!Value
!Header
!Free-<br>standing
)--"sv;
}
/**
* @brief Generates the footer of the table.
*/
inline std::string FtmTableEntry::table_tail(unsigned entry_count) {
return
"|-\n"
"!colspan=\"4\"|Total number of macros: " + std::to_string(entry_count) + "\n"
"|}\n";
}
/**
* @brief Generates cppreference table entry.
* @param source - source string to parse.
* @return non-empty table entry string, if success. An empty string otherwise.
*/
inline std::string FtmTableEntry::generate_entry(std::string_view source) {
if (!parse_line(source))
return {};
/* cppreference table entry sample:
|-
|{{ftm link|__cpp_lib_byte}}
|201603L
|{{header|atomic}} {{header|filesystem}} ...
|{{yes}}
*/
std::ostringstream str("", std::ios_base::ate);
str << "|-\n"
"|{{ftm link|" << macro_ << "}}\n"
"|{{c|" << date_ << "}}\n"
"|";
for (int n{}; auto const& header : headers_)
str << (n++ ? " " : "") << "{{header|" << header << "}}";
str << (free_ ? "\n|{{yes}}" : "\n|");
return str.str();
}
class SourceFileReader {
public:
explicit SourceFileReader(std::string_view const file_name);
SourceFileReader(SourceFileReader const&) = delete;
SourceFileReader& operator=(SourceFileReader const&) = delete;
[[nodiscard]] operator std::ifstream& () noexcept { return file_; }
[[nodiscard]] std::string_view cur_line() const { return cur_; }
[[nodiscard]] bool is_open() const { return ok_; }
[[nodiscard]] bool next_line();
void next_table();
private:
std::ifstream file_;
std::string cur_;
std::string next_;
int table_n_{};
bool ok_{false};
};
/**
* @brief Opens the source TEX file.
* Sets the success-flag that should be checked with is_open().
* @param file_name - the source TEX file name.
*/
inline SourceFileReader::SourceFileReader(std::string_view const file_name) {
const auto file = std::filesystem::path{file_name};
auto ec = std::error_code{};
if (!std::filesystem::exists(file, ec)) {
std::cerr << "ERROR: source file not found: " << file << '\n';
return;
}
constexpr std::uintmax_t min_file_size{200'000}; // bytes
if (std::uintmax_t size; (size = std::filesystem::file_size(file, ec)) < min_file_size) {
std::cerr << "ERROR: source file: " << file << "\n"
" is too small, size = " << size << " bytes;\n"
" expected size >= " << min_file_size << " bytes\n";
return;
}
if (file_.open(file_name.data()); not(ok_ = file_.is_open())) {
std::cerr << "ERROR: can't open the source file: " << file << '\n';
return;
}
}
/**
* @brief Prepare internal state to read a next table.
*/
inline void SourceFileReader::next_table() {
ok_ = true;
cur_.clear();
++table_n_;
}
/**
* @brief Obtains the next line, if it is available (may concatenate few lines)
* @exception std::logic_error, in case of wrong TEX file format
* @return true, if next line is available
*/
inline bool SourceFileReader::next_line() {
if (not ok_)
return false;
constexpr auto intro{"#define "sv};
constexpr auto outro{"\\end"sv}; // \end{codeblock}
if (cur_.empty()) { // This is a first run. Find the first valid line.
// Expected preliminary line are:
//
// "Future revisions of this document might replace"
// " the values of these macros with greater values."
// "\end{note}"
// ""
// "\begin{codeblock}"
// "#define ..." <== intro
//
if (table_n_ == 0) { // main table
constexpr auto nearest{"Future revisions of this document"sv};
while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(nearest)) {}
while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(intro)) {}
if (not ok_)
throw std::logic_error("Unexpected file format.");
} else { // hardening table
constexpr auto nearest{"Additionally, each of the following macros"sv};
while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(nearest)) {}
while ((ok_ = !!std::getline(file_, cur_)) && !cur_.starts_with(intro)) {}
if (not ok_)
throw std::logic_error("Unexpected file format.");
}
} else { // This is not the first run. Thus next_ contains the beginning.
cur_ = std::move(next_);
}
// Find the next begin (or EOB), appending the dependent lines.
while ((ok_ = !!std::getline(file_, next_)) && !next_.starts_with(intro)) {
if (next_.starts_with(outro)) {
ok_ = false; // End Of Block
break;
}
cur_ += next_;
}
return true;
}
/**
* @brief Generates/prints the table with standard library feature-testing macros.
* @note The result is a table described in MediaWiki language. The output should be
* copied onto "https://en.cppreference.com/w/cpp/utility/feature_test" page.
*/
inline bool FtmTableEntry::generate() {
SourceDownloader downloader;
if (not downloader.load()) {
if constexpr (option::try_local_file_if_download_failed) {
if constexpr (option::verbose)
std::cerr << "Trying to use local source file: "
<< downloader.file_name() << "\n\n";
} else {
return false;
}
}
auto lines{SourceFileReader{downloader.file_name()}};
if (not lines.is_open()) {
return false;
}
for (int table_n{}; table_n != 2; ++table_n, lines.next_table()) {
auto entry_count{0U};
std::cout << FtmTableEntry::table_head();
try {
for (FtmTableEntry table; lines.next_line(); ) {
const std::string line{ table.generate_entry(lines.cur_line()) };
if (not line.empty()) {
std::cout << line << '\n';
++entry_count;
}
}
} catch (std::logic_error const& ex) {
if constexpr (option::verbose)
std::cerr << "ERROR: " << ex.what() << '\n';
return false;
}
std::cout << FtmTableEntry::table_tail(entry_count) << '\n';
// The tables' sanity check :)
constexpr unsigned min_entries[]{167u, 13u}; // as per 2025-04
if (entry_count < min_entries[table_n]) {
std::cerr << "\nWARNING: Too few entries! Expected >= " << min_entries[table_n] << '\n';
}
if (table_n == 0) {
std::cout << "\nA hardened implementation also defines the following macros:\n";
}
}
return true;
}
/**
* @brief Performs self test and returns.
*
* @return true, if self-tests passed.
*/
inline bool self_tests() {
if constexpr (not option::enable_self_tests)
return true;
return FtmTableEntry::self_test();
}
int main() {
return self_tests() and FtmTableEntry::generate() ? EXIT_SUCCESS : EXIT_FAILURE;
}
|