Files
openvehicle-api/tests/unit_tests/idl_compiler/preproc_test.cpp
tompzf 6ed4b1534e Precommit (#1)
* first commit

* cleanup
2025-11-04 13:28:06 +01:00

778 lines
28 KiB
C++

#include "includes.h"
#include "parser_test.h"
#include "../../../sdv_executables/sdv_idl_compiler/preproc.cpp"
#include "../../../sdv_executables/sdv_idl_compiler/macro.cpp"
#include "../../../sdv_executables/sdv_idl_compiler/parser.h"
#include "../../../sdv_executables/sdv_idl_compiler/exception.h"
#include <fstream>
using CPreprocTest = CParserTest;
TEST_F(CPreprocTest, PreprocessorUnknownDirective)
{
// Unknown preproc error
EXPECT_THROW(CParser(nullptr).LexicalCheck(), CCompileException);
EXPECT_THROW(CParser("#abc").LexicalCheck(), CCompileException);
}
TEST_F(CPreprocTest, PreprocessorWhitespace)
{
// Parse preprocessor with additional whitespace before
// Single preprocessing directive
EXPECT_TRUE(CParser(" #define TEST").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser(" # define TEST").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("\t#\tdefine TEST").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser(" \n#define TEST").LexicalCheck().GetEnvironment().Defined("TEST"));
// Multiple preprocessing directives
EXPECT_TRUE(CParser(R"code(
# if defined HELLO
# define NO_TEST
# else
# define TEST
# endif)code").LexicalCheck().GetEnvironment().Defined("TEST"));
// Tests bug fix: #381248
// Verbatim block test
EXPECT_NO_THROW(CParser(R"code(#verbatim_begin
// ...
#verbatim_end)code").Parse());
EXPECT_NO_THROW(CParser(R"code( # verbatim_begin
// ...
# not valid text...
# verbatim_end)code").Parse());
}
TEST_F(CPreprocTest, PreprocessorMacroDefined)
{
// Parse preprocessor
EXPECT_TRUE(CParser("#define TEST").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST()").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST(a)").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST(a, b,c)").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST value").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST(a) value a").LexicalCheck().GetEnvironment().Defined("TEST"));
EXPECT_TRUE(CParser("#define TEST(a, b,c) value a*b*c").LexicalCheck().GetEnvironment().Defined("TEST"));
}
TEST_F(CPreprocTest, PreprocessorDefineRedefine)
{
// Redefinition
EXPECT_NO_THROW(CParser("#define TEST(a, b,c) value a*b*c\n#define TEST(a, b,c) value a*b*c").LexicalCheck());
EXPECT_THROW(CParser("#define TEST(a, b,c) value a*b*c\n#define TEST").LexicalCheck(), CCompileException);
}
TEST_F(CPreprocTest, PreprocessorDefineAndSingleExpand)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define ABBA 2\n"
"#define ABBABA 3\n"
"#define AB(c) AB##c\n"
"#define BA(c) AB ## c\n"
"#define TEXT(c) # c"
);
parser.LexicalCheck();
// Code will be prepended with an empty tring
CCodePos codeTest("");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("TEST", codeTest));
EXPECT_TRUE(static_cast<std::string>(codeTest.GetLocation()).empty());
// Code will be prepended with the value "2"
CCodePos codeAbba("");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("ABBA", codeAbba));
EXPECT_EQ(static_cast<std::string>(codeAbba.GetLocation()), "2");
// Code will be prepended with "ABBA" and expanded to "2"
CCodePos codeAbba2("(BA)");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("AB", codeAbba2));
EXPECT_EQ(static_cast<std::string>(codeAbba2.GetLocation()), "2");
// Code will be prepended with "ABBA" and expanded to "2"
CCodePos codeAbba3("(BA)");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("BA", codeAbba3));
EXPECT_EQ(static_cast<std::string>(codeAbba3.GetLocation()), "2");
// Code will be prepende with "\"hello\""
CCodePos codeText("(hello)");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("TEXT", codeText));
EXPECT_EQ(static_cast<std::string>(codeText.GetLocation()), "\"hello\"");
// Code will be prepended with "ABBA"
CCodePos codeAbba4("(B)A");
EXPECT_TRUE(parser.GetEnvironment().TestAndExpand("AB", codeAbba4));
CToken tokenAbba4 = codeAbba4.GetLocation();
++codeAbba4; // Skip prepended 'A'
++codeAbba4; // Skip prepended 'B'
++codeAbba4; // Skip prepended 'C'
++codeAbba4; // Skip 'A'
codeAbba4.UpdateLocation(tokenAbba4);
EXPECT_EQ(static_cast<std::string>(tokenAbba4), "ABBA");
}
TEST_F(CPreprocTest, PreprocessorDefineAndExpand)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define ABBA 2\n"
"#define ABBABA 3\n"
"#define AB(c) AB##c\n"
"#define BA(c) AB ## c\n"
"#define DE AB(BA) ## BA\n"
"#define EF ABBA ## BA\n"
"#define CIRCULAR CIRCULAR\n"
"#define MULTI(a, b) a ## b\n"
"#define MULTI_SPACE(a, b) a \\\n \\\r\n\t b\n"
"#define MULTI_COMMENT1(a, b) a // ## b\n"
"#define MULTI_COMMENT2(a, b) a /* ## */ b\n"
"#define TEXT(c) # c\n"
"#define Fx abc\n"
"#define Bx def\n"
"#define FB(arg) #arg\n"
"#define FB1(arg) FB(arg)\n"
"#define DOx(x) x\n"
"#define CIRCULAR2 DOx(CIRCULAR2)\n"
"#define CIRCULAR3 CIRCULAR"
);
parser.LexicalCheck();
// Simple parsing function
auto fnGetLastProcessedToken = [&](CCodePos& rCode) -> CToken
{
CToken token;
while (!rCode.HasEOF())
{
// If the current position is not part of the macro expansion, reset the set of macros used in a expansion.
bool bInMacroExpansion = rCode.CurrentPositionInMacroExpansion();
// Get a token
token = parser.GetLexer().GetToken(rCode, nullptr);
// Check whether the token is an identifier, if so, check for any macro
if (token.GetType() == ETokenType::token_identifier)
{
if (parser.GetEnvironment().TestAndExpand(static_cast<std::string>(token).c_str(), rCode, bInMacroExpansion))
continue; // macro was replaced, get a token again.
}
}
return token;
};
// Multiple arguments.
CCodePos codeMulti("FB1(MULTI(abc, def))");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeMulti)), "\"abcdef\"");
// Comments are ignored in expansion.
CCodePos codeComment1("FB1(MULTI_COMMENT1(abc, def))");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeComment1)), "\"abc\"");
CCodePos codeComment2("FB1(MULTI_COMMENT2(abc, def))");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeComment2)), "\"abc def\"");
CCodePos codeComment3("FB1(MULTI(abc /*the first*/, /*the second*/def))");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeComment3)), "\"abcdef\"");
// Macro is formed using another macro and expanded itself to "2".
CCodePos codeAbba5("AB(B)A");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeAbba5)), "2");
// Strings from arguments - argument expansion only when not stringificating.
// Version 1 stringificates the argument without expansion
// Version 2 expands the argument first and then stringificates
CCodePos codeFB0("FB(Fx Bx)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeFB0)), "\"Fx Bx\"");
CCodePos codeFB1("FB1(Fx Bx)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeFB1)), "\"abc def\"");
// Reduce large spaces to one space
// Version 1 has space in the arguments, which will be reduced to one space
// Version 2 has space in the definition, which will be reduced to one space
CCodePos codeSpace1("FB1(Fx \t\\\n \t\t\\\r\n \tBx)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeSpace1)), "\"abc def\"");
CCodePos codeSpace2("FB1(MULTI_SPACE(abc, def))");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeSpace2)), "\"abc def\"");
// Expand results before added to the code
// Version 1 expands ABBA into 2 and then adds A
// VERSION 2 glues ABBA to BA and expands ABABA into 3
CCodePos codeRes1("FB1(DE)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeRes1)), "\"2BA\"");
CCodePos codeRes2("FB1(EF)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeRes2)), "\"3\"");
// Circular referencing
// Version 1 calls itself
// Version 2 calls another macro calling itsel
// Version 3 has multiple circular macros in one parameter
CCodePos codeCircular1("FB1(CIRCULAR)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeCircular1)), "\"CIRCULAR\"");
CCodePos codeCircular2("FB1(CIRCULAR2)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeCircular2)), "\"CIRCULAR2\"");
CCodePos codeCircular3("FB1(CIRCULAR3 CIRCULAR CIRCULAR3)");
EXPECT_EQ(static_cast<std::string>(fnGetLastProcessedToken(codeCircular3)), "\"CIRCULAR CIRCULAR CIRCULAR\"");
}
TEST_F(CPreprocTest, PreprocessorUndef)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define ABBA 2\n"
"#define ABBABA 3\n"
"#define AB(c) AB##c\n"
"#define BA(c) AB ## c\n"
"#undef TEST\n"
"#undef ABRACADABRA\n"
"#undef ABBABA with dummy text... is ignored\n"
"#undef BA // with comments"
);
parser.LexicalCheck();
EXPECT_FALSE(parser.GetEnvironment().Defined("TEST"));
EXPECT_TRUE(parser.GetEnvironment().Defined("ABBA"));
EXPECT_FALSE(parser.GetEnvironment().Defined("ABBABA"));
EXPECT_TRUE(parser.GetEnvironment().Defined("AB"));
EXPECT_FALSE(parser.GetEnvironment().Defined("BA"));
}
TEST_F(CPreprocTest, PreprocessorIncludeLocal)
{
// Create a dummy files
std::ofstream fstreamNormal("dummy_source_test_preprocessor_include.tmp", std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamNormal.is_open());
fstreamNormal << "#define HELLO2";
fstreamNormal.close();
std::ofstream fstreamCircular("dummy_source_test_preprocessor_include_circular.tmp", std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamCircular.is_open());
fstreamCircular << "#include \"dummy_source_test_preprocessor_include_circular.tmp\"";
fstreamCircular.close();
std::filesystem::create_directory("dummy1");
std::filesystem::path pathDummy1File = "dummy1/dummy1_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy1Subdir(pathDummy1File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy1Subdir.is_open());
fstreamDummy1Subdir << "#define DUMMY1";
fstreamDummy1Subdir.close();
std::filesystem::create_directory("dummy2");
std::filesystem::path pathDummy2File = "dummy2/dummy2_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy2Subdir(pathDummy2File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy2Subdir.is_open());
fstreamDummy2Subdir << "#include \"../dummy3/dummy3_subdir_source_test_preprocessor_include.tmp\"";
fstreamDummy2Subdir.close();
std::filesystem::create_directory("dummy3");
std::filesystem::path pathDummy3File = "dummy3/dummy3_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy3Subdir(pathDummy3File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy3Subdir.is_open());
fstreamDummy3Subdir << "#define DUMMY3\n#include \"dummy4_subdir_source_test_preprocessor_include.tmp\"";
fstreamDummy3Subdir.close();
std::filesystem::path pathDummy4File = "dummy3/dummy4_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy4Subdir(pathDummy4File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy4Subdir.is_open());
fstreamDummy4Subdir << "#define DUMMY4";
fstreamDummy4Subdir.close();
// Make include dirs (1st argument is the executable file path).
std::vector<std::string> rvecArgs = {"idl_compiler_test", "-Idummy1", "-Idummy2"};
CIdlCompilerEnvironment environment(rvecArgs);
// Check non-existing
EXPECT_THROW(CParser("#include \"non_existing_file.tmp\"", environment).LexicalCheck(), CCompileException);
// Check local
EXPECT_TRUE(CParser("#include \"dummy_source_test_preprocessor_include.tmp\"", environment).LexicalCheck().
GetEnvironment().Defined("HELLO2"));
// Check local with relative path
EXPECT_TRUE(CParser("#include \"dummy1/dummy1_subdir_source_test_preprocessor_include.tmp\"").LexicalCheck().
GetEnvironment().Defined("DUMMY1"));
// Check circular (re-inclusion is prevented)
EXPECT_NO_THROW(CParser("#include \"dummy_source_test_preprocessor_include_circular.tmp\"", environment).LexicalCheck());
// Check through include path
EXPECT_TRUE(CParser("#include \"dummy1_subdir_source_test_preprocessor_include.tmp\"", environment).LexicalCheck().
GetEnvironment().Defined("DUMMY1"));
// Check local includes from an included file not in the search path
EXPECT_TRUE(CParser("#include \"dummy2_subdir_source_test_preprocessor_include.tmp\"", environment).LexicalCheck().
GetEnvironment().Defined("DUMMY4"));
try
{
std::filesystem::remove("dummy_source_test_preprocessor_include.tmp");
std::filesystem::remove("dummy_source_test_preprocessor_include_circular.tmp");
std::filesystem::remove_all("dummy1");
std::filesystem::remove_all("dummy2");
std::filesystem::remove_all("dummy3");
} catch (const std::filesystem::filesystem_error&)
{}
}
TEST_F(CPreprocTest, PreprocessorIncludeLocalAbsolute)
{
// Create a dummy files
std::filesystem::path pathCurrent = std::filesystem::current_path();
std::ofstream fstreamNormal("dummy_source_test_preprocessor_include.tmp", std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamNormal.is_open());
fstreamNormal << "#include \"" << (pathCurrent / "dummy" / "dummy_subdir_source_test_preprocessor_include.tmp").generic_u8string() << "\"";
fstreamNormal.close();
std::filesystem::create_directory("dummy");
std::filesystem::path pathDummy1File = "dummy/dummy_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummySubdir(pathDummy1File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummySubdir.is_open());
fstreamDummySubdir << "#define HELLO";
fstreamDummySubdir.close();
// Check access through path (should not be possible)
EXPECT_THROW(CParser("#include \"dummy_subdir_source_test_preprocessor_include.tmp\"").LexicalCheck(), CCompileException);
// Check relative path
EXPECT_TRUE(CParser("#include \"dummy/dummy_subdir_source_test_preprocessor_include.tmp\"").LexicalCheck().
GetEnvironment().Defined("HELLO"));
// Check direct absolute path
std::string ssCode = "#include \"";
ssCode += (pathCurrent / "dummy" / "dummy_subdir_source_test_preprocessor_include.tmp").generic_u8string();
ssCode += "\"";
EXPECT_TRUE(CParser(ssCode.c_str()).LexicalCheck().
GetEnvironment().Defined("HELLO"));
// Check indirect absolute path
EXPECT_TRUE(CParser("#include \"dummy_source_test_preprocessor_include.tmp\"").LexicalCheck().
GetEnvironment().Defined("HELLO"));
try
{
std::filesystem::remove("dummy_source_test_preprocessor_include.tmp");
std::filesystem::remove_all("dummy");
} catch (const std::filesystem::filesystem_error&)
{}
}
TEST_F(CPreprocTest, PreprocessorIncludeGlobal)
{
// Create a dummy files
std::ofstream fstreamNormal("dummy_source_test_preprocessor_include.tmp", std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamNormal.is_open());
fstreamNormal << "#define HELLO2";
fstreamNormal.close();
std::ofstream fstreamCircular("dummy_source_test_preprocessor_include_circular.tmp", std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamCircular.is_open());
fstreamCircular << "#include \"dummy_source_test_preprocessor_include_circular.tmp\"";
fstreamCircular.close();
std::filesystem::create_directory("dummy1");
std::filesystem::path pathDummy1File = "dummy1/dummy1_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy1Subdir(pathDummy1File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy1Subdir.is_open());
fstreamDummy1Subdir << "#define DUMMY1";
fstreamDummy1Subdir.close();
std::filesystem::create_directory("dummy2");
std::filesystem::path pathDummy2File = "dummy2/dummy2_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy2Subdir(pathDummy2File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy2Subdir.is_open());
fstreamDummy2Subdir << "#include \"../dummy3/dummy3_subdir_source_test_preprocessor_include.tmp\"";
fstreamDummy2Subdir.close();
std::filesystem::create_directory("dummy3");
std::filesystem::path pathDummy3File = "dummy3/dummy3_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy3Subdir(pathDummy3File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy3Subdir.is_open());
fstreamDummy3Subdir << "#define DUMMY3\n#include \"dummy4_subdir_source_test_preprocessor_include.tmp\"";
fstreamDummy3Subdir.close();
std::filesystem::path pathDummy4File = "dummy3/dummy4_subdir_source_test_preprocessor_include.tmp";
std::ofstream fstreamDummy4Subdir(pathDummy4File.string(), std::ios_base::out | std::ios_base::trunc);
ASSERT_TRUE(fstreamDummy4Subdir.is_open());
fstreamDummy4Subdir << "#define DUMMY4";
fstreamDummy4Subdir.close();
// Make include dirs (1st argument is the executable file path).
std::vector<std::string> rvecArgs = {"idl_compiler_test.exe", "-Idummy1", "-Idummy2"};
CIdlCompilerEnvironment environment(rvecArgs);
// Check non-existing
EXPECT_THROW(CParser("#include <non_existing_file.tmp>", environment).LexicalCheck(), CCompileException);
// Check local
EXPECT_THROW(CParser("#include <dummy_source_test_preprocessor_include.tmp>", environment).LexicalCheck(), CCompileException);
// Check local with relative path
EXPECT_THROW(CParser("#include <dummy1/dummy1_subdir_source_test_preprocessor_include.tmp>").LexicalCheck(), CCompileException);
// Check through include path
EXPECT_TRUE(CParser("#include <dummy1_subdir_source_test_preprocessor_include.tmp>", environment).LexicalCheck().
GetEnvironment().Defined("DUMMY1"));
// Check local includes from an included file not in the search path
EXPECT_TRUE(CParser("#include <dummy2_subdir_source_test_preprocessor_include.tmp>", environment).LexicalCheck().
GetEnvironment().Defined("DUMMY4"));
try
{
std::filesystem::remove("dummy_source_test_preprocessor_include.tmp");
std::filesystem::remove("dummy_source_test_preprocessor_include_circular.tmp");
std::filesystem::remove_all("dummy1");
std::filesystem::remove_all("dummy2");
std::filesystem::remove_all("dummy3");
} catch (const std::filesystem::filesystem_error&)
{}
}
TEST_F(CPreprocTest, PreprocessorIf)
{
// Direct if with integer
EXPECT_TRUE(CParser(
"#if 1\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Direct if with boolean
EXPECT_TRUE(CParser(
"#if true\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Direct if with character
EXPECT_TRUE(CParser(
"#if 'a'\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Direct if with macro
EXPECT_TRUE(CParser(
"#define a 10\n"
"#if a\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Direct if with zerod macro
EXPECT_TRUE(CParser(
"#define a 0\n"
"#if a\n"
"#define INCORRECT\n"
"#else\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Direct if with defined macro without value
EXPECT_THROW(CParser(
"#define a\n"
"#if a\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// Direct if with non-existent macro (equals to 0)
EXPECT_TRUE(CParser(
"#if a\n"
"#define INCORRECT\n"
"#else\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Incomplete if statement
EXPECT_THROW(CParser(
"#if\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// Correct if section using macros
EXPECT_TRUE(CParser(
"#define a 10\n"
"#define b(c, d) c*d\n"
"#define e 10\n"
"#if a + b(5, 5) + e == 45\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Incorrect if section using macros
EXPECT_FALSE(CParser(
"#define a 10\n"
"#define b(c, d) c*d\n"
"#define e 10\n"
"#if a + b(5, 5) + e == 45\n"
"#define CORRECT\n"
"#else\n"
"#define INCORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("INCORRECT"));
// Parenthesis while calculating
EXPECT_TRUE(CParser(
"#define a 10\n"
"#define b(c, d) c*d\n"
"#define e 10\n"
"#if a * (b(5, 5) + e) == 350\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Operators + - / * %
EXPECT_TRUE(CParser(
"#if 10 + 11 * 35 % 10 - 2 == 13\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Operators << >> & | ~
// NOTE: '!=' comes before '&'
EXPECT_TRUE(CParser(
"#if (2 << 2 | 7 >> 1 & (~ 120 & 7)) != 11\n"
"#define INCORRECT\n"
"#else\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Operators && || < > <= >= == !=
EXPECT_TRUE(CParser(
"#if (20 || 10 && 1) < 2\n"
"#define CORRECT1 1\n"
"#endif\n"
"#if 50 + 7 >= 57\n"
"#define CORRECT2 1\n"
"#endif\n"
"#if 50 + 8 > 57\n"
"#define CORRECT3 1\n"
"#endif\n"
"#if 50 + 7 <= 57\n"
"#define CORRECT4 1\n"
"#endif\n"
"#if 10 != TEST\n"
"#define CORRECT5 1\n"
"#endif\n"
"#if CORRECT1 + CORRECT2 + CORRECT3 + CORRECT4 + CORRECT5 == 5\n"
"#define CORRECT\n"
"#endif\n"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
// Div by zero /
EXPECT_THROW(CParser(
"#if 10 / TEST\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// Div by zero %
EXPECT_THROW(CParser(
"#if 10 % TEST\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// Incomplete
EXPECT_THROW(CParser(
"#define TEST\n"
"#if 10 % TEST\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// Exclude code
EXPECT_NO_THROW(CParser(
"#if 0\n"
"bla bla\n"
"#endif"
).LexicalCheck());
// Exclude code incomplete
EXPECT_THROW(CParser(
"#if 0\n"
"bla bla\n"
).LexicalCheck(), CCompileException);
}
TEST_F(CPreprocTest, PreprocessorDefinedOperator)
{
// defined(macro)
EXPECT_TRUE(CParser(
"#if !defined(TEST)\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
EXPECT_TRUE(CParser(
"#define TEST\n"
"#if defined(TEST)\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
EXPECT_THROW(CParser(
"#if defined()\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
// defined macro
EXPECT_TRUE(CParser(
"#if !defined TEST\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
EXPECT_TRUE(CParser(
"#define TEST\n"
"#if defined TEST\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck().GetEnvironment().Defined("CORRECT"));
EXPECT_THROW(CParser(
"#if defined\n"
"#define CORRECT\n"
"#endif"
).LexicalCheck(), CCompileException);
}
TEST_F(CPreprocTest, PreprocessorElif)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define AB(c) AB##c\n"
"#ifdef TOAST\n"
"#define XY\n"
"#elif defined(TEST)\n"
"#define VW\n"
"#endif"
);
parser.LexicalCheck();
EXPECT_TRUE(parser.GetEnvironment().Defined("TEST"));
EXPECT_FALSE(parser.GetEnvironment().Defined("XY"));
EXPECT_TRUE(parser.GetEnvironment().Defined("VW"));
}
TEST_F(CPreprocTest, PreprocessorIfDef)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define AB(c) AB##c\n"
"#ifdef TEST\n"
"#define XY\n"
"#ifdef AB\n"
"#define VW\n"
"#endif\n"
"#endif\n"
"#ifdef TEST2\n"
"#define MN\n"
"#endif"
);
parser.LexicalCheck();
EXPECT_TRUE(parser.GetEnvironment().Defined("TEST"));
EXPECT_TRUE(parser.GetEnvironment().Defined("AB"));
EXPECT_TRUE(parser.GetEnvironment().Defined("XY"));
EXPECT_TRUE(parser.GetEnvironment().Defined("VW"));
EXPECT_FALSE(parser.GetEnvironment().Defined("MD"));
}
TEST_F(CPreprocTest, PreprocessorIfnDef)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#define AB(c) AB##c\n"
"#ifndef TEST\n"
"#define XY\n"
"#ifndef AB\n"
"#define VW\n"
"#endif\n"
"#endif\n"
"#ifndef TEST2\n"
"#define MN\n"
"#endif"
);
parser.LexicalCheck();
EXPECT_TRUE(parser.GetEnvironment().Defined("TEST"));
EXPECT_TRUE(parser.GetEnvironment().Defined("AB"));
EXPECT_FALSE(parser.GetEnvironment().Defined("XY"));
EXPECT_FALSE(parser.GetEnvironment().Defined("VW"));
EXPECT_TRUE(parser.GetEnvironment().Defined("MN"));
}
TEST_F(CPreprocTest, PreprocessorElse)
{
// Parse preprocessor
CParser parser(
"#define TEST\n"
"#ifdef TEST\n"
"#define XY\n"
"#else\n"
"#define VW\n"
"#endif\n"
"#ifdef TEST2\n"
"#define ZA\n"
"#else\n"
"#define TR\n"
"#endif"
);
parser.LexicalCheck();
EXPECT_TRUE(parser.GetEnvironment().Defined("TEST"));
EXPECT_TRUE(parser.GetEnvironment().Defined("XY"));
EXPECT_FALSE(parser.GetEnvironment().Defined("VW"));
EXPECT_FALSE(parser.GetEnvironment().Defined("ZA"));
EXPECT_TRUE(parser.GetEnvironment().Defined("TR"));
}
TEST_F(CPreprocTest, ProcessVerbatim)
{
// Parse preprocessor
CParser parser(
"#verbatim const int i = 10\n"
"#verbatim const int j = 10\\n"
"#verbatim const int k = 10\n"
"#verbatim #define L\n"
"#verbatim #include <string>"
);
EXPECT_NO_THROW(parser.LexicalCheck());
}
TEST_F(CPreprocTest, ProcessVerbatimBlock)
{
// Parse preprocessor
CParser parser(
"#verbatim_begin ignored text\n"
"const int j = 10\\n"
"const int k = 10\n"
"#define L\n"
"#include <string>\n"
"#verbatim_end ignored text"
);
EXPECT_NO_THROW(parser.LexicalCheck());
CParser parser2(
"#verbatim_end ignored text"
);
EXPECT_THROW(parser2.LexicalCheck(), CCompileException);
CParser parser3(
"#verbatim_begin ignored text\n"
"const int j = 10\\n"
"const int k = 10\n"
"#define L\n"
"#include <string>\n"
);
EXPECT_THROW(parser3.LexicalCheck(), CCompileException);
}