首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >clang 源码导读(8):词法分析和预处理指令

clang 源码导读(8):词法分析和预处理指令

作者头像
酷酷的哀殿
发布于 2021-04-09 14:40:33
发布于 2021-04-09 14:40:33
4.2K1
举报
文章被收录于专栏:酷酷的哀殿酷酷的哀殿

Lex

clangLex 负责词法分析和预处理,处理宏、令牌和 pragma 构造

本文会通过实际的例子对 clangLex 的 词法分析 和 预处理指令 相关流程进行分享

下面是本文涉及到一些重要类型(有删减):

Token 包含了 词法分析 后的token,它包含诸如 在源码中的位置、类型 等各类信息

Lexer 负责将 文本 转为 Token

Preprocessor 是负责与 Lexer 进行预处理和配合Lexer 进行词法分析

IdentifierTable 维护 string 与 IdentifierInfo 的映射关系,类似于字典

IdentifierInfo 记录了 Token 的各种重要信息(是否属于编程语言的关键字,是否是函数名、变量名)

Token 与 IdentifierInfo 的区别是:IdentifierInfo 是经过精简并进行了内存占用优化

DiagnosticsEngine 提供 issues 报告的功能

词法分析

本节以下面的代码的函数返回值 void 为例介绍 词法分析 的流程:

代码语言:javascript
复制
void testC() {
}

本段结束后,会有一个流程图,方便对本节内容理解和记忆

  1. Preprocessor::Initialize 函数在被初始化调用时,会通过 IdentifierTable 的 AddKeywords 方法先初始化 编程语言关键字

image编程语言关键字 是指对编程语言有特殊含义的单词。比如,void 代表 空,是所有 c家族语 言的 关键字TokenKinds.def 维护了不同编程语言的 关键字

  1. AddKeyword 最终会更新 IdentifierTable 的内容

image

  1. Parser 会通过 Preprocessor::Lex 调用 Lexer::Lex 函数

image

  1. Lexer::Lex 函数会先通过 Result.startToken 函数,准备接收一个新的 Token,并做一些预备工作;然后调用 Lexer::LexTokenInternal 函数

image

  1. Lexer::LexTokenInternal 函数会依次解析每个字符,当检测到以字符 v开头后,会调用 Lexer::LexIdentifier 函数

image

  1. Lexer::LexIdentifier 函数会先通过 while 循环 的方式,将 CurPtr 指向 testC

image随后,会调用 FormTokenWithChars 函数更新 Token,Token 类型是 tok::raw_identifier

image

  1. FormTokenWithChars 函数会更新 Token 的长度、位置、类型 信息

image

  1. 随后,LexIdentifier 函数会调用 setRawIdentifierData 更新 Token 的 PtrData 属性 更新完成后,就可以通过 Result.getRawIdentifier().str() 获取到 Token 对应的 原始字符串 当 Token 是 tok::raw_identifier 类型时, PtrData 就会指向 原始字符串

image

  1. 再调用 Preprocessor 的 LookUpIdentifierInfo 函数更新Token的信息

image

  1. LookUpIdentifierInfo 函数会调用 getIdentifierInfo 查找标识信息(参数就是 void )

image

  1. getIdentifierInfo 会直接转发给 IdentifierTable::get 函数进行下一步处理

image

  1. IdentifierTable::get 会根据名字查找对应 IdentifierInfo 因为 void 是 编程语言关键字,所以,会返回通过 Identifiers.AddKeywords(LangOpts) 函数提交 语言关键字 void查找失败,会创建一个新的 IdentifierInfo 示例

image

  1. 返回 LookUpIdentifierInfo 函数后,会根据 IdentifierInfo 的信息更新 Token ,并返回 II Identifier.setIdentifierInfo(II) 更新 PtrData Identifier.setKind(II->getTokenID()); 更新 Kind

image

  1. 至此,Lexer::LexIdentifier 函数就完成了将void 解析为 Token 的使命

image

附: 词法分析 的流程图:

预处理指令

本节以 #pragma GCC poison 为例,介绍 预处理指令 的过程

#pragma clang poison 是一个预处理指令,可以实现禁止源码中出现某些标识符。 读者可以尝试在任意源码添加下面的示例片段,看看能否编程成功 更多相关知识,可以点击这个链接

代码语言:javascript
复制
#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 直接持有
代码语言:javascript
复制
  > `PragmaNamespace_anonymous` 是未命名的 `PragmaNamespace` 实例
  > `PragmaNamespace` 是名字为 `clang` 的实例

词法分析阶段

  1. 当 Lexer 的 LexTokenInternal 函数进行 词法分析 时,会检测到 字符 # ,此时,程序会转到会转到 LexTokenInternal 函数的 HandleDirective: 处理

image

  1. HandleDirective: 会调用 PP(预处理器) 的 HandleDirective 函数进行处理

image

  1. HandleDirective 函数通过 LexUnexpandedToken 函数解析下一个 Token(pragma)

image随后,分发给 HandlePragmaDirective 函数处理

image

  1. HandlePragmaDirective 函数会调用 PragmaHandlers 的 HandlePragma 函数进行下一步的处理

image

  1. HandlePragma 函数会先解析下一个 Token,并根据 Token 的名字 poison 找到 PragmaPoisonHandler 进行下一步的处理

image

  1. PragmaPoisonHandler::HandlePragma 的逻辑很简单,会直接调用 Preprocessor 的 HandlePragmaPoison 函数进行下一步处理

image

  1. HandlePragmaPoison 会不停地通过 LexUnexpandedToken(Tok) 读取 Token,并调用 IdentifierInfo 的 setIsPoisoned() 方法

xx

  1. setIsPoisoned() 函数会将 IdentifierInfo 的 IsPoisoned 和 NeedsHandleIdentifier 置为 true

image

检测&保存

  1. 后续进行 词法分析 时, Lexer::LexIdentifier 函数会先检测 IdentifierInfo 的 NeedsHandleIdentifier 是否等于 true,并回调 HandleIdentifier 进行下一步处理

image

  1. HandleIdentifier 函数会在检测到 CurPPLexer 并且 IsPoisoned 等于 true 时,调用 HandlePoisonedIdentifier 函数进行下一步处理

image

  1. HandlePoisonedIdentifier 函数会调用一个 Diag 函数,参数是 Token 和 diag::err_pp_used_poisoned_id

image

  1. Diag 函数先获取 Tok 的位置,并通过初始化阶段传入的 Diags 抛出异常

image

diag::err_pp_used_poisoned_id 对应的含义可以从clang/include/clang/Basic/DiagnosticLexKinds.td 获取

附:预处理 流程图

总结

本文通过实际的例子对 clangLex 的 词法分析 和 预处理指令 流程进行了总结和分享,并提供了对应的 流程图

点个在看少个 bug ?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • Lex
    • 词法分析
    • 预处理指令
    • 初始化阶段
    • 词法分析阶段
    • 检测&保存
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档