/*
* simplecpp - A simple and high-fidelity C/C++ preprocessor library
* Copyright (C) 2016-2023 simplecpp team
*/
// needs to be specified here otherwise _mingw.h will define it as 0x0601
// causing FileIdInfo not to be available
#if defined(_WIN32)
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0602
# endif
#endif
#include "simplecpp.h"
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
# define SIMPLECPP_WINDOWS
#endif
#include
#include
#include
#include
#include // IWYU pragma: keep
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef SIMPLECPP_WINDOWS
# include
#endif
#include
#include
#include
#if defined(_WIN32)
# ifndef NOMINMAX
# define NOMINMAX
# endif
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include
# undef ERROR
# include
#else
# include
# include
#endif
static bool isHex(const std::string &s)
{
return s.size()>2 && (s.compare(0,2,"0x")==0 || s.compare(0,2,"0X")==0);
}
static bool isOct(const std::string &s)
{
return s.size()>1 && (s[0]=='0') && (s[1] >= '0') && (s[1] < '8');
}
static bool isStringLiteral(const std::string &s)
{
return s.size() > 1 && (s[0]=='\"') && (*s.rbegin()=='\"');
}
static bool isCharLiteral(const std::string &s)
{
// char literal patterns can include 'a', '\t', '\000', '\xff', 'abcd', and maybe ''
// This only checks for the surrounding '' but doesn't parse the content.
return s.size() > 1 && (s[0]=='\'') && (*s.rbegin()=='\'');
}
static const simplecpp::TokenString DEFINE("define");
static const simplecpp::TokenString UNDEF("undef");
static const simplecpp::TokenString INCLUDE("include");
static const simplecpp::TokenString ERROR("error");
static const simplecpp::TokenString WARNING("warning");
static const simplecpp::TokenString IF("if");
static const simplecpp::TokenString IFDEF("ifdef");
static const simplecpp::TokenString IFNDEF("ifndef");
static const simplecpp::TokenString DEFINED("defined");
static const simplecpp::TokenString ELSE("else");
static const simplecpp::TokenString ELIF("elif");
static const simplecpp::TokenString ELIFDEF("elifdef");
static const simplecpp::TokenString ELIFNDEF("elifndef");
static const simplecpp::TokenString ENDIF("endif");
static const simplecpp::TokenString PRAGMA("pragma");
static const simplecpp::TokenString ONCE("once");
static const simplecpp::TokenString HAS_INCLUDE("__has_include");
template static std::string toString(T t)
{
// NOLINTNEXTLINE(misc-const-correctness) - false positive
std::ostringstream ostr;
ostr std::hex;
else if (oct)
istr >> std::oct;
istr >> ret;
return ret;
}
static bool endsWith(const std::string &s, const std::string &e)
{
return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin());
}
static bool sameline(const simplecpp::Token *tok1, const simplecpp::Token *tok2)
{
return tok1 && tok2 && tok1->location.sameline(tok2->location);
}
static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string &alt)
{
return (tok->name &&
tok->str() == alt &&
tok->previous &&
tok->next &&
(tok->previous->number || tok->previous->name || tok->previous->op == ')') &&
(tok->next->number || tok->next->name || tok->next->op == '('));
}
static bool isAlternativeUnaryOp(const simplecpp::Token *tok, const std::string &alt)
{
return ((tok->name && tok->str() == alt) &&
(!tok->previous || tok->previous->op == '(') &&
(tok->next && (tok->next->name || tok->next->number)));
}
static std::string replaceAll(std::string s, const std::string& from, const std::string& to)
{
for (size_t pos = s.find(from); pos != std::string::npos; pos = s.find(from, pos + to.size()))
s.replace(pos, from.size(), to);
return s;
}
void simplecpp::Location::adjust(const std::string &str)
{
if (strpbrk(str.c_str(), "\r\n") == nullptr) {
col += str.size();
return;
}
for (std::size_t i = 0U; i < str.size(); ++i) {
col++;
if (str[i] == '\n' || str[i] == '\r') {
col = 1;
line++;
if (str[i] == '\r' && (i+1)previous)
tok = tok->previous;
for (; tok; tok = tok->next) {
if (tok->previous) {
std::cout previous) ? ' ' : '\n');
}
std::cout str();
}
std::cout next) {
if (tok != this) {
std::cout previous) ? ' ' : '\n');
}
std::cout str();
}
std::cout = 0x80) ? 0xff : ch16));
}
// Handling of newlines..
if (ch == '\r') {
ch = '\n';
int ch2 = get();
if (isUtf16) {
const int c2 = get();
ch2 = makeUtf16Char(ch2, c2);
}
if (ch2 != '\n')
ungetChar();
}
return ch;
}
int peekChar() {
int ch = peek();
if (ch == EOF)
return ch;
// For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the
// character is non-ASCII character then replace it with 0xff
if (isUtf16) {
(void)get();
const auto ch2 = static_cast(peek());
unget();
const int ch16 = makeUtf16Char(ch, ch2);
ch = (ch16 >= 0x80) ? 0xff : ch16;
}
// Handling of newlines..
if (ch == '\r')
ch = '\n';
return ch;
}
void ungetChar() {
unget();
if (isUtf16)
unget();
}
protected:
void init() {
// initialize since we use peek() in getAndSkipBOM()
isUtf16 = false;
bom = getAndSkipBOM();
isUtf16 = (bom == 0xfeff || bom == 0xfffe);
}
private:
inline int makeUtf16Char(const unsigned char ch, const unsigned char ch2) const {
return (bom == 0xfeff) ? (chemplace_back(e);
}
}
simplecpp::TokenList::TokenList(const TokenList &other) : frontToken(nullptr), backToken(nullptr), files(other.files)
{
*this = other;
}
simplecpp::TokenList::TokenList(TokenList &&other) : frontToken(nullptr), backToken(nullptr), files(other.files)
{
*this = std::move(other);
}
simplecpp::TokenList::~TokenList()
{
clear();
}
simplecpp::TokenList &simplecpp::TokenList::operator=(const TokenList &other)
{
if (this != &other) {
clear();
files = other.files;
for (const Token *tok = other.cfront(); tok; tok = tok->next)
push_back(new Token(*tok));
sizeOfType = other.sizeOfType;
}
return *this;
}
simplecpp::TokenList &simplecpp::TokenList::operator=(TokenList &&other)
{
if (this != &other) {
clear();
frontToken = other.frontToken;
other.frontToken = nullptr;
backToken = other.backToken;
other.backToken = nullptr;
files = other.files;
sizeOfType = std::move(other.sizeOfType);
}
return *this;
}
void simplecpp::TokenList::clear()
{
backToken = nullptr;
while (frontToken) {
Token * const next = frontToken->next;
delete frontToken;
frontToken = next;
}
sizeOfType.clear();
}
void simplecpp::TokenList::push_back(Token *tok)
{
if (!frontToken)
frontToken = tok;
else
backToken->next = tok;
tok->previous = backToken;
backToken = tok;
}
void simplecpp::TokenList::dump(bool linenrs) const
{
std::cout location.line < loc.line || tok->location.fileIndex != loc.fileIndex) {
ret location;
filechg = true;
}
if (linenrs && filechg) {
ret loc.line) {
ret = 4U && endsWith(currentToken, COMMENT_END))
break;
ch = stream.readChar();
}
// multiline..
std::string::size_type pos = 0;
while ((pos = currentToken.find("\\\n",pos)) != std::string::npos) {
currentToken.erase(pos,2);
++multiline;
}
if (multiline || isLastLinePreprocessor()) {
pos = 0;
while ((pos = currentToken.find('\n',pos)) != std::string::npos) {
currentToken.erase(pos,1);
++multiline;
}
}
}
// string / char literal
else if (ch == '\"' || ch == '\'') {
std::string prefix;
if (cback() && cback()->name && isStringLiteralPrefix(cback()->str()) &&
((cback()->location.col + cback()->str().size()) == location.col) &&
(cback()->location.line == location.line)) {
prefix = cback()->str();
}
// C++11 raw string literal
if (ch == '\"' && !prefix.empty() && *cback()->str().rbegin() == 'R') {
std::string delim;
currentToken = ch;
prefix.resize(prefix.size() - 1);
ch = stream.readChar();
while (stream.good() && ch != '(' && ch != '\n') {
delim += ch;
ch = stream.readChar();
}
if (!stream.good() || ch == '\n') {
if (outputList) {
Output err{
Output::SYNTAX_ERROR,
location,
"Invalid newline in raw string delimiter."
};
outputList->emplace_back(std::move(err));
}
return;
}
const std::string endOfRawString(')' + delim + currentToken);
while (stream.good() && (!endsWith(currentToken, endOfRawString) || currentToken.size() emplace_back(std::move(err));
}
return;
}
currentToken.erase(currentToken.size() - endOfRawString.size(), endOfRawString.size() - 1U);
currentToken = escapeString(currentToken);
currentToken.insert(0, prefix);
back()->setstr(currentToken);
location.adjust(currentToken);
if (currentToken.find_first_of("\r\n") == std::string::npos)
location.col += 2 + (2 * delim.size());
else
location.col += 1 + delim.size();
continue;
}
currentToken = readUntil(stream,location,ch,ch,outputList);
if (currentToken.size() < 2U)
// Error is reported by readUntil()
return;
std::string s = currentToken;
std::string::size_type pos;
int newlines = 0;
while ((pos = s.find_first_of("\r\n")) != std::string::npos) {
s.erase(pos,1);
newlines++;
}
if (prefix.empty())
push_back(new Token(s, location, !!std::isspace(stream.peekChar()))); // push string without newlines
else
back()->setstr(prefix + s);
if (newlines > 0) {
const Token * const llTok = lastLineTok();
if (llTok && llTok->op == '#' && llTok->next && (llTok->next->str() == "define" || llTok->next->str() == "pragma") && llTok->next->next) {
multiline += newlines;
location.adjust(s);
continue;
}
}
location.adjust(currentToken);
continue;
}
else {
currentToken += ch;
}
if (*currentToken.begin() == '= 0 && start) {
if (start->op == ')')
++indentlevel;
else if (start->op == '(')
--indentlevel;
else if (start->isOneOf(";{}"))
break;
start = start->previous;
}
if (indentlevel == -1 && start) {
const Token * const ftok = start;
bool isFuncDecl = ftok->name;
while (isFuncDecl) {
if (!start->name && start->str() != "::" && start->op != '*' && start->op != '&')
isFuncDecl = false;
if (!start->previous)
break;
if (start->previous->isOneOf(";{}:"))
break;
start = start->previous;
}
isFuncDecl &= start != ftok && start->name;
if (isFuncDecl) {
// TODO: we could loop through the parameters here and check if they are correct.
continue;
}
}
}
tok->setstr(tok->str() + "=");
deleteToken(tok->next);
} else if ((tok->op == '|' || tok->op == '&') && tok->op == tok->next->op) {
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
} else if (tok->op == ':' && tok->next->op == ':') {
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
} else if (tok->op == '-' && tok->next->op == '>') {
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
} else if ((tok->op == '') && tok->op == tok->next->op) {
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
if (tok->next && tok->next->op == '=' && tok->next->next && tok->next->next->op != '=') {
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
}
} else if ((tok->op == '+' || tok->op == '-') && tok->op == tok->next->op) {
if (tok->location.col + 1U != tok->next->location.col)
continue;
if (tok->previous && tok->previous->number)
continue;
if (tok->next->next && tok->next->next->number)
continue;
tok->setstr(tok->str() + tok->next->str());
deleteToken(tok->next);
}
}
}
static const std::string COMPL("compl");
static const std::string NOT("not");
void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok)
{
for (; tok && tok->op != ')'; tok = tok->next) {
// "not" might be !
if (isAlternativeUnaryOp(tok, NOT))
tok->op = '!';
// "compl" might be ~
else if (isAlternativeUnaryOp(tok, COMPL))
tok->op = '~';
if (tok->op == '!' && tok->next && tok->next->number) {
tok->setstr(tok->next->str() == "0" ? "1" : "0");
deleteToken(tok->next);
} else if (tok->op == '~' && tok->next && tok->next->number) {
tok->setstr(toString(~stringToLL(tok->next->str())));
deleteToken(tok->next);
} else {
if (tok->previous && (tok->previous->number || tok->previous->name))
continue;
if (!tok->next || !tok->next->number)
continue;
switch (tok->op) {
case '+':
tok->setstr(tok->next->str());
deleteToken(tok->next);
break;
case '-':
tok->setstr(tok->op + tok->next->str());
deleteToken(tok->next);
break;
}
}
}
}
void simplecpp::TokenList::constFoldMulDivRem(Token *tok)
{
for (; tok && tok->op != ')'; tok = tok->next) {
if (!tok->previous || !tok->previous->number)
continue;
if (!tok->next || !tok->next->number)
continue;
long long result;
if (tok->op == '*') {
result = (stringToLL(tok->previous->str()) * stringToLL(tok->next->str()));
}
else if (tok->op == '/' || tok->op == '%') {
const long long rhs = stringToLL(tok->next->str());
if (rhs == 0)
throw std::overflow_error("division/modulo by zero");
const long long lhs = stringToLL(tok->previous->str());
if (rhs == -1 && lhs == std::numeric_limits::min())
throw std::overflow_error("division overflow");
if (tok->op == '/')
result = (lhs / rhs);
else
result = (lhs % rhs);
} else {
continue;
}
tok = tok->previous;
tok->setstr(toString(result));
deleteToken(tok->next);
deleteToken(tok->next);
}
}
void simplecpp::TokenList::constFoldAddSub(Token *tok)
{
for (; tok && tok->op != ')'; tok = tok->next) {
if (!tok->previous || !tok->previous->number)
continue;
if (!tok->next || !tok->next->number)
continue;
long long result;
if (tok->op == '+')
result = stringToLL(tok->previous->str()) + stringToLL(tok->next->str());
else if (tok->op == '-')
result = stringToLL(tok->previous->str()) - stringToLL(tok->next->str());
else
continue;
tok = tok->previous;
tok->setstr(toString(result));
deleteToken(tok->next);
deleteToken(tok->next);
}
}
void simplecpp::TokenList::constFoldShift(Token *tok)
{
for (; tok && tok->op != ')'; tok = tok->next) {
if (!tok->previous || !tok->previous->number)
continue;
if (!tok->next || !tok->next->number)
continue;
long long result;
if (tok->str() == ">")
result = stringToLL(tok->previous->str()) >> stringToLL(tok->next->str());
else
continue;
tok = tok->previous;
tok->setstr(toString(result));
deleteToken(tok->next);
deleteToken(tok->next);
}
}
static const std::string NOTEQ("not_eq");
void simplecpp::TokenList::constFoldComparison(Token *tok)
{
for (; tok && tok->op != ')'; tok = tok->next) {
if (isAlternativeBinaryOp(tok,NOTEQ))
tok->setstr("!=");
if (!tok->startsWithOneOf("=!"))
continue;
if (!tok->previous || !tok->previous->number)
continue;
if (!tok->next || !tok->next->number)
continue;
int result;
if (tok->str() == "==")
result = (stringToLL(tok->previous->str()) == stringToLL(tok->next->str()));
else if (tok->str() == "!=")
result = (stringToLL(tok->previous->str()) != stringToLL(tok->next->str()));
else if (tok->str() == ">")
result = (stringToLL(tok->previous->str()) > stringToLL(tok->next->str()));
else if (tok->str() == ">=")
result = (stringToLL(tok->previous->str()) >= stringToLL(tok->next->str()));
else if (tok->str() == "next)
b->location = loc;
} else {
tokensB.push_back(new Token(*B));
tokensB.back()->location = loc;
}
output.takeTokens(tokensB);
} else {
std::string strAB;
const bool varargs = variadic && !args.empty() && B->str() == args[args.size()-1U];
if (expandArg(tokensB, B, parametertokens)) {
if (tokensB.empty()) {
strAB = A->str();
}
else if (varargs && A->op == ',') {
strAB = ",";
}
else if (varargs && unexpectedA) {
throw invalidHashHash::unexpectedToken(tok->location, name(), A);
}
else {
strAB = A->str() + tokensB.cfront()->str();
tokensB.deleteToken(tokensB.front());
}
} else {
if (unexpectedA)
throw invalidHashHash::unexpectedToken(tok->location, name(), A);
strAB = A->str() + B->str();
}
// producing universal character is undefined behavior
if (A->previous && A->previous->str() == "\\") {
if (strAB[0] == 'u' && strAB.size() == 5)
throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB);
if (strAB[0] == 'U' && strAB.size() == 9)
throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB);
}
if (varargs && tokensB.empty() && tok->previous->str() == ",") {
output.deleteToken(A);
}
else if (strAB != "," && macros.find(strAB) == macros.end()) {
A->setstr(strAB);
for (Token *b = tokensB.front(); b; b = b->next)
b->location = loc;
output.takeTokens(tokensB);
} else if (sameline(B, nextTok) && sameline(B, nextTok->next) && nextTok->op == '#' && nextTok->next->op == '#') {
TokenList output2(files);
output2.push_back(new Token(strAB, tok->location));
nextTok = expandHashHash(output2, loc, nextTok, macros, expandedmacros, parametertokens);
output.deleteToken(A);
output.takeTokens(output2);
} else {
output.deleteToken(A);
TokenList tokens(files);
tokens.push_back(new Token(strAB, tok->location));
// for function like macros, push the (...)
if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') {
const MacroMap::const_iterator it = macros.find(strAB);
if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) {
const Token * const tok2 = appendTokens(tokens, loc, B->next, macros, expandedmacros, parametertokens);
if (tok2)
nextTok = tok2->next;
}
}
if (expandResult)
expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens);
else
output.takeTokens(tokens);
for (Token *b = tokensB.front(); b; b = b->next)
b->location = loc;
output.takeTokens(tokensB);
}
}
return nextTok;
}
static bool isReplaced(const std::set &expandedmacros) {
// return true if size > 1
auto it = expandedmacros.cbegin();
if (it == expandedmacros.cend())
return false;
++it;
return (it != expandedmacros.cend());
}
/** name token in definition */
const Token *nameTokDef;
/** arguments for macro */
std::vector args;
/** first token in replacement string */
const Token *valueToken;
/** token after replacement string */
const Token *endToken;
/** files */
std::vector &files;
/** this is used for -D where the definition is not seen anywhere in code */
TokenList tokenListDefine;
/** usage of this macro */
mutable std::list usageList;
/** is macro variadic? */
bool variadic;
/** does the macro expansion have __VA_OPT__? */
bool variadicOpt;
/** Expansion value for varadic macros with __VA_OPT__ expanded and discarded respectively */
const TokenList *optExpandValue{};
const TokenList *optNoExpandValue{};
/** was the value of this macro actually defined in the code? */
bool valueDefinedInCode_;
};
}
namespace simplecpp {
#ifdef __CYGWIN__
static bool startsWith(const std::string &s, const std::string &p)
{
return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin());
}
std::string convertCygwinToWindowsPath(const std::string &cygwinPath)
{
std::string windowsPath;
std::string::size_type pos = 0;
if (cygwinPath.size() >= 11 && startsWith(cygwinPath, "/cygdrive/")) {
const unsigned char driveLetter = cygwinPath[10];
if (std::isalpha(driveLetter)) {
if (cygwinPath.size() == 11) {
windowsPath = toupper(driveLetter);
windowsPath += ":\\"; // volume root directory
pos = 11;
} else if (cygwinPath[11] == '/') {
windowsPath = toupper(driveLetter);
windowsPath += ":";
pos = 11;
}
}
}
for (; pos < cygwinPath.size(); ++pos) {
unsigned char c = cygwinPath[pos];
if (c == '/')
c = '\\';
windowsPath += c;
}
return windowsPath;
}
#endif
bool isAbsolutePath(const std::string &path)
{
#ifdef SIMPLECPP_WINDOWS
// C:\\path\\file
// C:/path/file
if (path.length() >= 3 && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/'))
return true;
// \\host\path\file
// //host/path/file
if (path.length() >= 2 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/'))
return true;
return false;
#else
return !path.empty() && path[0] == '/';
#endif
}
}
namespace simplecpp {
/**
* perform path simplifications for . and ..
*/
std::string simplifyPath(std::string path)
{
if (path.empty())
return path;
std::string::size_type pos;
// replace backslash separators
std::replace(path.begin(), path.end(), '\\', '/');
const bool unc(path.compare(0,2,"//") == 0);
// replace "//" with "/"
pos = 0;
while ((pos = path.find("//",pos)) != std::string::npos) {
path.erase(pos,1);
}
// remove "./"
pos = 0;
while ((pos = path.find("./",pos)) != std::string::npos) {
if (pos == 0 || path[pos - 1U] == '/')
path.erase(pos,2);
else
pos += 2;
}
// remove trailing dot if path ends with "/."
if (endsWith(path,"/."))
path.erase(path.size()-1);
// simplify ".."
pos = 1; // don't simplify ".." if path starts with that
while ((pos = path.find("/..", pos)) != std::string::npos) {
// not end of path, then string must be "/../"
if (pos + 3 < path.size() && path[pos + 3] != '/') {
++pos;
continue;
}
// get previous subpath
std::string::size_type pos1 = path.rfind('/', pos - 1U);
if (pos1 == std::string::npos) {
pos1 = 0;
} else {
pos1 += 1U;
}
const std::string previousSubPath = path.substr(pos1, pos - pos1);
if (previousSubPath == "..") {
// don't simplify
++pos;
} else {
// remove previous subpath and ".."
path.erase(pos1, pos - pos1 + 4);
if (path.empty())
path = ".";
// update pos
pos = (pos1 == 0) ? 1 : (pos1 - 1);
}
}
// Remove trailing '/'?
//if (path.size() > 1 && endsWith(path, "/"))
// path.erase(path.size()-1);
if (unc)
path = '/' + path;
return path;
}
}
/** Evaluate sizeof(type)
* @throws std::runtime_error thrown on missing arguments or invalid expression
*/
static void simplifySizeof(simplecpp::TokenList &expr, const std::map &sizeOfType)
{
for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) {
if (tok->str() != "sizeof")
continue;
const simplecpp::Token *tok1 = tok->next;
if (!tok1) {
throw std::runtime_error("missing sizeof argument");
}
const simplecpp::Token *tok2 = tok1->next;
if (!tok2) {
throw std::runtime_error("missing sizeof argument");
}
if (tok1->op == '(') {
tok1 = tok1->next;
while (tok2->op != ')') {
tok2 = tok2->next;
if (!tok2) {
throw std::runtime_error("invalid sizeof expression");
}
}
}
std::string type;
for (const simplecpp::Token *typeToken = tok1; typeToken != tok2; typeToken = typeToken->next) {
if ((typeToken->str() == "unsigned" || typeToken->str() == "signed") && typeToken->next->name)
continue;
if (typeToken->str() == "*" && type.find('*') != std::string::npos)
continue;
if (!type.empty())
type += ' ';
type += typeToken->str();
}
const std::map::const_iterator it = sizeOfType.find(type);
if (it != sizeOfType.end())
tok->setstr(toString(it->second));
else
continue;
tok2 = tok2->next;
while (tok->next != tok2)
expr.deleteToken(tok->next);
}
}
static bool isCpp17OrLater(const simplecpp::DUI &dui)
{
const std::string std_ver = simplecpp::getCppStdString(dui.std);
return std_ver.empty() || (std_ver >= "201703L");
}
static bool isGnu(const simplecpp::DUI &dui)
{
return dui.std.rfind("gnu", 0) != std::string::npos;
}
static std::string dirPath(const std::string& path, bool withTrailingSlash=true)
{
const std::size_t lastSlash = path.find_last_of("\\/");
if (lastSlash == std::string::npos) {
return "";
}
return path.substr(0, lastSlash + (withTrailingSlash ? 1U : 0U));
}
static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader);
/** Evaluate __has_include(include)
* @throws std::runtime_error thrown on missing arguments or invalid expression
*/
static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui)
{
if (!isCpp17OrLater(dui) && !isGnu(dui))
return;
for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) {
if (tok->str() != HAS_INCLUDE)
continue;
const simplecpp::Token *tok1 = tok->next;
if (!tok1) {
throw std::runtime_error("missing __has_include argument");
}
const simplecpp::Token *tok2 = tok1->next;
if (!tok2) {
throw std::runtime_error("missing __has_include argument");
}
if (tok1->op == '(') {
tok1 = tok1->next;
while (tok2->op != ')') {
tok2 = tok2->next;
if (!tok2) {
throw std::runtime_error("invalid __has_include expression");
}
}
}
const std::string &sourcefile = expr.file(tok->location);
const bool systemheader = (tok1 && tok1->op == '') {
tok3 = tok3->next;
if (!tok3) {
throw std::runtime_error("invalid __has_include expression");
}
}
for (const simplecpp::Token *headerToken = tok1->next; headerToken != tok3; headerToken = headerToken->next)
header += headerToken->str();
} else {
header = tok1->str().substr(1U, tok1->str().size() - 2U);
}
std::ifstream f;
const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader);
tok->setstr(header2.empty() ? "0" : "1");
tok2 = tok2->next;
while (tok->next != tok2)
expr.deleteToken(tok->next);
}
}
/** Evaluate name
* @throws std::runtime_error thrown on undefined function-like macro
*/
static void simplifyName(simplecpp::TokenList &expr)
{
for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) {
if (tok->name) {
static const std::set altop{"and","or","bitand","bitor","compl","not","not_eq","xor"};
if (altop.find(tok->str()) != altop.end()) {
bool alt;
if (tok->str() == "not" || tok->str() == "compl") {
alt = isAlternativeUnaryOp(tok,tok->str());
} else {
alt = isAlternativeBinaryOp(tok,tok->str());
}
if (alt)
continue;
}
if (tok->next && tok->next->str() == "(")
throw std::runtime_error("undefined function-like macro invocation: " + tok->str() + "( ... )");
tok->setstr("0");
}
}
}
/*
* Reads at least minlen and at most maxlen digits (inc. prefix) in base base
* from s starting at position pos and converts them to a
* unsigned long long value, updating pos to point to the first
* unused element of s.
* Returns ULLONG_MAX if the result is not representable and
* @throws std::runtime_error thrown if the above requirements were not possible to satisfy.
*/
static unsigned long long stringToULLbounded(
const std::string& s,
std::size_t& pos,
int base = 0,
std::ptrdiff_t minlen = 1,
std::size_t maxlen = std::string::npos
)
{
const std::string sub = s.substr(pos, maxlen);
const char * const start = sub.c_str();
char* end;
const unsigned long long value = std::strtoull(start, &end, base);
pos += end - start;
if (end - start < minlen)
throw std::runtime_error("expected digit");
return value;
}
long long simplecpp::characterLiteralToLL(const std::string& str)
{
// default is wide/utf32
bool narrow = false;
bool utf8 = false;
bool utf16 = false;
std::size_t pos;
if (!str.empty() && str[0] == '\'') {
narrow = true;
pos = 1;
} else if (str.size() >= 2 && str[0] == 'u' && str[1] == '\'') {
utf16 = true;
pos = 2;
} else if (str.size() >= 3 && str[0] == 'u' && str[1] == '8' && str[2] == '\'') {
utf8 = true;
pos = 3;
} else if (str.size() >= 2 && (str[0] == 'L' || str[0] == 'U') && str[1] == '\'') {
pos = 2;
} else {
throw std::runtime_error("expected a character literal");
}
unsigned long long multivalue = 0;
std::size_t nbytes = 0;
while (pos + 1 < str.size()) {
if (str[pos] == '\'' || str[pos] == '\n')
throw std::runtime_error("raw single quotes and newlines not allowed in character literals");
if (nbytes >= 1 && !narrow)
throw std::runtime_error("multiple characters only supported in narrow character literals");
unsigned long long value;
if (str[pos] == '\\') {
pos++;
const char escape = str[pos++];
if (pos >= str.size())
throw std::runtime_error("unexpected end of character literal");
switch (escape) {
// obscure GCC extensions
case '%':
case '(':
case '[':
case '{':
// standard escape sequences
case '\'':
case '"':
case '?':
case '\\':
value = static_cast(escape);
break;
case 'a':
value = static_cast('\a');
break;
case 'b':
value = static_cast('\b');
break;
case 'f':
value = static_cast('\f');
break;
case 'n':
value = static_cast('\n');
break;
case 'r':
value = static_cast('\r');
break;
case 't':
value = static_cast('\t');
break;
case 'v':
value = static_cast('\v');
break;
// GCC extension for ESC character
case 'e':
case 'E':
value = static_cast('\x1b');
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
// octal escape sequences consist of 1 to 3 digits
value = stringToULLbounded(str, --pos, 8, 1, 3);
break;
case 'x':
// hexadecimal escape sequences consist of at least 1 digit
value = stringToULLbounded(str, pos, 16);
break;
case 'u':
case 'U': {
// universal character names have exactly 4 or 8 digits
const std::size_t ndigits = (escape == 'u' ? 4 : 8);
value = stringToULLbounded(str, pos, 16, ndigits, ndigits);
// UTF-8 encodes code points above 0x7f in multiple code units
// code points above 0x10ffff are not allowed
if (((narrow || utf8) && value > 0x7f) || (utf16 && value > 0xffff) || value > 0x10ffff)
throw std::runtime_error("code point too large");
if (value >= 0xd800 && value = 0x80) {
// Assuming this is a UTF-8 encoded code point.
// This decoder may not completely validate the input.
// Noncharacters are neither rejected nor replaced.
int additional_bytes;
if (value >= 0xf5) // higher values would result in code points above 0x10ffff
throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid");
if (value >= 0xf0)
additional_bytes = 3;
else if (value >= 0xe0)
additional_bytes = 2;
else if (value >= 0xc2) // 0xc0 and 0xc1 are always overlong 2-bytes encodings
additional_bytes = 1;
else
throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid");
value &= (1 = str.size())
throw std::runtime_error("assumed UTF-8 encoded source, but character literal ends unexpectedly");
const unsigned char c = str[pos++];
if (((c >> 6) != 2) // ensure c has form 0xb10xxxxxx
|| (!value && additional_bytes == 1 && c < 0xa0) // overlong 3-bytes encoding
|| (!value && additional_bytes == 2 && c < 0x90)) // overlong 4-bytes encoding
throw std::runtime_error("assumed UTF-8 encoded source, but sequence is invalid");
value = (value 0xffff) || value > 0x10ffff)
throw std::runtime_error("code point too large");
}
}
if (((narrow || utf8) && value > std::numeric_limits::max()) || (utf16 && value >> 16) || value >> 32)
throw std::runtime_error("numeric escape sequence too large");
multivalue setstr(toString(simplecpp::characterLiteralToLL(tok->str())));
}
}
static void simplifyComments(simplecpp::TokenList &expr)
{
for (simplecpp::Token *tok = expr.front(); tok;) {
simplecpp::Token * const d = tok;
tok = tok->next;
if (d->comment)
expr.deleteToken(d);
}
}
/**
* @throws std::runtime_error thrown on invalid literals, missing sizeof arguments or invalid expressions,
* missing __has_include() arguments or expressions, undefined function-like macros, invalid number literals
* @throws std::overflow_error thrown on overflow or division by zero
*/
static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, const std::map &sizeOfType)
{
simplifyComments(expr);
simplifySizeof(expr, sizeOfType);
simplifyHasInclude(expr, dui);
simplifyName(expr);
simplifyNumbers(expr);
expr.constFold();
// TODO: handle invalid expressions
return expr.cfront() && expr.cfront() == expr.cback() && expr.cfront()->number ? stringToLL(expr.cfront()->str()) : 0LL;
}
static const simplecpp::Token *gotoNextLine(const simplecpp::Token *tok)
{
const unsigned int line = tok->location.line;
const unsigned int file = tok->location.fileIndex;
while (tok && tok->location.line == line && tok->location.fileIndex == file)
tok = tok->next;
return tok;
}
#ifdef SIMPLECPP_WINDOWS
class NonExistingFilesCache {
public:
NonExistingFilesCache() {}
bool contains(const std::string& path) {
std::lock_guard lock(m_mutex);
return (m_pathSet.find(path) != m_pathSet.end());
}
void add(const std::string& path) {
std::lock_guard lock(m_mutex);
m_pathSet.insert(path);
}
void clear() {
std::lock_guard lock(m_mutex);
m_pathSet.clear();
}
private:
std::set m_pathSet;
std::mutex m_mutex;
};
static NonExistingFilesCache nonExistingFilesCache;
#endif
static std::string openHeaderDirect(std::ifstream &f, const std::string &path)
{
#ifdef SIMPLECPP_WINDOWS
if (nonExistingFilesCache.contains(path))
return ""; // file is known not to exist, skip expensive file open call
#endif
f.open(path.c_str());
if (f.is_open())
return path;
#ifdef SIMPLECPP_WINDOWS
nonExistingFilesCache.add(path);
#endif
return "";
}
static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader)
{
if (simplecpp::isAbsolutePath(header))
return openHeaderDirect(f, simplecpp::simplifyPath(header));
// prefer first to search the header relatively to source file if found, when not a system header
if (!systemheader) {
std::string path = openHeaderDirect(f, simplecpp::simplifyPath(dirPath(sourcefile) + header));
if (!path.empty()) {
return path;
}
}
// search the header on the include paths (provided by the flags "-I...")
for (const auto &includePath : dui.includePaths) {
std::string path = openHeaderDirect(f, simplecpp::simplifyPath(includePath + "/" + header));
if (!path.empty())
return path;
}
return "";
}
namespace {
struct FileID {
#ifdef _WIN32
struct {
std::uint64_t VolumeSerialNumber;
struct {
std::uint64_t IdentifierHi;
std::uint64_t IdentifierLo;
} FileId;
} fileIdInfo;
bool operator==(const FileID &that) const noexcept {
return fileIdInfo.VolumeSerialNumber == that.fileIdInfo.VolumeSerialNumber &&
fileIdInfo.FileId.IdentifierHi == that.fileIdInfo.FileId.IdentifierHi &&
fileIdInfo.FileId.IdentifierLo == that.fileIdInfo.FileId.IdentifierLo;
}
#else
dev_t dev;
ino_t ino;
bool operator==(const FileID& that) const noexcept {
return dev == that.dev && ino == that.ino;
}
#endif
struct Hasher {
std::size_t operator()(const FileID &id) const {
#ifdef _WIN32
return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^
id.fileIdInfo.VolumeSerialNumber);
#else
return static_cast(id.dev) ^ static_cast(id.ino);
#endif
}
};
};
}
struct simplecpp::FileDataCache::Impl
{
void clear()
{
mIdMap.clear();
}
using id_map_type = std::unordered_map;
id_map_type mIdMap;
};
simplecpp::FileDataCache::FileDataCache()
: mImpl(new Impl)
{}
simplecpp::FileDataCache::~FileDataCache() = default;
simplecpp::FileDataCache::FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT = default;
simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) SIMPLECPP_NOEXCEPT = default;
static bool getFileId(const std::string &path, FileID &id);
std::pair simplecpp::FileDataCache::tryload(FileDataCache::name_map_type::iterator &name_it, const simplecpp::DUI &dui, std::vector &filenames, simplecpp::OutputList *outputList)
{
const std::string &path = name_it->first;
FileID fileId;
if (!getFileId(path, fileId))
return {nullptr, false};
const auto id_it = mImpl->mIdMap.find(fileId);
if (id_it != mImpl->mIdMap.end()) {
name_it->second = id_it->second;
return {id_it->second, false};
}
auto *const data = new FileData {path, TokenList(path, filenames, {}, outputList)};
if (dui.removeComments)
data->tokens.removeComments();
name_it->second = data;
mImpl->mIdMap.emplace(fileId, data);
mData.emplace_back(data);
return {data, true};
}
std::pair simplecpp::FileDataCache::get_private(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList)
{
if (isAbsolutePath(header)) {
auto ins = mNameMap.emplace(simplecpp::simplifyPath(header), nullptr);
if (ins.second) {
const auto ret = tryload(ins.first, dui, filenames, outputList);
if (ret.first != nullptr) {
return ret;
}
} else {
return {ins.first->second, false};
}
return {nullptr, false};
}
if (!systemheader) {
auto ins = mNameMap.emplace(simplecpp::simplifyPath(dirPath(sourcefile) + header), nullptr);
if (ins.second) {
const auto ret = tryload(ins.first, dui, filenames, outputList);
if (ret.first != nullptr) {
return ret;
}
} else if (ins.first->second != nullptr) {
return {ins.first->second, false};
}
}
for (const auto &includePath : dui.includePaths) {
auto ins = mNameMap.emplace(simplecpp::simplifyPath(includePath + "/" + header), nullptr);
if (ins.second) {
const auto ret = tryload(ins.first, dui, filenames, outputList);
if (ret.first != nullptr) {
return ret;
}
} else if (ins.first->second != nullptr) {
return {ins.first->second, false};
}
}
return {nullptr, false};
}
std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList)
{
auto ret = get_private(sourcefile, header, dui, systemheader, filenames, outputList);
if (mLoadCallback && ret.first)
mLoadCallback(*ret.first, ret.second);
return ret;
}
void simplecpp::FileDataCache::clear()
{
mImpl->clear();
mNameMap.clear();
mData.clear();
}
static bool getFileId(const std::string &path, FileID &id)
{
#ifdef _WIN32
HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return false;
BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &id.fileIdInfo, sizeof(id.fileIdInfo));
if (!ret) {
const DWORD err = GetLastError();
if (err == ERROR_INVALID_PARAMETER || // encountered when using a non-NTFS filesystem e.g. exFAT
err == ERROR_NOT_SUPPORTED) // encountered on Windows Server Core (used as a Docker container)
{
BY_HANDLE_FILE_INFORMATION fileInfo;
ret = GetFileInformationByHandle(hFile, &fileInfo);
if (ret) {
id.fileIdInfo.VolumeSerialNumber = static_cast(fileInfo.dwVolumeSerialNumber);
id.fileIdInfo.FileId.IdentifierHi = static_cast(fileInfo.nFileIndexHigh);
id.fileIdInfo.FileId.IdentifierLo = static_cast(fileInfo.nFileIndexLow);
}
}
}
CloseHandle(hFile);
return ret == TRUE;
#else
struct stat statbuf;
if (stat(path.c_str(), &statbuf) != 0)
return false;
id.dev = statbuf.st_dev;
id.ino = statbuf.st_ino;
return true;
#endif
}
simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, FileDataCache cache)
{
#ifdef SIMPLECPP_WINDOWS
if (dui.clearIncludeCache)
nonExistingFilesCache.clear();
#endif
std::list filelist;
// -include files
for (auto it = dui.includes.cbegin(); it != dui.includes.cend(); ++it) {
const std::string &filename = *it;
const auto loadResult = cache.get("", filename, dui, false, filenames, outputList);
const bool loaded = loadResult.second;
FileData *const filedata = loadResult.first;
if (filedata == nullptr) {
if (outputList) {
simplecpp::Output err{
simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND,
{},
"Can not open include file '" + filename + "' that is explicitly included."
};
outputList->emplace_back(std::move(err));
}
continue;
}
if (!loaded)
continue;
if (!filedata->tokens.front())
continue;
if (dui.removeComments)
filedata->tokens.removeComments();
filelist.emplace_back(filedata->tokens.front());
}
for (const Token *rawtok = rawtokens.cfront(); rawtok || !filelist.empty(); rawtok = rawtok ? rawtok->next : nullptr) {
if (rawtok == nullptr) {
rawtok = filelist.back();
filelist.pop_back();
}
if (rawtok->op != '#' || sameline(rawtok->previousSkipComments(), rawtok))
continue;
rawtok = rawtok->nextSkipComments();
if (!rawtok || rawtok->str() != INCLUDE)
continue;
const std::string &sourcefile = rawtokens.file(rawtok->location);
const Token * const htok = rawtok->nextSkipComments();
if (!sameline(rawtok, htok))
continue;
const bool systemheader = (htok->str()[0] == '') {
TokenString hdr;
// TODO: Sometimes spaces must be added in the string
// Somehow preprocessToken etc must be told that the location should be source location not destination location
for (const Token *tok = inc2.cfront(); tok; tok = tok->next) {
hdr += tok->str();
}
inc2.clear();
inc2.push_back(new Token(hdr, inc1.cfront()->location));
inc2.front()->op = '')
header += tok->str();
if (tok && tok->op == '>')
closingAngularBracket = true;
} else {
header = tok->str().substr(1U, tok->str().size() - 2U);
closingAngularBracket = true;
}
if (tok) {
std::ifstream f;
const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader);
expr.push_back(new Token(header2.empty() ? "0" : "1", tok->location));
}
}
if (par)
tok = tok ? tok->next : nullptr;
if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')') || (!closingAngularBracket)) {
if (outputList) {
Output out{
Output::SYNTAX_ERROR,
rawtok->location,
"failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"
};
outputList->emplace_back(std::move(out));
}
output.clear();
return;
}
continue;
}
maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location);
const Token *tmp = tok;
if (!preprocessToken(expr, tmp, macros, files, outputList, dui)) {
output.clear();
return;
}
if (!tmp)
break;
tok = tmp->previous;
}
try {
if (ifCond) {
std::string E;
for (const simplecpp::Token *tok = expr.cfront(); tok; tok = tok->next)
E += (E.empty() ? "" : " ") + tok->str();
const long long result = evaluate(expr, dui, sizeOfType);
conditionIsTrue = (result != 0);
ifCond->emplace_back(rawtok->location, E, result);
} else {
const long long result = evaluate(expr, dui, sizeOfType);
conditionIsTrue = (result != 0);
}
} catch (const std::runtime_error &e) {
if (outputList) {
std::string msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition";
if (e.what() && *e.what())
msg += std::string(", ") + e.what();
Output out{
Output::SYNTAX_ERROR,
rawtok->location,
std::move(msg)
};
outputList->emplace_back(std::move(out));
}
output.clear();
return;
}
}
if (rawtok->str() != ELIF && rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF) {
// push a new ifstate..
if (ifstates.top() != True)
ifstates.push(AlwaysFalse);
else
ifstates.push(conditionIsTrue ? True : ElseIsTrue);
iftokens.push(rawtok);
} else {
if (ifstates.top() == True)
ifstates.top() = AlwaysFalse;
else if (ifstates.top() == ElseIsTrue && conditionIsTrue)
ifstates.top() = True;
iftokens.top()->nextcond = rawtok;
iftokens.top() = rawtok;
}
} else if (rawtok->str() == ELSE) {
ifstates.top() = (ifstates.top() == ElseIsTrue) ? True : AlwaysFalse;
iftokens.top()->nextcond = rawtok;
iftokens.top() = rawtok;
} else if (rawtok->str() == ENDIF) {
ifstates.pop();
iftokens.top()->nextcond = rawtok;
iftokens.pop();
} else if (rawtok->str() == UNDEF) {
if (ifstates.top() == True) {
const Token *tok = rawtok->next;
while (sameline(rawtok,tok) && tok->comment)
tok = tok->next;
if (sameline(rawtok, tok))
macros.erase(tok->str());
}
} else if (ifstates.top() == True && rawtok->str() == PRAGMA && rawtok->next && rawtok->next->str() == ONCE && sameline(rawtok,rawtok->next)) {
pragmaOnce.insert(rawtokens.file(rawtok->location));
}
if (ifstates.top() != True && rawtok->nextcond)
rawtok = rawtok->nextcond->previous;
else
rawtok = gotoNextLine(rawtok);
continue;
}
if (ifstates.top() != True) {
// drop code
rawtok = gotoNextLine(rawtok);
continue;
}
bool hash=false, hashhash=false;
if (rawtok->op == '#' && sameline(rawtok,rawtok->next)) {
if (rawtok->next->op != '#') {
hash = true;
rawtok = rawtok->next; // skip '#'
} else if (sameline(rawtok,rawtok->next->next)) {
hashhash = true;
rawtok = rawtok->next->next; // skip '#' '#'
}
}
const Location loc(rawtok->location);
TokenList tokens(files);
if (!preprocessToken(tokens, rawtok, macros, files, outputList, dui)) {
output.clear();
return;
}
if (hash || hashhash) {
std::string s;
for (const Token *hashtok = tokens.cfront(); hashtok; hashtok = hashtok->next)
s += hashtok->str();
if (hash)
output.push_back(new Token('\"' + s + '\"', loc));
else if (output.back())
output.back()->setstr(output.cback()->str() + s);
else
output.push_back(new Token(s, loc));
} else {
output.takeTokens(tokens);
}
}
if (macroUsage) {
for (simplecpp::MacroMap::const_iterator macroIt = macros.begin(); macroIt != macros.end(); ++macroIt) {
const Macro ¯o = macroIt->second;
std::list usage = macro.usage();
const std::list& temp = maybeUsedMacros[macro.name()];
usage.insert(usage.end(), temp.begin(), temp.end());
for (std::list::const_iterator usageIt = usage.begin(); usageIt != usage.end(); ++usageIt) {
MacroUsage mu(macro.valueDefinedInCode());
mu.macroName = macro.name();
mu.macroLocation = macro.defineLocation();
mu.useLocation = *usageIt;
macroUsage->emplace_back(std::move(mu));
}
}
}
}
void simplecpp::cleanup(FileDataCache &cache)
{
cache.clear();
}
simplecpp::cstd_t simplecpp::getCStd(const std::string &std)
{
if (std == "c90" || std == "c89" || std == "iso9899:1990" || std == "iso9899:199409" || std == "gnu90" || std == "gnu89")
return C89;
if (std == "c99" || std == "c9x" || std == "iso9899:1999" || std == "iso9899:199x" || std == "gnu99" || std == "gnu9x")
return C99;
if (std == "c11" || std == "c1x" || std == "iso9899:2011" || std == "gnu11" || std == "gnu1x")
return C11;
if (std == "c17" || std == "c18" || std == "iso9899:2017" || std == "iso9899:2018" || std == "gnu17" || std == "gnu18")
return C17;
if (std == "c23" || std == "gnu23" || std == "c2x" || std == "gnu2x")
return C23;
if (std == "c2y" || std == "gnu2y")
return C2Y;
return CUnknown;
}
const char *simplecpp::getCStdName(cstd_t std)
{
switch (std) {
case CUnknown: return "C";
case C89: return "C89";
case C99: return "C99";
case C11: return "C11";
case C17: return "C17";
case C23: return "C23";
case C2Y: return "C2Y";
}
return "";
}
std::string simplecpp::getCStdString(cstd_t std)
{
switch (std) {
case C89:
// __STDC_VERSION__ is not set for C90 although the macro was added in the 1994 amendments
return "";
case C99:
return "199901L";
case C11:
return "201112L";
case C17:
return "201710L";
case C23:
// supported by GCC 9+ and Clang 9+
// Clang 9, 10, 11, 12, 13 return "201710L"
// Clang 14, 15, 16, 17 return "202000L"
// Clang 9, 10, 11, 12, 13, 14, 15, 16, 17 do not support "c23" and "gnu23"
return "202311L";
case C2Y:
// supported by GCC 15+ and Clang 19+
// Clang 19, 20, 21, 22 return "202400L"
return "202500L";
case CUnknown:
return "";
}
return "";
}
std::string simplecpp::getCStdString(const std::string &std)
{
return getCStdString(getCStd(std));
}
simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std)
{
if (std == "c++98" || std == "c++03" || std == "gnu++98" || std == "gnu++03")
return CPP03;
if (std == "c++11" || std == "gnu++11" || std == "c++0x" || std == "gnu++0x")
return CPP11;
if (std == "c++14" || std == "c++1y" || std == "gnu++14" || std == "gnu++1y")
return CPP14;
if (std == "c++17" || std == "c++1z" || std == "gnu++17" || std == "gnu++1z")
return CPP17;
if (std == "c++20" || std == "c++2a" || std == "gnu++20" || std == "gnu++2a")
return CPP20;
if (std == "c++23" || std == "c++2b" || std == "gnu++23" || std == "gnu++2b")
return CPP23;
if (std == "c++26" || std == "c++2c" || std == "gnu++26" || std == "gnu++2c")
return CPP26;
return CPPUnknown;
}
const char *simplecpp::getCppStdName(cppstd_t std)
{
switch (std) {
case CPPUnknown: return "C++";
case CPP03: return "C++03";
case CPP11: return "C++11";
case CPP14: return "C++14";
case CPP17: return "C++17";
case CPP20: return "C++20";
case CPP23: return "C++23";
case CPP26: return "C++26";
}
return "";
}
std::string simplecpp::getCppStdString(cppstd_t std)
{
switch (std) {
case CPP03:
return "199711L";
case CPP11:
return "201103L";
case CPP14:
return "201402L";
case CPP17:
return "201703L";
case CPP20:
// GCC 10 returns "201703L" - correct in 11+
return "202002L";
case CPP23:
// supported by GCC 11+ and Clang 12+
// GCC 11, 12, 13 return "202100L"
// Clang 12, 13, 14, 15, 16 do not support "c++23" and "gnu++23" and return "202101L"
// Clang 17, 18 return "202302L"
return "202302L";
case CPP26:
// supported by GCC 14+ and Clang 17+
return "202400L";
case CPPUnknown:
return "";
}
return "";
}
std::string simplecpp::getCppStdString(const std::string &std)
{
return getCppStdString(getCppStd(std));
}