feat(parse): implement # and ## operator handling in macro expansion

- Add support for # (stringify) and ## (concatenation) operators in macro replacement lists
- Implement scc_pp_expand_string_unsafe() to process operator tokens during macro expansion
- Add helper macros to identify blank, stringify, and concatenation tokens in replacement lists
- Include missing headers (ctype.h, string.h) for character handling functions
- Update object macro expansion to use new string expansion function instead of simple concatenation
- Improve whitespace handling in macro replacement parsing to prevent interference with operator processing
This commit is contained in:
zzy
2025-12-13 18:29:21 +08:00
parent 07a76d82f4
commit ce8031b21f
4 changed files with 140 additions and 28 deletions

View File

@@ -1,7 +1,9 @@
#include <ctype.h>
#include <lex_parser.h>
#include <pp_macro.h>
#include <pp_parse.h>
#include <pp_token.h>
#include <string.h>
static const struct {
const char *name;
@@ -75,11 +77,23 @@ static cbool parse_macro_replace_list(scc_probe_stream_t *stream,
Assert(ret == true);
try_to_cut_list(list, &replacement);
} else if (ch == '#') {
// TODO for # ##
// 处理 # 和 ## 操作符
scc_probe_stream_consume(stream);
try_to_cut_list(list, &replacement);
scc_cstring_append_ch(&replacement, '#');
if (scc_probe_stream_peek(stream) == '#') {
// ## 连接操作符
scc_probe_stream_consume(stream);
scc_cstring_append_ch(&replacement, '#');
}
// 我需要尽可能防止空白字符干扰解析
scc_lex_parse_skip_whitespace(stream, &pos);
try_to_cut_list(list, &replacement);
} else if (scc_lex_parse_is_whitespace(ch)) {
scc_probe_stream_consume(stream);
try_to_cut_list(list, &replacement);
scc_lex_parse_skip_whitespace(stream, &pos);
scc_cstring_append_ch(&replacement, ' ');
try_to_cut_list(list, &replacement);
} else {
scc_probe_stream_consume(stream);
@@ -284,23 +298,71 @@ FREE:
scc_cstring_free(&name);
}
static inline void scc_generate_cstr(scc_cstring_t *buff) {
scc_cstring_t out_buff = scc_cstring_new();
scc_cstring_append_ch(&out_buff, '\"');
// TODO it is too simple
scc_cstring_append(&out_buff, buff);
scc_cstring_append_ch(&out_buff, '\"');
// FIXME 可能有着更好的解决方案
scc_cstring_clear(buff);
scc_cstring_append(buff, &out_buff);
scc_cstring_free(&out_buff);
}
#define SCC_PP_IS_LIST_BLANK(i) \
((i) < list->size && scc_vec_at(*list, (i)).data[0] == ' ' && \
scc_vec_at(*list, (i)).data[1] == '\0')
#define SCC_PP_IS_LIST_TO_STRING(i) \
((i) < list->size && scc_vec_at(*list, (i)).data[0] == '#' && \
scc_vec_at(*list, (i)).data[1] == '\0')
#define SCC_PP_IS_LIST_CONNECT(i) \
((i) < list->size && scc_vec_at(*list, (i)).data[0] == '#' && \
scc_vec_at(*list, (i)).data[1] == '#' && \
scc_vec_at(*list, (i)).data[2] == '\0')
#define SCC_PP_USE_CONNECT(font, rear) \
if (rear < list->size) { \
scc_cstring_append(out_buff, &scc_vec_at(*list, font)); \
scc_cstring_append(out_buff, &scc_vec_at(*list, rear)); \
} else { \
scc_cstring_append(out_buff, &scc_vec_at(*list, font)); \
}
// for # ## to generator string
cbool scc_pp_expand_string() { return false; }
static inline cbool scc_pp_expand_string_unsafe(scc_pp_macro_list_t *list,
scc_cstring_t *out_buff) {
for (usize i = 0; i < list->size; ++i) {
if (SCC_PP_IS_LIST_BLANK(i + 1)) {
if (SCC_PP_IS_LIST_CONNECT(i + 2)) {
SCC_PP_USE_CONNECT(i, i + 3);
i += 3;
continue;
}
} else if (SCC_PP_IS_LIST_CONNECT(i + 1)) {
SCC_PP_USE_CONNECT(i, i + 2);
i += 2;
continue;
} else if (SCC_PP_IS_LIST_TO_STRING(i)) {
i += 1;
if (i < list->size) {
scc_generate_cstr(&scc_vec_at(*list, i));
} else {
LOG_WARN("# need a valid literator");
break;
}
}
scc_cstring_append(out_buff, &scc_vec_at(*list, i));
}
return true;
}
// 展开对象宏
cbool scc_pp_expand_object_macro(scc_pp_macro_t *macro,
scc_cstring_t *out_buff) {
Assert(macro->type == SCC_PP_MACRO_OBJECT && macro->params.size == 0);
// FIXME hack cstring to init and clean
scc_cstring_free(out_buff);
Assert(scc_cstring_is_empty(out_buff) == true);
// 对象宏输出替换文本并进行递归展开
for (usize i = 0; i < macro->replaces.size; ++i) {
scc_cstring_append(out_buff, &scc_vec_at(macro->replaces, i));
// YOU MUST USE + 1 to cmp because we use unsigned integer
if (i + 1 < macro->replaces.size) {
scc_cstring_append_ch(out_buff, ' ');
}
}
scc_pp_expand_string_unsafe(&macro->replaces, out_buff);
return true;
}
@@ -310,25 +372,21 @@ cbool scc_pp_expand_function_macro(scc_pp_macro_t *macro,
scc_cstring_t *out_buff) {
Assert(macro->type == SCC_PP_MACRO_FUNCTION);
Assert(out_buff != null);
// FIXME hack cstring to init and clean
scc_cstring_free(out_buff);
Assert(scc_cstring_is_empty(out_buff) == true);
for (usize i = 0; i < macro->replaces.size; ++i) {
// TODO ... __VA_ARGS__
for (usize j = 0; j < macro->params.size; ++j) {
if (scc_strcmp(
scc_cstring_as_cstr(&scc_vec_at(macro->replaces, i)),
scc_cstring_as_cstr(&scc_vec_at(macro->params, j))) == 0) {
scc_cstring_append(out_buff, &scc_vec_at(*params, j));
goto MATCH;
scc_cstring_free(&scc_vec_at(macro->replaces, i));
scc_cstring_append(&scc_vec_at(macro->replaces, i),
&scc_vec_at(*params, j));
continue;
}
}
scc_cstring_append(out_buff, &scc_vec_at(macro->replaces, i));
MATCH:
// YOU MUST USE + 1 to cmp because we use unsigned
if (i + 1 < macro->replaces.size) {
scc_cstring_append_ch(out_buff, ' ');
}
}
scc_pp_expand_string_unsafe(&macro->replaces, out_buff);
return true;
}

View File

@@ -90,7 +90,7 @@ 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",
CHECK_PP_OUTPUT_EXACT("#define JOIN(pre,suf) pre ## suf\nJOIN(var, 123)\n",
"var123\n");
}