From 40b8f3d7216f72cf20f388647f89aa88176b9462 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 13:02:28 +0200 Subject: [PATCH 01/13] 686 --- main.cpp | 9 +- simplecpp.cpp | 43 ++++++-- simplecpp.h | 67 ++++++------ test.cpp | 276 ++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 271 insertions(+), 124 deletions(-) diff --git a/main.cpp b/main.cpp index abb07228..c1621544 100644 --- a/main.cpp +++ b/main.cpp @@ -224,7 +224,7 @@ int main(int argc, char **argv) { simplecpp::TokenList *rawtokens; if (toklist_inf == Fstream) { - rawtokens = new simplecpp::TokenList(f,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(f,files,filename,dui,&outputList); } else if (toklist_inf == Sstream || toklist_inf == CharBuffer) { std::ostringstream oss; @@ -233,14 +233,14 @@ int main(int argc, char **argv) const std::string s = oss.str(); if (toklist_inf == Sstream) { std::istringstream iss(s); - rawtokens = new simplecpp::TokenList(iss,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(iss,files,filename,dui,&outputList); } else { - rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,&outputList); + rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,dui,&outputList); } } else { f.close(); - rawtokens = new simplecpp::TokenList(filename,files,&outputList); + rawtokens = new simplecpp::TokenList(filename,files,dui,&outputList); } rawtokens->removeComments(); simplecpp::FileDataCache filedata; @@ -276,6 +276,7 @@ int main(int argc, char **argv) std::cerr << "directive as macro parameter: "; break; case simplecpp::Output::PORTABILITY_BACKSLASH: + case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: std::cerr << "portability: "; break; case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: diff --git a/simplecpp.cpp b/simplecpp.cpp index 5ab6aa30..fefe2d08 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -475,26 +475,26 @@ namespace { simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} -simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, OutputList *outputList) +simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdIStream stream(istr); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/) +simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdCharBufStream stream(data, size); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList) +simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { try { FileStream stream(filename, filenames); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } catch (const simplecpp::Output & e) { outputList->emplace_back(e); } @@ -659,13 +659,23 @@ void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int l static const std::string COMMENT_END("*/"); -void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) +void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const DUI &dui, OutputList *outputList) { unsigned int multiline = 0U; bool trailing_nl = true; const Token *oldLastToken = nullptr; + unsigned long maxline; + { + cstd_t cstd = getCStd(dui.std); + cppstd_t cppstd = getCppStd(dui.std); + if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) + maxline = 32767; + else + maxline = 2147483647; + } + Location location(fileIndex(filename), 1, 1); while (stream.good()) { unsigned char ch = stream.readChar(); @@ -730,7 +740,22 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (!ppTok || !ppTok->number) continue; - const unsigned int line = std::atol(ppTok->str().c_str()); + unsigned long line = 0; + try { + line = std::stoul(ppTok->str()); + } catch (...) {} + + if (line == 0 || line > maxline) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, + location, + "Line number out of range: " + ppTok->str() + "." + }; + outputList->emplace_back(std::move(err)); + } + } + ppTok = advanceAndSkipComments(ppTok); unsigned int fileindex; @@ -3163,7 +3188,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat return {id_it->second, false}; } - auto *const data = new FileData {path, TokenList(path, filenames, outputList)}; + auto *const data = new FileData {path, TokenList(path, filenames, {}, outputList)}; if (dui.removeComments) data->tokens.removeComments(); diff --git a/simplecpp.h b/simplecpp.h index 10f7d2a8..3396eae5 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -249,6 +249,7 @@ namespace simplecpp { SYNTAX_ERROR, DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, + PORTABILITY_LINE_DIRECTIVE, PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, @@ -262,6 +263,21 @@ namespace simplecpp { using OutputList = std::list; + /** + * Command line preprocessor settings. + * On the command line these are configured by -D, -U, -I, --include, -std + */ + struct SIMPLECPP_LIB DUI { + DUI() = default; + std::list defines; + std::set undefined; + std::list includePaths; + std::list includes; + std::string std; + bool clearIncludeCache{}; + bool removeComments{}; /** remove comment tokens from included files */ + }; + /** List of tokens. */ class SIMPLECPP_LIB TokenList { public: @@ -269,45 +285,45 @@ namespace simplecpp { explicit TokenList(std::vector &filenames); /** generates a token list from the given std::istream parameter */ - TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); + TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** generates a token list from the given buffer */ template - TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size-1, filenames, filename, outputList, 0) + TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size-1, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ template - TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size-1, filenames, filename, outputList, 0) + TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size-1, filenames, filename, dui, outputList, 0) {} #if SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size, filenames, filename, outputList, 0) + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size, filenames, filename, outputList, 0) + TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size, filenames, filename, dui, outputList, 0) {} #endif // SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} #ifdef __cpp_lib_span /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data.data(), data.size(), filenames, filename, dui, outputList, 0) {} #endif // __cpp_lib_span /** generates a token list from the given filename parameter */ - TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); + TokenList(const std::string &filename, std::vector &filenames, const DUI &dui = {}, OutputList *outputList = nullptr); TokenList(const TokenList &other); TokenList(TokenList &&other); ~TokenList(); @@ -323,7 +339,7 @@ namespace simplecpp { void dump(bool linenrs = false) const; std::string stringify(bool linenrs = false) const; - void readfile(Stream &stream, const std::string &filename=std::string(), OutputList *outputList = nullptr); + void readfile(Stream &stream, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** * @throws std::overflow_error thrown on overflow or division by zero * @throws std::runtime_error thrown on invalid expressions @@ -387,7 +403,7 @@ namespace simplecpp { const std::string& file(const Location& loc) const; private: - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/); + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/); void combineOperators(); @@ -436,21 +452,6 @@ namespace simplecpp { long long result; // condition result }; - /** - * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std - */ - struct SIMPLECPP_LIB DUI { - DUI() = default; - std::list defines; - std::set undefined; - std::list includePaths; - std::list includes; - std::string std; - bool clearIncludeCache{}; - bool removeComments{}; /** remove comment tokens from included files */ - }; - struct SIMPLECPP_LIB FileData { /** The canonical filename associated with this data */ std::string filename; diff --git a/test.cpp b/test.cpp index 1771526c..645801a9 100644 --- a/test.cpp +++ b/test.cpp @@ -105,42 +105,48 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons #define TEST_CASE(F) (testcase(#F, F, argc, argv)) -static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { switch (USE_INPUT) { case Input::Stringstream: { std::istringstream istr(std::string(code, size)); - return {istr,filenames,filename,outputList}; + return {istr,filenames,filename,dui,outputList}; } case Input::CharBuffer: - return {{code, size}, filenames, filename, outputList}; + return {{code, size}, filenames, filename, dui, outputList}; } return simplecpp::TokenList{filenames}; // unreachable - needed for GCC and Visual Studio } -static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { - return makeTokenList(code, strlen(code), filenames, filename, outputList); + return makeTokenList(code, strlen(code), filenames, filename, dui, outputList); } -static std::string readfile(const char code[], simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { std::vector files; - return makeTokenList(code,files,std::string(),outputList).stringify(); + return makeTokenList(code, strlen(code), files, std::string(), dui, outputList); } -static std::string readfile(const char code[], std::size_t size, simplecpp::OutputList *outputList=nullptr) +static std::string readfile(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { std::vector files; - return makeTokenList(code,size,files,std::string(),outputList).stringify(); + return makeTokenList(code,files,std::string(),dui,outputList).stringify(); +} + +static std::string readfile(const char code[], std::size_t size, const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) +{ + std::vector files; + return makeTokenList(code,size,files,std::string(),dui,outputList).stringify(); } static std::string preprocess(const char code[], std::size_t size, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) { std::vector files; simplecpp::FileDataCache cache; - simplecpp::TokenList tokens = makeTokenList(code, size, files, file); + simplecpp::TokenList tokens = makeTokenList(code, size, files, file, dui, outputList); if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); @@ -244,15 +250,15 @@ static void backslash() // preprocessed differently simplecpp::OutputList outputList; - readfile("//123 \\\n456\n", &outputList); + readfile("//123 \\\n456\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("//123 \\ \n456\n", &outputList); + readfile("//123 \\ \n456\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); outputList.clear(); - readfile("#define A \\\n123\n", &outputList); + readfile("#define A \\\n123\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("#define A \\ \n123\n", &outputList); + readfile("#define A \\ \n123\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); } @@ -927,7 +933,7 @@ static void define_invalid_1() static void define_invalid_2() { - const char code[] = "#define\nhas#"; + const char code[] = "#define\nhas#\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); @@ -936,7 +942,7 @@ static void define_invalid_2() static void define_invalid_3() { const char code[] = "#define R()\n" - "R"; + "R\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); @@ -945,7 +951,7 @@ static void define_invalid_3() static void define_invalid_4() { const char code[] = "#define X(...)\n" - "X"; + "X\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); @@ -1255,7 +1261,7 @@ static void define_va_opt_3() // non-escaped newline without closing parenthesis const char code1[] = "#define err(...) __VA_OPT__(printf( __VA_ARGS__);\n" ")\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1267,7 +1273,7 @@ static void define_va_opt_3() // non-escaped newline without open parenthesis const char code2[] = "#define err(...) __VA_OPT__\n" "(something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1278,7 +1284,7 @@ static void define_va_opt_4() { // missing parenthesis const char code1[] = "#define err(...) __VA_OPT__ something\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1289,7 +1295,7 @@ static void define_va_opt_4() // missing open parenthesis const char code2[] = "#define err(...) __VA_OPT__ something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1300,7 +1306,7 @@ static void define_va_opt_5() { // parenthesis not directly proceeding __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__ something (something)\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1312,7 +1318,7 @@ static void define_va_opt_6() { // nested __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__(__VA_OPT__(something))\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1323,7 +1329,7 @@ static void define_va_opt_6() static void define_va_opt_7() { // eof in __VA_OPT__ - const char code1[] = "#define err(...) __VA_OPT__"; + const char code1[] = "#define err(...) __VA_OPT__\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1332,7 +1338,7 @@ static void define_va_opt_7() outputList.clear(); - const char code2[] = "#define err(...) __VA_OPT__("; + const char code2[] = "#define err(...) __VA_OPT__(\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1340,7 +1346,7 @@ static void define_va_opt_7() outputList.clear(); - const char code3[] = "#define err(...) __VA_OPT__(x"; + const char code3[] = "#define err(...) __VA_OPT__(x\n"; ASSERT_EQUALS("", preprocess(code3, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1350,7 +1356,7 @@ static void define_va_opt_7() static void define_va_opt_8() { const char code[] = "#define f(...) #__VA_OPT__(x)\n" - "const char* v1 = f();"; + "const char* v1 = f();\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\nconst char * v1 = \"\" ;", preprocess(code, &outputList)); @@ -1382,7 +1388,7 @@ static void define_ifdef() static void if_invalid_1() { - const char code[] = "#if'\\u'"; + const char code[] = "#if'\\u'\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, expected digit\n", toString(outputList)); @@ -1390,7 +1396,7 @@ static void if_invalid_1() static void if_invalid_2() { - const char code[] = "#if-0xBBB4444444444444%~B"; + const char code[] = "#if-0xBBB4444444444444%~B\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division overflow\n", toString(outputList)); @@ -1398,7 +1404,7 @@ static void if_invalid_2() static void if_invalid_3() { - const char code[] = "#if@u'\\udefa'"; + const char code[] = "#if@u'\\udefa'\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, surrogate code points not allowed in universal character names\n", toString(outputList)); @@ -1456,7 +1462,7 @@ static void error3() " bla bla.\"\n"; std::vector files; simplecpp::OutputList outputList; - const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", &outputList); + const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -1699,19 +1705,19 @@ static void hashhash9() simplecpp::OutputList outputList; code = "#define A +##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '+' and 'x' yields an invalid token.\n", toString(outputList)); code = "#define A 2##=\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '2' and '=' yields an invalid token.\n", toString(outputList)); code = "#define A <<##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '<<' and 'x' yields an invalid token.\n", toString(outputList)); @@ -1880,7 +1886,7 @@ static void hashhash_int_literal() static void hashhash_invalid_1() { - const char code[] = "#define f(a) (##x)\nf(1)"; + const char code[] = "#define f(a) (##x)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token '('\n", toString(outputList)); @@ -1888,7 +1894,7 @@ static void hashhash_invalid_1() static void hashhash_invalid_2() { - const char code[] = "#define f(a) (x##)\nf(1)"; + const char code[] = "#define f(a) (x##)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token ')'\n", toString(outputList)); @@ -1897,7 +1903,7 @@ static void hashhash_invalid_2() static void hashhash_invalid_string_number() { const char code[] = - "#define BAD(x) x##12345\nBAD(\"ABC\")"; + "#define BAD(x) x##12345\nBAD(\"ABC\")\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1907,7 +1913,7 @@ static void hashhash_invalid_string_number() static void hashhash_invalid_missing_args() { const char code[] = - "#define BAD(x) ##x\nBAD()"; + "#define BAD(x) ##x\nBAD()\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1941,7 +1947,7 @@ static void hashhash_va_args_unexpected() { const char code[] = "#define C(...)!##__VA_ARGS__\n" - "C(1)"; + "C(1)\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Unexpected token '!'\n", toString(outputList)); @@ -1950,7 +1956,7 @@ static void hashhash_va_args_unexpected() static void hashhash_universal_character() { const char code[] = - "#define A(x,y) x##y\nint A(\\u01,04);"; + "#define A(x,y) x##y\nint A(\\u01,04);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\u01' and '04' yields universal character '\\u0104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -1959,7 +1965,7 @@ static void hashhash_universal_character() static void hashhash_universal_character_2() { const char code[] = - "#define A(x,y) x##y\nint A(\\U0104, 0104);"; + "#define A(x,y) x##y\nint A(\\U0104, 0104);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\U0104' and '0104' yields universal character '\\U01040104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -2098,7 +2104,7 @@ static void has_include_6() static void define_has_include_invalid_1() { const char code[] = "#define A)__has_include\n" - "#if\u000BA"; + "#if\u000BA\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); @@ -2107,7 +2113,7 @@ static void define_has_include_invalid_1() static void define_has_include_invalid_2() { const char code[] = "#define f __has_include\n" - "#if#f<"; + "#if#f<\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); @@ -2116,18 +2122,18 @@ static void define_has_include_invalid_2() static void define_has_include_invalid_3() { const char code[] = "#define\u0000X\u0007__has_include(\n" - "#if%X&"; + "#if%X&\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); } static void define_has_include_invalid_4() { const char code[] = "#define\u0000X\u0000__has_include<2\n" - "#if*X"; + "#if*X\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); } @@ -2276,7 +2282,7 @@ static void ifDefinedNestedNoPar() static void ifDefinedInvalid1() // #50 - invalid unterminated defined { - const char code[] = "#if defined(A"; + const char code[] = "#if defined(A\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2284,7 +2290,7 @@ static void ifDefinedInvalid1() // #50 - invalid unterminated defined static void ifDefinedInvalid2() { - const char code[] = "#if defined"; + const char code[] = "#if defined\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2584,6 +2590,7 @@ static void location12() preprocess(code)); } + static void missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; @@ -2847,6 +2854,117 @@ static void nullDirective3() ASSERT_EQUALS("\n\n\n\nx = 1 ;", preprocess(code)); } +static void lineDirective() +{ + std::vector filenames; + + for (const std::string std : {"c89", "c90", "c++03"}) { + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 0.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 32768.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 18446744073709551617.\n", + toString(outputList)); + } + } + + for (const std::string std : {"c99", "c++11"}) { + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 0.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 2147483648.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,Line number out of range: 18446744073709551617.\n", + toString(outputList)); + } + } +} + static void include1() { const char code[] = "#include \"A.h\"\n"; @@ -3086,7 +3204,7 @@ static void readfile_nullbyte() { const char code[] = "ab\0cd\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1, &outputList)); + ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1,{},&outputList)); ASSERT_EQUALS(true, outputList.empty()); // should warning be written? } @@ -3114,11 +3232,11 @@ static void readfile_char_error() { simplecpp::OutputList outputList; - readfile("A = L's", &outputList); + readfile("A = L's", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = 's\n'", &outputList); + readfile("A = 's\n'", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); } @@ -3172,36 +3290,36 @@ static void readfile_string_error() { simplecpp::OutputList outputList; - readfile("A = \"abs", &outputList); + readfile("A = \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = u8\"abs\n\"", &outputList); + readfile("A = u8\"abs\n\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as\n(abc)as\"", &outputList); + readfile("A = R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = u8R\"as\n(abc)as\"", &outputList); + readfile("A = u8R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as(abc)a\"", &outputList); + readfile("A = R\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = LR\"as(abc)a\"", &outputList); + readfile("A = LR\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("#define A \"abs", &outputList); + readfile("#define A \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); // Don't warn for a multiline define - readfile("#define A \"abs\\\n\"\n", &outputList); + readfile("#define A \"abs\\\n\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -3213,11 +3331,11 @@ static void readfile_cpp14_number() static void readfile_unhandled_chars() { simplecpp::OutputList outputList; - readfile("// 你好世界\n", &outputList); + readfile("// 你好世界\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("s=\"你好世界\"\n", &outputList); + readfile("s=\"你好世界\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("int 你好世界=0;\n", &outputList); + readfile("int 你好世界=0;\n", {}, &outputList); ASSERT_EQUALS("file0,1,unhandled_char_error,The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported.\n", toString(outputList)); } @@ -3233,7 +3351,7 @@ static void readfile_file_not_found() { simplecpp::OutputList outputList; std::vector files; - (void)simplecpp::TokenList("NotAFile", files, &outputList); + (void)simplecpp::TokenList("NotAFile", files, {}, &outputList); ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } @@ -3242,61 +3360,61 @@ static void readfile_no_eof_newline() { const char code[] = ""; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "\\\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "#define A\\"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "/* comment \n comment */"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "/* comment \n comment */\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } } @@ -3510,7 +3628,7 @@ static void unicode_invalid() static void warning() { - const char code[] = "#warning MSG\n1"; + const char code[] = "#warning MSG\n1\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\n1", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,#warning,#warning MSG\n", toString(outputList)); @@ -3606,17 +3724,17 @@ static void preprocessSizeOf() { simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess("#if 3 > sizeof", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof A", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof A\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, invalid sizeof expression\n", toString(outputList)); } @@ -4340,6 +4458,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(nullDirective2); TEST_CASE(nullDirective3); + TEST_CASE(lineDirective); + TEST_CASE(include1); TEST_CASE(include2); TEST_CASE(include3); From be6e79ef3dd92af6cc839834c4a0ff10b7f284c0 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 14:13:44 +0200 Subject: [PATCH 02/13] Fix warnings --- test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test.cpp b/test.cpp index 645801a9..a98173c9 100644 --- a/test.cpp +++ b/test.cpp @@ -223,6 +223,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; + case simplecpp::Output::Type::PORTABILITY_LINE_DIRECTIVE: + ostr << "portability_line_directive,"; + break; case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: ostr << "portability_no_eof_newline,"; break; From a1240b3c81d1aa445c122c139c36f167c532efd1 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 14:15:29 +0200 Subject: [PATCH 03/13] Fix tests --- test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test.cpp b/test.cpp index a98173c9..152177cf 100644 --- a/test.cpp +++ b/test.cpp @@ -2871,7 +2871,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 0.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0.\n", toString(outputList)); } @@ -2890,7 +2890,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 32768.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 32768.\n", toString(outputList)); } @@ -2900,7 +2900,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 18446744073709551617.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617.\n", toString(outputList)); } } @@ -2915,7 +2915,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 0.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0.\n", toString(outputList)); } @@ -2952,7 +2952,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 2147483648.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648.\n", toString(outputList)); } @@ -2962,7 +2962,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,Line number out of range: 18446744073709551617.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617.\n", toString(outputList)); } } From e5ecbd4f43ee83f1cb394c72a1e6335df44f26c7 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 14:17:17 +0200 Subject: [PATCH 04/13] Fix formatting --- simplecpp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index 3396eae5..a389c0a3 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -264,9 +264,9 @@ namespace simplecpp { using OutputList = std::list; /** - * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std - */ + * Command line preprocessor settings. + * On the command line these are configured by -D, -U, -I, --include, -std + */ struct SIMPLECPP_LIB DUI { DUI() = default; std::list defines; From d4f1c1f98510c19df8b5077065159d599c3bd0ce Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 14:19:35 +0200 Subject: [PATCH 05/13] Fix clang-tidy issues --- simplecpp.cpp | 10 ++++++---- test.cpp | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index fefe2d08..f1e02c59 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -668,8 +668,8 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, unsigned long maxline; { - cstd_t cstd = getCStd(dui.std); - cppstd_t cppstd = getCppStd(dui.std); + const cstd_t cstd = getCStd(dui.std); + const cppstd_t cppstd = getCppStd(dui.std); if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) maxline = 32767; else @@ -740,10 +740,12 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (!ppTok || !ppTok->number) continue; - unsigned long line = 0; + unsigned long line; try { line = std::stoul(ppTok->str()); - } catch (...) {} + } catch (...) { + line = 0; + } if (line == 0 || line > maxline) { if (outputList) { diff --git a/test.cpp b/test.cpp index 152177cf..1310b46c 100644 --- a/test.cpp +++ b/test.cpp @@ -2859,8 +2859,6 @@ static void nullDirective3() static void lineDirective() { - std::vector filenames; - for (const std::string std : {"c89", "c90", "c++03"}) { simplecpp::DUI dui; dui.std = std; From 036b59d10620f4b2517ffb109ab97dfa2fa9d24d Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 15:49:15 +0200 Subject: [PATCH 06/13] Add range, support type, and standard in error messages --- simplecpp.cpp | 63 +++++++++++++++++++++++++++--------- simplecpp.h | 10 ++++-- test.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 139 insertions(+), 24 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index f1e02c59..68958ad9 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -666,15 +666,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const Token *oldLastToken = nullptr; + const cstd_t cstd = getCStd(dui.std); + const bool std_is_c = cstd != CUnknown; + const cppstd_t cppstd = getCppStd(dui.std, std_is_c ? CPPUnknown : CPP26); // use C++26 by default + unsigned long maxline; - { - const cstd_t cstd = getCStd(dui.std); - const cppstd_t cppstd = getCppStd(dui.std); - if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) - maxline = 32767; - else - maxline = 2147483647; - } + if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) + maxline = 32767; + else + maxline = 2147483647; Location location(fileIndex(filename), 1, 1); while (stream.good()) { @@ -744,15 +744,22 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, try { line = std::stoul(ppTok->str()); } catch (...) { - line = 0; + line = std::numeric_limits::max(); } if (line == 0 || line > maxline) { if (outputList) { + std::string msg = "Line number out of range: " + ppTok->str() + ". "; + if (line == 0) { + msg += "Line number zero is undefined behavior."; + } else { + const char *support_type = cppstd == CPP26 ? "conditionally supported" : "undefined behavior"; + std::string std_name = std_is_c ? getCStdName(cstd) : getCppStdName(cppstd); + msg += "Line numbers above " + std::to_string(maxline) + " are " + support_type + " in " + std_name + "."; + } simplecpp::Output err{ simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, - location, - "Line number out of range: " + ppTok->str() + "." + location, msg }; outputList->emplace_back(std::move(err)); } @@ -3997,7 +4004,7 @@ void simplecpp::cleanup(FileDataCache &cache) cache.clear(); } -simplecpp::cstd_t simplecpp::getCStd(const std::string &std) +simplecpp::cstd_t simplecpp::getCStd(const std::string &std, cstd_t dflt) { if (std == "c90" || std == "c89" || std == "iso9899:1990" || std == "iso9899:199409" || std == "gnu90" || std == "gnu89") return C89; @@ -4011,7 +4018,19 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) return C23; if (std == "c2y" || std == "gnu2y") return C2Y; - return CUnknown; + return dflt; +} + +const char *simplecpp::getCStdName(cstd_t std) +{ + switch (std) { + case CUnknown: return "C"; + case C89: return "C89"; + case C99: return "C99"; + case C11: return "C17"; + case C23: return "C23"; + case C2Y: return "C2Y"; + } } std::string simplecpp::getCStdString(cstd_t std) @@ -4047,7 +4066,7 @@ std::string simplecpp::getCStdString(const std::string &std) return getCStdString(getCStd(std)); } -simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) +simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std, cppstd_t dflt) { if (std == "c++98" || std == "c++03" || std == "gnu++98" || std == "gnu++03") return CPP03; @@ -4063,7 +4082,21 @@ simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) return CPP23; if (std == "c++26" || std == "c++2c" || std == "gnu++26" || std == "gnu++2c") return CPP26; - return CPPUnknown; + return dflt; +} + +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"; + } } std::string simplecpp::getCppStdString(cppstd_t std) diff --git a/simplecpp.h b/simplecpp.h index a389c0a3..48b82b5b 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -588,10 +588,16 @@ namespace simplecpp { SIMPLECPP_LIB std::string convertCygwinToWindowsPath(const std::string &cygwinPath); /** Returns the C version a given standard */ - SIMPLECPP_LIB cstd_t getCStd(const std::string &std); + SIMPLECPP_LIB cstd_t getCStd(const std::string &std, cstd_t dflt = CUnknown); + + /** Returns the name of a C standard */ + SIMPLECPP_LIB const char *getCStdName(cstd_t std); /** Returns the C++ version a given standard */ - SIMPLECPP_LIB cppstd_t getCppStd(const std::string &std); + SIMPLECPP_LIB cppstd_t getCppStd(const std::string &std, cppstd_t dflt = CPPUnknown); + + /** Returns the name of a C++ standard */ + SIMPLECPP_LIB const char *getCppStdName(cppstd_t std); /** Returns the __STDC_VERSION__ value for a given standard */ SIMPLECPP_LIB std::string getCStdString(const std::string &std); diff --git a/test.cpp b/test.cpp index 1310b46c..9d11ed17 100644 --- a/test.cpp +++ b/test.cpp @@ -2860,6 +2860,12 @@ static void nullDirective3() static void lineDirective() { for (const std::string std : {"c89", "c90", "c++03"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + simplecpp::DUI dui; dui.std = std; @@ -2869,7 +2875,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", toString(outputList)); } @@ -2888,7 +2894,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 32768.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 32768. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", toString(outputList)); } @@ -2898,12 +2904,18 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", toString(outputList)); } } - for (const std::string std : {"c99", "c++11"}) { + for (const std::string std : {"c99", "c++11", "c++14", "c++17", "c++20", "c++23"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + simplecpp::DUI dui; dui.std = std; @@ -2913,7 +2925,71 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + } + + { + std::string std_name = "C++26"; + + simplecpp::DUI dui; + dui.std = "c++26"; + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", toString(outputList)); } @@ -2950,7 +3026,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", toString(outputList)); } @@ -2960,7 +3036,7 @@ static void lineDirective() ";\n"; simplecpp::OutputList outputList; makeTokenList(code, dui, &outputList); - ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617.\n", + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", toString(outputList)); } } From 3ee1302b79c085d60f309b7d3027ba859fc6a635 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 15:52:03 +0200 Subject: [PATCH 07/13] Fix getCStdName --- simplecpp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 68958ad9..271fdd7a 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -4027,7 +4027,8 @@ const char *simplecpp::getCStdName(cstd_t std) case CUnknown: return "C"; case C89: return "C89"; case C99: return "C99"; - case C11: return "C17"; + case C11: return "C11"; + case C17: return "C17"; case C23: return "C23"; case C2Y: return "C2Y"; } From b33c236b0791f2ff52ccf8582f3136976f694be6 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 15:55:51 +0200 Subject: [PATCH 08/13] Fix warnings --- simplecpp.cpp | 2 ++ test.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 271fdd7a..17ec656b 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -4032,6 +4032,7 @@ const char *simplecpp::getCStdName(cstd_t std) case C23: return "C23"; case C2Y: return "C2Y"; } + return ""; } std::string simplecpp::getCStdString(cstd_t std) @@ -4098,6 +4099,7 @@ const char *simplecpp::getCppStdName(cppstd_t std) case CPP23: return "C++23"; case CPP26: return "C++26"; } + return ""; } std::string simplecpp::getCppStdString(cppstd_t std) diff --git a/test.cpp b/test.cpp index 9d11ed17..abd8d63b 100644 --- a/test.cpp +++ b/test.cpp @@ -2909,7 +2909,7 @@ static void lineDirective() } } - for (const std::string std : {"c99", "c++11", "c++14", "c++17", "c++20", "c++23"}) { + for (const std::string std : {"c99", "c11", "c17", "c23", "c2y", "c++11", "c++14", "c++17", "c++20", "c++23"}) { std::string std_name; if (simplecpp::getCStd(std) != simplecpp::CUnknown) std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); From d9fbdcdd53c227dfd28b767e0b0f17a7fdd3b907 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 16:07:42 +0200 Subject: [PATCH 09/13] Fix clang-tidy issues --- simplecpp.cpp | 7 ++++--- test.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 17ec656b..2077cc7b 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -753,9 +753,10 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (line == 0) { msg += "Line number zero is undefined behavior."; } else { - const char *support_type = cppstd == CPP26 ? "conditionally supported" : "undefined behavior"; - std::string std_name = std_is_c ? getCStdName(cstd) : getCppStdName(cppstd); - msg += "Line numbers above " + std::to_string(maxline) + " are " + support_type + " in " + std_name + "."; + msg += "Line numbers above " + std::to_string(maxline) + " are " + + (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + + " in " + + (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; } simplecpp::Output err{ simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, diff --git a/test.cpp b/test.cpp index abd8d63b..27fafd06 100644 --- a/test.cpp +++ b/test.cpp @@ -2978,7 +2978,7 @@ static void lineDirective() } { - std::string std_name = "C++26"; + const std::string std_name = "C++26"; simplecpp::DUI dui; dui.std = "c++26"; From b9a7613e72d851e36439626e11ca8ab7e50f30a6 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 16:22:32 +0200 Subject: [PATCH 10/13] Fix formatting --- simplecpp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 2077cc7b..f773db01 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -754,9 +754,9 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, msg += "Line number zero is undefined behavior."; } else { msg += "Line numbers above " + std::to_string(maxline) + " are " + - (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + - " in " + - (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; + (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + + " in " + + (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; } simplecpp::Output err{ simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, From 96423345924c63226b718bc589900a72c3929a73 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 16:25:02 +0200 Subject: [PATCH 11/13] Fix formatting --- simplecpp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index f773db01..50196209 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -755,8 +755,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } else { msg += "Line numbers above " + std::to_string(maxline) + " are " + (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + - " in " + - (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; + " in " + (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; } simplecpp::Output err{ simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, From 40a815fafaa398a1b2642959f455a58136b8e609 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 16:40:17 +0200 Subject: [PATCH 12/13] Fix formatting --- simplecpp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 50196209..ca787dac 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -754,8 +754,8 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, msg += "Line number zero is undefined behavior."; } else { msg += "Line numbers above " + std::to_string(maxline) + " are " + - (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + - " in " + (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; + (cppstd == CPP26 ? "conditionally supported" : "undefined behavior") + + " in " + (std_is_c ? getCStdName(cstd) : getCppStdName(cppstd)) + "."; } simplecpp::Output err{ simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, From 0cca41f84bb1d8c823474d573304cf96fec2b52b Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 22 Jul 2026 17:22:21 +0200 Subject: [PATCH 13/13] Add syntax error for signed line numbers --- simplecpp.cpp | 13 +++++++++++++ test.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index ca787dac..e67c0289 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -737,6 +737,19 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ppTok->str() == "line") ppTok = advanceAndSkipComments(ppTok); + if (ppTok && (ppTok->str()[0] == '-' || ppTok->str()[0] == '+')) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::SYNTAX_ERROR, + location, + "Invalid character in line directive: '" + ppTok->str() + "'." + }; + outputList->emplace_back(std::move(err)); + } + clear(); + return; + } + if (!ppTok || !ppTok->number) continue; diff --git a/test.cpp b/test.cpp index 27fafd06..e56e196a 100644 --- a/test.cpp +++ b/test.cpp @@ -2869,6 +2869,16 @@ static void lineDirective() simplecpp::DUI dui; dui.std = std; + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + { const char code[] = "#line 0\n" @@ -2919,6 +2929,16 @@ static void lineDirective() simplecpp::DUI dui; dui.std = std; + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + { const char code[] = "#line 0\n" @@ -2983,6 +3003,16 @@ static void lineDirective() simplecpp::DUI dui; dui.std = "c++26"; + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + { const char code[] = "#line 0\n"