clangLex
负责词法分析和预处理,处理宏、令牌和 pragma 构造
本文会通过实际的例子对 clangLex
的 词法分析 和 预处理指令 相关流程进行分享
下面是本文涉及到一些重要类型(有删减):
Token
包含了 词法分析 后的token
,它包含诸如 在源码中的位置、类型 等各类信息
Lexer
负责将 文本 转为 Token
Preprocessor
是负责与 Lexer
进行预处理和配合Lexer
进行词法分析
IdentifierTable
维护 string
与 IdentifierInfo
的映射关系,类似于字典
IdentifierInfo
记录了 Token
的各种重要信息(是否属于编程语言的关键字,是否是函数名、变量名)
Token
与IdentifierInfo
的区别是:IdentifierInfo
是经过精简并进行了内存占用优化
DiagnosticsEngine
提供 issues
报告的功能
本节以下面的代码的函数返回值 void
为例介绍 词法分析 的流程:
void testC() {
}
本段结束后,会有一个流程图,方便对本节内容理解和记忆
Preprocessor::Initialize
函数在被初始化调用时,会通过 IdentifierTable
的 AddKeywords
方法先初始化 编程语言关键字
image编程语言关键字 是指对编程语言有特殊含义的单词。比如,void
代表 空
,是所有 c
家族语 言的 关键字TokenKinds.def
维护了不同编程语言的 关键字
AddKeyword
最终会更新 IdentifierTable
的内容
image
Parser
会通过 Preprocessor::Lex
调用 Lexer::Lex
函数
image
Lexer::Lex
函数会先通过 Result.startToken
函数,准备接收一个新的 Token
,并做一些预备工作;然后调用 Lexer::LexTokenInternal
函数
image
Lexer::LexTokenInternal
函数会依次解析每个字符,当检测到以字符 v
开头后,会调用 Lexer::LexIdentifier
函数
image
Lexer::LexIdentifier
函数会先通过 while 循环
的方式,将 CurPtr
指向 testC
image随后,会调用 FormTokenWithChars
函数更新 Token
,Token
类型是 tok::raw_identifier
image
FormTokenWithChars
函数会更新 Token
的长度、位置、类型 信息
image
LexIdentifier
函数会调用 setRawIdentifierData
更新 Token
的 PtrData
属性
更新完成后,就可以通过 Result.getRawIdentifier().str()
获取到 Token
对应的 原始字符串
当 Token
是 tok::raw_identifier
类型时, PtrData
就会指向 原始字符串
image
Preprocessor
的 LookUpIdentifierInfo
函数更新Token
的信息
image
LookUpIdentifierInfo
函数会调用 getIdentifierInfo
查找标识信息(参数就是 void
)
image
getIdentifierInfo
会直接转发给 IdentifierTable::get
函数进行下一步处理
image
IdentifierTable::get
会根据名字查找对应 IdentifierInfo
因为 void
是 编程语言关键字,所以,会返回通过 Identifiers.AddKeywords(LangOpts)
函数提交 语言关键字 void查找失败,会创建一个新的 IdentifierInfo
示例
image
LookUpIdentifierInfo
函数后,会根据 IdentifierInfo
的信息更新 Token
,并返回 II
Identifier.setIdentifierInfo(II)
更新 PtrData
Identifier.setKind(II->getTokenID());
更新 Kind
image
Lexer::LexIdentifier
函数就完成了将void
解析为 Token
的使命
image
附: 词法分析
的流程图:
本节以 #pragma GCC poison
为例,介绍 预处理指令 的过程
#pragma clang poison
是一个预处理指令,可以实现禁止源码中出现某些标识符。 读者可以尝试在任意源码添加下面的示例片段,看看能否编程成功 更多相关知识,可以点击这个链接
#pragma clang poison testC testB
void testC() {
}
void testB() {
testC();
}
预处理器 初始化时会保存传入的Diags
,并会创建一个匿名的 PragmaNamespace
,并调用 RegisterBuiltinPragmas
函数
image
RegisterBuiltinPragmas
函数会生成 PragmaPoisonHandler
的实例,并通过函数 AddPragmaHandler
进行注册
image
函数 AddPragmaHandler
会根据 Namespace 参数决定是否由Preprocessor
直接持有
本例中
#pragma clang poison
存在一个命名空间:clang
,所以会 间接持有
image
为了方便理解,我们可以看看下面两种持有方式:
#pragma once
#pragma marker
是由 Preprocessor
直接持有 > `PragmaNamespace_anonymous` 是未命名的 `PragmaNamespace` 实例
> `PragmaNamespace` 是名字为 `clang` 的实例
Lexer
的 LexTokenInternal
函数进行 词法分析 时,会检测到 字符 #
,此时,程序会转到会转到 LexTokenInternal
函数的 HandleDirective:
处理
image
HandleDirective:
会调用 PP
(预处理器) 的 HandleDirective
函数进行处理
image
HandleDirective
函数通过 LexUnexpandedToken
函数解析下一个 Token
(pragma
)
image随后,分发给 HandlePragmaDirective
函数处理
image
HandlePragmaDirective
函数会调用 PragmaHandlers
的 HandlePragma
函数进行下一步的处理
image
HandlePragma
函数会先解析下一个 Token
,并根据 Token
的名字 poison
找到 PragmaPoisonHandler
进行下一步的处理
image
PragmaPoisonHandler::HandlePragma
的逻辑很简单,会直接调用 Preprocessor
的 HandlePragmaPoison
函数进行下一步处理
image
HandlePragmaPoison
会不停地通过 LexUnexpandedToken(Tok)
读取 Token
,并调用 IdentifierInfo
的 setIsPoisoned()
方法
xx
setIsPoisoned()
函数会将 IdentifierInfo
的 IsPoisoned
和 NeedsHandleIdentifier
置为 true
image
Lexer::LexIdentifier
函数会先检测 IdentifierInfo
的 NeedsHandleIdentifier
是否等于 true,并回调 HandleIdentifier
进行下一步处理
image
HandleIdentifier
函数会在检测到 CurPPLexer
并且 IsPoisoned
等于 true 时,调用 HandlePoisonedIdentifier
函数进行下一步处理
image
HandlePoisonedIdentifier
函数会调用一个 Diag
函数,参数是 Token
和 diag::err_pp_used_poisoned_id
image
Diag
函数先获取 Tok
的位置,并通过初始化阶段传入的 Diags
抛出异常
image
diag::err_pp_used_poisoned_id
对应的含义可以从clang/include/clang/Basic/DiagnosticLexKinds.td
获取
附:预处理 流程图
本文通过实际的例子对 clangLex
的 词法分析 和 预处理指令 流程进行了总结和分享,并提供了对应的 流程图
点个在看少个 bug ?