> For the complete documentation index, see [llms.txt](https://code-visualization.shawnxie.top/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://code-visualization.shawnxie.top/di-er-pian-yuan-ma-jie-gou-hua-yuan-li/compiler-view.md).

# 编译器视角下的代码结构

如果把代码可视化理解成“把代码画成图”，很容易低估前置工作的难度。工具不能直接理解一段源码文本。它必须先知道哪些字符组成关键字，哪些字符组成变量名，哪些代码是一个方法，哪些调用属于某个对象，哪些类型之间存在继承或实现关系。

编译器提供了一条成熟路径：把文本逐步转换成结构化表示。代码可视化并不需要完整实现一个编译器，但它大量复用编译器前端和程序分析中的思想。本章先建立这个视角，后面再分别展开 AST、符号表、IR、CFG 和 DFG。

## 源码不是稳定的分析对象

人读代码时可以凭经验跳过空格、注释和格式差异，也可以根据上下文推断变量含义。但工具如果只把源码当普通字符串处理，很快会遇到问题。

例如下面两行都出现了 `UserService`：

```java
private UserService userService;
String text = "UserService";
```

如果只做字符串搜索，它们看起来都命中了 `UserService`。但从代码理解角度看，第一处是类型引用，第二处只是字符串字面量。再比如一个方法名可能出现在注释、文档、测试名称、日志文本和真实调用表达式中，只有后者才应该进入调用图。

所以，代码可视化的第一步不是画图，而是把源码转换成工具能稳定识别的结构化数据。

## 编译器的基本流程

一个典型编译器会经历多个阶段：

```
源码文本
  -> 词法分析
  -> 语法分析
  -> 语义分析
  -> 中间表示
  -> 优化
  -> 目标代码
```

不同语言和工具链的实现细节不同，但这条链路反映了一个共同思路：越往后，表示越脱离原始文本，也越接近程序的语义和行为。

对代码可视化最有用的是前四个阶段：

* 词法分析：识别 Token，例如关键字、标识符、字面量、操作符。
* 语法分析：把 Token 组织成语法树或抽象语法树。
* 语义分析：建立符号、作用域、类型、引用和重载解析。
* 中间表示：把程序转换成更适合控制流、数据流和优化分析的形式。

目标代码生成和机器级优化对本书不是重点。我们关心的是如何把源码变成可分析、可查询、可视化的事实。

## 词法分析：把字符变成 Token

词法分析解决的是“这段文本由哪些基本语法单元组成”。例如下面的 Java 代码：

```java
int total = price * count;
```

词法分析后可以得到类似这样的 Token 序列：

```
KEYWORD(int)
IDENTIFIER(total)
OPERATOR(=)
IDENTIFIER(price)
OPERATOR(*)
IDENTIFIER(count)
SEPARATOR(;)
```

Token 本身还不理解程序含义，但它已经比原始字符串更稳定。工具可以区分关键字、变量名和操作符，也可以忽略空格、换行和注释等格式差异。

在代码可视化中，Token 层很少直接成为最终图形，但它是后续语法树的基础。正则表达式可视化、语法高亮、简单代码扫描也会使用类似思想。

## 语法分析：把 Token 变成树

语法分析根据语言规则，把 Token 组织成结构。对于上面的表达式，语法分析会识别出变量声明、赋值表达式、乘法表达式等节点。

抽象语法树（AST）保留的是对代码理解有用的结构。例如一个方法声明节点会包含方法名、参数、返回类型和方法体；一个调用表达式节点会包含调用目标、参数列表和源码位置。

AST 让工具可以回答很多结构问题：

* 一个文件里有哪些类和方法。
* 一个方法体里有哪些语句。
* 哪些地方出现了方法调用表达式。
* 哪些字段被声明为某种类型。
* 哪些注解或装饰器标记了入口。

如果没有 AST，工具只能在文本里猜。如果有 AST，工具就能基于语言结构进行可靠遍历。

## 语义分析：把结构变成意义

AST 只告诉我们代码“长什么样”，还不能完全回答“名字指向谁”。例如：

```java
service.create(order);
```

AST 可以识别这是一次方法调用，方法名是 `create`，接收者是 `service`。但它不知道 `service` 具体是什么类型，也不知道 `create` 最终会解析到哪个类的方法。要回答这些问题，需要语义分析。

语义分析会建立：

* 符号表：记录类、方法、变量等有名字的实体。
* 作用域：判断某个名字在当前位置指向哪个定义。
* 类型关系：判断变量、字段和表达式的类型。
* 引用关系：把使用位置连接到定义位置。

这些信息是 IDE 跳转、引用查找、重命名重构、调用图构建和影响面分析的基础。

## 中间表示：更适合分析程序行为

AST 更接近源码结构，但很多分析需要更接近执行行为。例如一个 `if` 语句包含两个分支，循环会让执行路径回到前面，异常会改变正常控制路径。直接在 AST 上分析这些路径并不方便。

中间表示（IR）把程序转换成更规整的形式。基于 IR，工具可以构建控制流图（CFG）和数据流图（DFG），进一步分析：

* 某段代码是否可达。
* 一个变量的值从哪里来。
* 用户输入是否流向危险 API。
* 修改一个条件会影响哪些路径。
* 哪些路径被测试覆盖。

对代码可视化来说，中间表示的价值在于把语法结构转化为行为关系。后续的安全分析、测试选择、影响面分析和性能路径解释，都可能依赖这些表示。

## 代码可视化需要哪些编译器产物

不同可视化场景需要的产物不同：

| 场景           | 需要的编译器或分析产物            |
| ------------ | ---------------------- |
| 代码大纲         | AST、声明节点、源码位置          |
| 类图和继承图       | 符号表、类型关系、继承和实现关系       |
| 调用图          | AST 调用表达式、类型解析、符号引用    |
| 影响面分析        | 调用图、依赖图、变更实体映射         |
| 数据流安全分析      | CFG、DFG、污点源和危险 Sink    |
| 自动重构         | AST、符号表、引用关系、类型检查      |
| AI Agent 上下文 | 文件结构、符号、调用关系、相关测试、架构规则 |

这张表说明一个关键点：代码可视化不是单一技术，而是多个分析产物的组合。

## 静态语言与动态语言的差异

Java、C#、Go、Rust 这类静态类型语言通常更容易获得稳定的类型和调用关系。Python、JavaScript、Ruby 这类动态语言更灵活，但静态分析难度更高。

动态语言中，函数、对象和模块关系可能在运行时改变。框架也可能通过约定、装饰器、依赖注入或反射建立关系。此时，工具需要结合更多信号：运行时 Trace、测试执行、类型注解、配置文件和框架规则。

这并不意味着动态语言不能做代码可视化，而是要更谨慎地标注不确定性。比如调用图中的某些边是确定解析出来的，某些边是根据命名约定推断的，某些边需要运行时数据确认。

## 和 AI 时代的关系

AI Agent 读代码时，也会遇到同样的问题。它可以读取文本，但如果没有结构化索引，就很容易遗漏相关文件、误判调用关系或忽略架构边界。

编译器视角提供的是一种上下文工程能力：把仓库解析成符号、关系和路径，让 AI 在修改代码前可以查询事实，而不是盲目读取大量文件。

例如 Agent 可以先查询：

* 目标方法在哪里定义。
* 谁调用了这个方法。
* 它调用了哪些下游方法。
* 相关测试有哪些。
* 它所在模块有哪些依赖约束。

这些查询都建立在源码结构化和语义分析之上。

## 小结

编译器视角告诉我们：代码可视化的起点不是图，而是结构化。词法分析让工具识别基本语法单元，语法分析让工具获得 AST，语义分析让工具理解符号和类型，中间表示让工具进一步分析控制流和数据流。

后续章节会依次展开这些基础能力。理解它们之后，代码图谱、影响面分析、AI Agent 上下文和 Review 证据层都会变得更容易解释。
