autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
149 lines (148 loc) • 5.24 kB
JavaScript
/**
* @module CMakeParser
* @description CMake 轻量解析器 — 从 CMakeLists.txt 提取项目拓扑
*
* 支持解析:
* - project() — 项目名和版本
* - add_subdirectory() — 子目录发现
* - add_library() / add_executable() — 目标声明
* - target_link_libraries() — 依赖关系
*
* 设计策略: 仅解析顶层调用,不跟踪控制流 (if/else/macro)
*/
// ── 正则模式 ────────────────────────────────────────
const PROJECT_RE = /project\(\s*(\w+)(?:\s+VERSION\s+([\d.]+))?/;
const ADD_SUBDIRECTORY_RE = /add_subdirectory\(\s*(\S+)\s*\)/g;
// add_library(name STATIC src1.cpp src2.cpp) — 允许多行
const ADD_LIBRARY_RE = /add_library\(\s*(\w+)\s+(STATIC|SHARED|INTERFACE|MODULE|OBJECT)?\s*([\s\S]*?)\)/g;
const ADD_EXECUTABLE_RE = /add_executable\(\s*(\w+)\s*([\s\S]*?)\)/g;
// target_link_libraries(target PUBLIC|PRIVATE|INTERFACE dep1 dep2)
const TARGET_LINK_RE = /target_link_libraries\(\s*(\w+)\s+([\s\S]*?)\)/g;
// ── 公开 API ────────────────────────────────────────
/**
* 解析 CMakeLists.txt 内容
*/
export function parseCMakeProject(content) {
const result = {
projectName: '',
subdirectories: [],
targets: [],
};
// 移除注释
const cleaned = removeComments(content);
// 解析 project()
const projMatch = cleaned.match(PROJECT_RE);
if (projMatch) {
result.projectName = projMatch[1];
result.version = projMatch[2];
}
// 解析 add_subdirectory()
const subdirRe = new RegExp(ADD_SUBDIRECTORY_RE.source, 'g');
let m;
while ((m = subdirRe.exec(cleaned)) !== null) {
result.subdirectories.push(m[1]);
}
// 解析 add_library()
const targetMap = new Map();
const libRe = new RegExp(ADD_LIBRARY_RE.source, 'gs');
while ((m = libRe.exec(cleaned)) !== null) {
const name = m[1];
const typeStr = (m[2] ?? 'STATIC').toUpperCase();
const sourcesStr = m[3] ?? '';
const typeMap = {
STATIC: 'static-library',
SHARED: 'shared-library',
INTERFACE: 'interface-library',
MODULE: 'shared-library',
OBJECT: 'static-library',
};
const target = {
name,
type: typeMap[typeStr] ?? 'static-library',
sources: extractSourceFiles(sourcesStr),
linkDependencies: [],
};
targetMap.set(name, target);
}
// 解析 add_executable()
const exeRe = new RegExp(ADD_EXECUTABLE_RE.source, 'gs');
while ((m = exeRe.exec(cleaned)) !== null) {
const name = m[1];
const sourcesStr = m[2] ?? '';
const target = {
name,
type: 'executable',
sources: extractSourceFiles(sourcesStr),
linkDependencies: [],
};
targetMap.set(name, target);
}
// 解析 target_link_libraries()
const linkRe = new RegExp(TARGET_LINK_RE.source, 'gs');
while ((m = linkRe.exec(cleaned)) !== null) {
const targetName = m[1];
const depsStr = m[2];
const target = targetMap.get(targetName);
if (!target) {
continue;
}
target.linkDependencies = parseLinkDependencies(depsStr);
}
result.targets = [...targetMap.values()];
return result;
}
// ── 内部函数 ────────────────────────────────────────
function removeComments(content) {
return content
.split('\n')
.map((line) => {
const commentIdx = line.indexOf('#');
if (commentIdx === -1) {
return line;
}
// 简单处理:不检查字符串内的 #
return line.substring(0, commentIdx);
})
.join('\n');
}
function extractSourceFiles(str) {
const sources = [];
// 提取非关键字的 token
const tokens = str.trim().split(/\s+/);
for (const token of tokens) {
const clean = token.trim();
if (clean === '' ||
clean === 'STATIC' ||
clean === 'SHARED' ||
clean === 'INTERFACE' ||
clean === 'MODULE' ||
clean === 'OBJECT' ||
clean === 'PUBLIC' ||
clean === 'PRIVATE' ||
clean === 'IMPORTED' ||
clean === 'ALIAS' ||
clean === 'EXCLUDE_FROM_ALL' ||
clean.startsWith('$')) {
continue;
}
sources.push(clean);
}
return sources;
}
function parseLinkDependencies(str) {
const deps = [];
const tokens = str.trim().split(/\s+/);
let currentScope = 'PUBLIC';
for (const token of tokens) {
const clean = token.trim();
if (clean === 'PUBLIC' || clean === 'PRIVATE' || clean === 'INTERFACE') {
currentScope = clean;
continue;
}
if (clean === '' || clean.startsWith('$') || clean.startsWith('#')) {
continue;
}
deps.push({ target: clean, scope: currentScope });
}
return deps;
}