使用 Clang 解析标头

问题描述 投票:0回答:1

我正在创建一种编程语言,就像 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 的声明,因为用户使用过一次包含防护或编译指示

c++ clang llvm
1个回答
0
投票

你所要求的是不可能的。

首先澄清一下问题:你要先让Clang解析

a.h
并创建一个翻译单元(表示为
ASTUnit
)。 然后你希望 Clang 将
b.h
解析为某种 continuation,其中 你得到一个独特的
ASTUnit
对象,但它仍然排除了 自从
c.h
已经用于
ASTUnit
以来,来自
a.h
的定义 包含它。

这是不可能的,至少有两个原因:

  1. C++ 中的翻译单元 (TU) 无法完成并重新打开。 在 TU 处理结束时,某些活动(例如模板) 实例化发生,并且这些必须最后发生。 最后, Clang 没有 API 可以“添加”到现有的

    ASTUnit
    以及更多内容 源代码(无论您是否想要一个新的
    ASTUnit
    对象)。 这 可能可以使用纯 C 语言的解析器,但这不是 Clang。

  2. 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 比较(包括用作地图密钥)。

© www.soinside.com 2019 - 2024. All rights reserved.