我正在创建一种编程语言,就像 Zig 一样,我希望允许在我的编程语言中导入/包含 c 标头,我要求 Clang 使用此函数将标头解析为 ASTUnit
clang::ASTUnit *ClangLoadFromCommandLine(
const char **args_begin,
const char **args_end,
struct ErrorMsg **errors_ptr,
unsigned long *errors_len,
const char *resources_path,
clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags
) {
std::shared_ptr<clang::PCHContainerOperations> pch_container_ops = std::make_shared<clang::PCHContainerOperations>();
bool only_local_decls = true;
bool user_files_are_volatile = true;
bool allow_pch_with_compiler_errors = false;
bool single_file_parse = false;
bool for_serialization = false;
bool retain_excluded_conditional_blocks = false;
bool store_preambles_in_memory = false;
llvm::StringRef preamble_storage_path = llvm::StringRef();
clang::ArrayRef<clang::ASTUnit::RemappedFile> remapped_files = std::nullopt;
std::unique_ptr<clang::ASTUnit> err_unit;
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = nullptr;
std::optional<llvm::StringRef> ModuleFormat = std::nullopt;
std::unique_ptr<clang::ASTUnit> ast_unit_unique_ptr = clang::ASTUnit::LoadFromCommandLine(
args_begin, args_end,
pch_container_ops,
diags,
resources_path,
store_preambles_in_memory,
preamble_storage_path,
only_local_decls,
clang::CaptureDiagsKind::All,
remapped_files,
true, // remapped files keep original name
0, // precompiled preable after n parses
clang::TU_Complete,
false, // cache code completion results
false, // include brief comments in code completion
allow_pch_with_compiler_errors,
clang::SkipFunctionBodiesScope::None,
single_file_parse,
user_files_are_volatile,
for_serialization,
retain_excluded_conditional_blocks,
ModuleFormat,
&err_unit,
VFS);
clang::ASTUnit *ast_unit = ast_unit_unique_ptr.release();
*errors_len = 0;
// Early failures in LoadFromCommandLine may return with ErrUnit unset.
if (!ast_unit && !err_unit) {
return nullptr;
}
if (diags->hasErrorOccurred()) {
// Take ownership of the err_unit ASTUnit object so that it won't be
// free'd when we return, invalidating the error message pointers
clang::ASTUnit *unit = ast_unit ? ast_unit : err_unit.release();
take_clang_diagnostics(errors_ptr, errors_len, unit->stored_diag_begin(), unit->stored_diag_end());
return nullptr;
}
return ast_unit;
}
但是当用户包含两个具有共同第三个标头的标头时,就会出现问题。例如用户的代码是
import "a.h"
import "b.h"
但是 a.h 和 b.h 都包含 c.h。现在,由于我正在使用该函数,ASTUnit 在 a.h 单元和 b.h 单元中都包含 c.h,我翻译了两次,导致 c.h 被翻译了两次
为此我尝试做以下事情
PPCallbacks 中还有 InclusionDirective 函数,我尝试实现它,但它无法提供避免 #include 或正确跟踪的方法
我只想这个,用户写道
import "a.h"
import "b.h"
我问 clang 使用这样的函数
ASTUnit* heyClangPleaseGiveAstUnitFor(clang_state, a.h)
ASTUnit* heyClangPleaseGiveAstUnitFor(clang_state, b.h)
a.h 的 ast 单元包含用户包含的 c.h,但 b.h 的 ASTunit 不应包含 c.h 的声明,因为用户使用过一次包含防护或编译指示
你所要求的是不可能的。
首先澄清一下问题:你要先让Clang解析
a.h
并创建一个翻译单元(表示为
ASTUnit
)。
然后你希望 Clang 将 b.h
解析为某种 continuation,其中
你得到一个独特的 ASTUnit
对象,但它仍然排除了
自从 c.h
已经用于 ASTUnit
以来,来自 a.h
的定义
包含它。
这是不可能的,至少有两个原因:
C++ 中的翻译单元 (TU) 无法完成并重新打开。 在 TU 处理结束时,某些活动(例如模板) 实例化发生,并且这些必须最后发生。 最后, Clang 没有 API 可以“添加”到现有的
ASTUnit
以及更多内容
源代码(无论您是否想要一个新的 ASTUnit
对象)。 这
可能可以使用纯 C 语言的解析器,但这不是 Clang。
Clang TU 表示要求(首先要良好) 近似值)声明所指的所有内容也是 在同一个 TU 中声明。 当
b.h
指代 c.h
中的某物时,
它的 TU 无法指向不同的声明
TU 例如 a.h
。
您建议拦截
b.h
的行为,包括 c.h
使用
PPCallbacks
,
但即使你可以使用该机制来阻止处理 c.h
(我认为通过合理的努力这是不可能的),你只会
成功地导致 Clang 因 b.h
中的任何声明而窒息
需要来自 c.h
的声明,因为后者不可用,并且
(再次)Clang 无法“借用”另一个声明
TU.
您可以做的是,在使用 Clang 解析之后,在您自己的代码中, 通过识别排除
c.h
中实体的重复处理
在 AST 遍历过程中使用它们的源位置。
例如,将
a.h
和 b.h
解析为单独的 TU(因为您已经
做),然后构建从源位置到翻译实体的地图。
然后,当你第二次看到c.h
中的声明时,你就知道了
忽略他们。 但是,请注意,Clang
SourceLocation
object 不能直接跨 TU 进行比较,因此必须将其翻转
到文件/行/列表示中(使用
SourceManager
API)用于跨 TU 比较(包括用作地图密钥)。