From ce8031b21fe3ce139ed02f40a388921f24969555 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Sat, 13 Dec 2025 18:29:21 +0800 Subject: [PATCH] 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 --- libs/pprocessor/src/parse.c | 102 +++++++++++++++++++++++------- libs/pprocessor/tests/test_unit.c | 2 +- tools/cbuild/cbuild.py | 11 ++-- tools/wc.py | 53 ++++++++++++++++ 4 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 tools/wc.py diff --git a/libs/pprocessor/src/parse.c b/libs/pprocessor/src/parse.c index 3a77d73..1228a0c 100644 --- a/libs/pprocessor/src/parse.c +++ b/libs/pprocessor/src/parse.c @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include 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(¯o->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(¯o->replaces, out_buff); return true; } diff --git a/libs/pprocessor/tests/test_unit.c b/libs/pprocessor/tests/test_unit.c index b46681d..db723cf 100644 --- a/libs/pprocessor/tests/test_unit.c +++ b/libs/pprocessor/tests/test_unit.c @@ -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"); } diff --git a/tools/cbuild/cbuild.py b/tools/cbuild/cbuild.py index 4f8d84b..77414a8 100644 --- a/tools/cbuild/cbuild.py +++ b/tools/cbuild/cbuild.py @@ -608,7 +608,7 @@ class GccCompiler(Compiler): flags = { BuildMode.TEST: [ "-DTEST_MODE", - "-O2", + "-O0", "-g", "--coverage", "-fprofile-update=atomic", @@ -648,7 +648,7 @@ class ClangCompiler(Compiler): flags = { BuildMode.TEST: [ "-DTEST_MODE", - "-O2", + "-O0", "-g", "--coverage", "-fprofile-update=atomic", @@ -863,7 +863,7 @@ class PackageBuilder: """打印依赖树""" self.context.resolver.print_tree() - def tests(self, filter_str: str = ""): + def tests(self, filter_str: str = "", timeout: int = 30): """运行测试""" targets = [ t for t in self.context.get_targets() if t.type == TargetType.TEST_EXEC @@ -890,7 +890,7 @@ class PackageBuilder: for target in targets: logger.info("运行测试: %s", target.name) try: - result = subprocess.run(target.output, check=True, timeout=30) + result = subprocess.run(target.output, check=True, timeout=timeout) if result.returncode == 0: print(f" ✓ 测试 {target.name} 通过") passed += 1 @@ -1066,6 +1066,7 @@ def create_parser(): # test 命令 test_parser = subparsers.add_parser("test", help="运行测试") add_common_args(test_parser) + test_parser.add_argument("--timeout", "-t", type=int, default=3, help="测试时间") test_parser.add_argument("--filter", default="", help="过滤测试") test_parser.set_defaults(mode=BuildMode.TEST) @@ -1135,7 +1136,7 @@ def main(): builder.run() elif args.command == "test": builder.build([TargetType.TEST_EXEC]) - builder.tests(getattr(args, "filter", "")) + builder.tests(args.filter, args.timeout) elif args.command == "clean": if hasattr(args, "all") and args.all: # 清理所有模式 diff --git a/tools/wc.py b/tools/wc.py new file mode 100644 index 0000000..be37635 --- /dev/null +++ b/tools/wc.py @@ -0,0 +1,53 @@ +"""统计目录下C/C++文件的行数(write by AI)""" + +import os + + +def count_lines(file_path): + """统计单个文件的代码行数""" + try: + with open(file_path, "rb") as f: # 二进制模式读取避免编码问题 + return sum(1 for _ in f) + except UnicodeDecodeError: + print(f"警告:无法解码文件 {file_path}(可能不是文本文件)") + return 0 + except Exception as e: + print(f"读取 {file_path} 出错: {str(e)}") + return 0 + + +def scan_files(directory, exclude_dirs=None): + """扫描目录获取所有C/C++文件""" + if exclude_dirs is None: + exclude_dirs = [".git", "venv", "__pycache__", ".old"] # 默认排除的目录 + + c_files = [] + for root, dirs, files in os.walk(directory): + # 跳过排除目录 + dirs[:] = [d for d in dirs if d not in exclude_dirs] + + for file in files: + if file.endswith((".c", ".h")): + full_path = os.path.join(root, file) + c_files.append(full_path) + return c_files + + +def main(): + """main function""" + target_dir = input("请输入要扫描的目录路径(留空为当前目录): ") or "." + + files = scan_files(target_dir) + total_lines = 0 + + print("\n统计结果:") + for idx, file in enumerate(files, 1): + lines = count_lines(file) + total_lines += lines + print(f"{idx:4d}. {file} ({lines} 行)") + + print(f"\n总计: {len(files)} 个C/C++文件,共 {total_lines} 行代码") + + +if __name__ == "__main__": + main()