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:
@@ -1,7 +1,9 @@
|
|||||||
|
#include <ctype.h>
|
||||||
#include <lex_parser.h>
|
#include <lex_parser.h>
|
||||||
#include <pp_macro.h>
|
#include <pp_macro.h>
|
||||||
#include <pp_parse.h>
|
#include <pp_parse.h>
|
||||||
#include <pp_token.h>
|
#include <pp_token.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -75,11 +77,23 @@ static cbool parse_macro_replace_list(scc_probe_stream_t *stream,
|
|||||||
Assert(ret == true);
|
Assert(ret == true);
|
||||||
try_to_cut_list(list, &replacement);
|
try_to_cut_list(list, &replacement);
|
||||||
} else if (ch == '#') {
|
} else if (ch == '#') {
|
||||||
// TODO for # ##
|
// 处理 # 和 ## 操作符
|
||||||
scc_probe_stream_consume(stream);
|
scc_probe_stream_consume(stream);
|
||||||
try_to_cut_list(list, &replacement);
|
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)) {
|
} 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);
|
try_to_cut_list(list, &replacement);
|
||||||
} else {
|
} else {
|
||||||
scc_probe_stream_consume(stream);
|
scc_probe_stream_consume(stream);
|
||||||
@@ -284,23 +298,71 @@ FREE:
|
|||||||
scc_cstring_free(&name);
|
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
|
// 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,
|
cbool scc_pp_expand_object_macro(scc_pp_macro_t *macro,
|
||||||
scc_cstring_t *out_buff) {
|
scc_cstring_t *out_buff) {
|
||||||
Assert(macro->type == SCC_PP_MACRO_OBJECT && macro->params.size == 0);
|
Assert(macro->type == SCC_PP_MACRO_OBJECT && macro->params.size == 0);
|
||||||
// FIXME hack cstring to init and clean
|
Assert(scc_cstring_is_empty(out_buff) == true);
|
||||||
scc_cstring_free(out_buff);
|
|
||||||
// 对象宏输出替换文本并进行递归展开
|
// 对象宏输出替换文本并进行递归展开
|
||||||
for (usize i = 0; i < macro->replaces.size; ++i) {
|
scc_pp_expand_string_unsafe(¯o->replaces, out_buff);
|
||||||
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, ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,25 +372,21 @@ cbool scc_pp_expand_function_macro(scc_pp_macro_t *macro,
|
|||||||
scc_cstring_t *out_buff) {
|
scc_cstring_t *out_buff) {
|
||||||
Assert(macro->type == SCC_PP_MACRO_FUNCTION);
|
Assert(macro->type == SCC_PP_MACRO_FUNCTION);
|
||||||
Assert(out_buff != null);
|
Assert(out_buff != null);
|
||||||
// FIXME hack cstring to init and clean
|
Assert(scc_cstring_is_empty(out_buff) == true);
|
||||||
scc_cstring_free(out_buff);
|
|
||||||
for (usize i = 0; i < macro->replaces.size; ++i) {
|
for (usize i = 0; i < macro->replaces.size; ++i) {
|
||||||
// TODO ... __VA_ARGS__
|
// TODO ... __VA_ARGS__
|
||||||
for (usize j = 0; j < macro->params.size; ++j) {
|
for (usize j = 0; j < macro->params.size; ++j) {
|
||||||
if (scc_strcmp(
|
if (scc_strcmp(
|
||||||
scc_cstring_as_cstr(&scc_vec_at(macro->replaces, i)),
|
scc_cstring_as_cstr(&scc_vec_at(macro->replaces, i)),
|
||||||
scc_cstring_as_cstr(&scc_vec_at(macro->params, j))) == 0) {
|
scc_cstring_as_cstr(&scc_vec_at(macro->params, j))) == 0) {
|
||||||
scc_cstring_append(out_buff, &scc_vec_at(*params, j));
|
scc_cstring_free(&scc_vec_at(macro->replaces, i));
|
||||||
goto MATCH;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ static void test_define_concat_operator(void) {
|
|||||||
TEST_CASE("concatenation operator (##)");
|
TEST_CASE("concatenation operator (##)");
|
||||||
CHECK_PP_OUTPUT_EXACT("#define CONCAT(a,b) a##b\nCONCAT(hello,world)\n",
|
CHECK_PP_OUTPUT_EXACT("#define CONCAT(a,b) a##b\nCONCAT(hello,world)\n",
|
||||||
"helloworld\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");
|
"var123\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ class GccCompiler(Compiler):
|
|||||||
flags = {
|
flags = {
|
||||||
BuildMode.TEST: [
|
BuildMode.TEST: [
|
||||||
"-DTEST_MODE",
|
"-DTEST_MODE",
|
||||||
"-O2",
|
"-O0",
|
||||||
"-g",
|
"-g",
|
||||||
"--coverage",
|
"--coverage",
|
||||||
"-fprofile-update=atomic",
|
"-fprofile-update=atomic",
|
||||||
@@ -648,7 +648,7 @@ class ClangCompiler(Compiler):
|
|||||||
flags = {
|
flags = {
|
||||||
BuildMode.TEST: [
|
BuildMode.TEST: [
|
||||||
"-DTEST_MODE",
|
"-DTEST_MODE",
|
||||||
"-O2",
|
"-O0",
|
||||||
"-g",
|
"-g",
|
||||||
"--coverage",
|
"--coverage",
|
||||||
"-fprofile-update=atomic",
|
"-fprofile-update=atomic",
|
||||||
@@ -863,7 +863,7 @@ class PackageBuilder:
|
|||||||
"""打印依赖树"""
|
"""打印依赖树"""
|
||||||
self.context.resolver.print_tree()
|
self.context.resolver.print_tree()
|
||||||
|
|
||||||
def tests(self, filter_str: str = ""):
|
def tests(self, filter_str: str = "", timeout: int = 30):
|
||||||
"""运行测试"""
|
"""运行测试"""
|
||||||
targets = [
|
targets = [
|
||||||
t for t in self.context.get_targets() if t.type == TargetType.TEST_EXEC
|
t for t in self.context.get_targets() if t.type == TargetType.TEST_EXEC
|
||||||
@@ -890,7 +890,7 @@ class PackageBuilder:
|
|||||||
for target in targets:
|
for target in targets:
|
||||||
logger.info("运行测试: %s", target.name)
|
logger.info("运行测试: %s", target.name)
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(target.output, check=True, timeout=30)
|
result = subprocess.run(target.output, check=True, timeout=timeout)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print(f" ✓ 测试 {target.name} 通过")
|
print(f" ✓ 测试 {target.name} 通过")
|
||||||
passed += 1
|
passed += 1
|
||||||
@@ -1066,6 +1066,7 @@ def create_parser():
|
|||||||
# test 命令
|
# test 命令
|
||||||
test_parser = subparsers.add_parser("test", help="运行测试")
|
test_parser = subparsers.add_parser("test", help="运行测试")
|
||||||
add_common_args(test_parser)
|
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.add_argument("--filter", default="", help="过滤测试")
|
||||||
test_parser.set_defaults(mode=BuildMode.TEST)
|
test_parser.set_defaults(mode=BuildMode.TEST)
|
||||||
|
|
||||||
@@ -1135,7 +1136,7 @@ def main():
|
|||||||
builder.run()
|
builder.run()
|
||||||
elif args.command == "test":
|
elif args.command == "test":
|
||||||
builder.build([TargetType.TEST_EXEC])
|
builder.build([TargetType.TEST_EXEC])
|
||||||
builder.tests(getattr(args, "filter", ""))
|
builder.tests(args.filter, args.timeout)
|
||||||
elif args.command == "clean":
|
elif args.command == "clean":
|
||||||
if hasattr(args, "all") and args.all:
|
if hasattr(args, "all") and args.all:
|
||||||
# 清理所有模式
|
# 清理所有模式
|
||||||
|
|||||||
53
tools/wc.py
Normal file
53
tools/wc.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user