- Replace `scc_probe_stream_consume` with `scc_probe_stream_next` for consistent stream advancement - Remove redundant `scc_probe_stream_reset` calls before peeking, as `next` and `peek` handle state - Update `scc_cstring_new` to `scc_cstring_create` and `scc_pos_init` to `scc_pos_create` for naming consistency - Change `scc_pp_macro_get` parameter to `const scc_cstring_t*` for better const-correctness - Improves code clarity and maintains proper stream position tracking
215 lines
8.3 KiB
C
215 lines
8.3 KiB
C
#include <assert.h>
|
|
#include <pprocessor.h>
|
|
#include <string.h>
|
|
#include <utest/acutest.h>
|
|
|
|
static cbool process_input(const char *input, scc_cstring_t *output) {
|
|
scc_pproc_t pp;
|
|
scc_mem_probe_stream_t mem_stream;
|
|
scc_probe_stream_t *output_stream;
|
|
|
|
// 初始化预处理器
|
|
output_stream =
|
|
scc_pproc_init(&pp, scc_mem_probe_stream_init(&mem_stream, input,
|
|
strlen(input), false));
|
|
|
|
// 获取输出结果
|
|
int ch;
|
|
*output = scc_cstring_create();
|
|
while (1) {
|
|
ch = scc_probe_stream_consume(output_stream);
|
|
if (ch == scc_stream_eof) {
|
|
break;
|
|
}
|
|
scc_cstring_append_ch(output, (char)ch);
|
|
}
|
|
// 清理资源
|
|
scc_pproc_drop(&pp);
|
|
|
|
return true;
|
|
}
|
|
|
|
#define CHECK_PP_OUTPUT_EXACT(input, expect) \
|
|
do { \
|
|
scc_cstring_t output; \
|
|
process_input(input, &output); \
|
|
assert(output.data != NULL); \
|
|
TEST_CHECK(strcmp(output.data, expect) == 0); \
|
|
TEST_MSG("Expected: %s", expect); \
|
|
TEST_MSG("Produced: %s", output.data); \
|
|
} while (0)
|
|
|
|
#define CHECK_PP_OUTPUT_CONTAIN(input, expect) \
|
|
do { \
|
|
scc_cstring_t output; \
|
|
process_input(input, &output); \
|
|
assert(output.data != NULL); \
|
|
TEST_CHECK(strstr(output.data, expect) != NULL); \
|
|
TEST_MSG("Expected: %s", expect); \
|
|
TEST_MSG("Produced: %s", output.data); \
|
|
} while (0)
|
|
|
|
static void test_define_simple_object_macro(void) {
|
|
TEST_CASE("simple object-like macro");
|
|
CHECK_PP_OUTPUT_EXACT("#define MAX 100\nMAX\n", "100\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define NAME test\r\nNAME\n", "test\n");
|
|
}
|
|
|
|
static void test_define_complex_object_macro(void) {
|
|
TEST_CASE("complex object-like macro");
|
|
CHECK_PP_OUTPUT_EXACT("#define VALUE (100 + 50)\nVALUE\n", "(100 + 50)\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define PI 3.14159\nPI\n", "3.14159\n");
|
|
}
|
|
|
|
static void test_define_object_macro_backspace(void) {
|
|
TEST_CASE("object-like macro check backspace");
|
|
CHECK_PP_OUTPUT_EXACT("#define MAX 100\nMAX\n", "100\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define NAME \ttest\r\nNAME\n", "test\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define \tVALUE (100 \t+ 50)\nVALUE\n",
|
|
"(100 + 50)\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define \tPI \t 3.14159\nPI\n", "3.14159\n");
|
|
}
|
|
|
|
static void test_define_function_macro(void) {
|
|
TEST_CASE("function-like macro");
|
|
CHECK_PP_OUTPUT_EXACT("#define ADD(a,b) a + b\nADD(1, 2)\n", "1 + 2\n");
|
|
CHECK_PP_OUTPUT_EXACT(
|
|
"#define MAX(a,b) ((a) > (b) ? (a) : (b))\nMAX(10, 20)\n",
|
|
"((10) > (20) ? (10) : (20))\n");
|
|
}
|
|
|
|
static void test_define_stringify_operator(void) {
|
|
TEST_CASE("stringify operator (#)");
|
|
CHECK_PP_OUTPUT_EXACT("#define STRINGIFY(x) #x\nSTRINGIFY(hello)\n",
|
|
"\"hello\"\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define STR(x) #x\nSTR(test value)\n",
|
|
"\"test value\"\n");
|
|
}
|
|
|
|
static void test_define_concat_operator(void) {
|
|
TEST_CASE("concatenation operator (##)");
|
|
CHECK_PP_OUTPUT_EXACT("#define CONCAT(a,b) a##b\nCONCAT(hello,world)\n",
|
|
"helloworld\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define JOIN(pre,suf) pre ## suf\nJOIN(var, 123)\n",
|
|
"var123\n");
|
|
}
|
|
|
|
static void test_define_nested_macros(void) {
|
|
TEST_CASE("nested macros");
|
|
CHECK_PP_OUTPUT_EXACT(
|
|
"#define MAX 100\n#define TWICE_MAX (MAX * 2)\nTWICE_MAX\n",
|
|
"(100 * 2)\n");
|
|
CHECK_PP_OUTPUT_EXACT(
|
|
"#define A 1\n#define B (A + 1)\n#define C (B + 1)\nC\n",
|
|
"((1 + 1) + 1)\n");
|
|
}
|
|
|
|
static void test_undef_macros(void) {
|
|
TEST_CASE("test_undef_macros");
|
|
CHECK_PP_OUTPUT_EXACT("#define x 1\n"
|
|
"x\n"
|
|
"#undef x\n"
|
|
"x\n"
|
|
"#define x 2\n"
|
|
"x\n",
|
|
"1\nx\n2\n");
|
|
}
|
|
|
|
static void hard_test_define_func_macros(void) {
|
|
TEST_CASE("func_macros_hard with pp_01");
|
|
CHECK_PP_OUTPUT_EXACT("#define hash_hash # ## #\n"
|
|
"#define mkstr(a) # a\n"
|
|
"#define in_between(a) mkstr(a)\n"
|
|
"#define join(c, d) in_between(c hash_hash d)\n"
|
|
"char p[] = join(x, y);\n",
|
|
"char p[] = \"x ## y\";\n");
|
|
|
|
TEST_CASE("func_macros_hard with recursive define");
|
|
CHECK_PP_OUTPUT_EXACT("#define M1(x) M2(x + 1)\n"
|
|
"#define M2(x) M1(x * 2)\n"
|
|
"M1(5)\n",
|
|
"M1(5 + 1 * 2)\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define A B\n"
|
|
"#define B C\n"
|
|
"#define C 1\n"
|
|
"A\n",
|
|
"1\n");
|
|
|
|
TEST_CASE("func_macros_hard with self recursive call");
|
|
CHECK_PP_OUTPUT_EXACT("#define M(x) x\n"
|
|
"M(M(10))\n",
|
|
"10\n");
|
|
CHECK_PP_OUTPUT_EXACT("#define M(x) M(x)\n"
|
|
"#define N(x) x\n"
|
|
"N(M(1))\n",
|
|
"M(1)\n");
|
|
|
|
TEST_CASE("func_macros_hard with define by macro");
|
|
CHECK_PP_OUTPUT_EXACT("#define M1(x) M1(x + 1)\n"
|
|
"#define M2 M1\n"
|
|
"#define M3(x) x\n"
|
|
"M3(M3(M2)(0))\n",
|
|
"M1(0 + 1)\n");
|
|
|
|
TEST_CASE("TODO");
|
|
CHECK_PP_OUTPUT_EXACT("#define str(x) # x\n"
|
|
"str()\n",
|
|
"\"\"\n");
|
|
|
|
TEST_CASE("TODO");
|
|
CHECK_PP_OUTPUT_EXACT("#define x 1\n"
|
|
"#define f(a) f(x * (a))\n"
|
|
"f(0)\n"
|
|
"f(x)",
|
|
"f(1 * (0))\n"
|
|
"f(1 * (1))");
|
|
CHECK_PP_OUTPUT_EXACT("#define x x(0)\n"
|
|
"#define f(a) f(x * (a))\n"
|
|
"f(f(0))\n"
|
|
"f(f(x))\n"
|
|
"f(f(a))\n",
|
|
"f(x(0) * (f(x(0) * (0))))\n"
|
|
"f(x(0) * (f(x(0) * (x(0)))))\n"
|
|
"f(x(0) * (f(x(0) * (a))))\n");
|
|
}
|
|
|
|
static void test_conditional_compilation(void) {
|
|
TEST_CASE("conditional compilation");
|
|
CHECK_PP_OUTPUT_EXACT("#if 1\ntrue\n#endif\n", "true\n");
|
|
CHECK_PP_OUTPUT_EXACT("#if 0\nfalse\n#endif\n", "");
|
|
CHECK_PP_OUTPUT_EXACT("#define FLAG 1\n#if FLAG\ntrue\n#endif\n", "true\n");
|
|
}
|
|
|
|
static void test_error_cases(void) {
|
|
TEST_CASE("macro redefinition");
|
|
// 应检测到警告或错误
|
|
// CHECK_PP_OUTPUT_CONTAIN("#define A 1\n#define A 2\n", "warning");
|
|
|
|
TEST_CASE("undefined macro");
|
|
CHECK_PP_OUTPUT_EXACT("UNDEFINED_MACRO\n", "UNDEFINED_MACRO\n");
|
|
}
|
|
|
|
static void test_edge_cases(void) {
|
|
TEST_CASE("empty macro");
|
|
CHECK_PP_OUTPUT_EXACT("#define EMPTY\nEMPTY\n", "\n");
|
|
|
|
TEST_CASE("macro with only spaces");
|
|
CHECK_PP_OUTPUT_EXACT("#define SPACE \nSPACE\n", "\n");
|
|
|
|
TEST_CASE("deep nesting");
|
|
CHECK_PP_OUTPUT_EXACT("#define A B\n#define B C\n#define C 1\nA\n", "1\n");
|
|
}
|
|
|
|
#define TEST_LIST_CASE(func_name) {#func_name, func_name}
|
|
TEST_LIST = {
|
|
TEST_LIST_CASE(test_define_simple_object_macro),
|
|
TEST_LIST_CASE(test_define_complex_object_macro),
|
|
TEST_LIST_CASE(test_define_object_macro_backspace),
|
|
TEST_LIST_CASE(test_define_function_macro),
|
|
TEST_LIST_CASE(test_define_stringify_operator),
|
|
TEST_LIST_CASE(test_define_concat_operator),
|
|
TEST_LIST_CASE(test_define_nested_macros),
|
|
TEST_LIST_CASE(test_undef_macros),
|
|
TEST_LIST_CASE(hard_test_define_func_macros),
|
|
{NULL, NULL},
|
|
}; |