|
| 1 | +# UAST 结构与转换流程详解 |
| 2 | + |
| 3 | +## 1. 引言 |
| 4 | + |
| 5 | +本文档旨在详细解析项目中的 **UAST (Universal Abstract Syntax Tree, 统一抽象语法树)** 的核心数据结构、设计理念以及从特定语言的 CST (Concrete Syntax Tree, 具体语法树) 到 UAST 的转换流程。 |
| 6 | + |
| 7 | +**面向读者**: |
| 8 | +* **新加入的研发人员**: 快速理解项目核心的代码表示层。 |
| 9 | +* **语言扩展开发者**: 在为项目支持新语言时,提供标准的 UAST 构建指南。 |
| 10 | +* **架构师与代码分析工具开发者**: 深入了解 UAST 的设计,以便于上层应用的开发与集成。 |
| 11 | + |
| 12 | +**系统概览**: UAST 是本项目中用于表示多语言代码的统一中间表示。它将不同编程语言的语法结构抽象为一组通用的、包含丰富语义信息的图结构,是实现跨语言代码分析、转换和生成等功能的核心基础。 |
| 13 | + |
| 14 | +## 2. 核心概念与数据结构 |
| 15 | + |
| 16 | +UAST 的设计围绕着几个核心概念展开,它们共同构成了一个强大的代码表示模型。 |
| 17 | + |
| 18 | +### 2.1. 顶层结构 |
| 19 | + |
| 20 | +* `Repository`: 代码库的最高层级抽象,包含一个或多个 `Module`。 |
| 21 | +* `Module`: 代表一个独立的、特定语言的代码单元,例如一个 Go Module、一个 Java Maven 项目或一个 Python 包。 |
| 22 | +* `Package`: 语言内部的命名空间,如 Go 的 `package` 或 Java 的 `package`。 |
| 23 | +* `File`: 代表一个物理源代码文件。 |
| 24 | + |
| 25 | +### 2.2. 核心实体:`Node` |
| 26 | + |
| 27 | +`Node` 是 UAST 图模型中最基本的单元。每个 `Node` 代表代码中的一个具名实体。 |
| 28 | + |
| 29 | +* **`NodeType`**: 节点类型,主要分为三种: |
| 30 | + * `FUNC`: 代表函数或方法。 |
| 31 | + * `TYPE`: 代表类、结构体、接口、枚举等类型定义。 |
| 32 | + * `VAR`: 代表全局变量或常量。 |
| 33 | + |
| 34 | +* **`Identity`**: 全局唯一标识符,是链接不同 `Node` 的关键。它由三部分组成: |
| 35 | + * `ModPath`: 模块路径 (e.g., `github.com/your/project@v1.2.0`) |
| 36 | + * `PkgPath`: 包路径 (e.g., `github.com/your/project/internal/utils`) |
| 37 | + * `Name`: 实体名称 (e.g., `MyFunction`, `MyStruct.MyMethod`) |
| 38 | + * **完整形式**: `ModPath?PkgPath#Name` |
| 39 | + |
| 40 | +### 2.3. 实体详情 |
| 41 | + |
| 42 | +每个 `Node` 都关联一个更详细的实体描述结构,存储了该实体的具体信息。 |
| 43 | + |
| 44 | +* **`Function`**: 存储函数的签名、参数、返回值、接收者(如果是方法)以及它调用的其他函数/方法列表。 |
| 45 | +* **`Type`**: 存储类型的种类(`struct`, `interface` 等)、字段、内嵌/继承的类型、实现的方法和接口。 |
| 46 | +* **`Var`**: 存储变量的类型、是否为常量/指针等信息。 |
| 47 | + |
| 48 | +### 2.4. 关系:`Relation` |
| 49 | + |
| 50 | +`Relation` 用于描述两个 `Node` 之间的关系,是构建 UAST 图谱的边。 |
| 51 | + |
| 52 | +* **`RelationKind`**: 关系类型,主要包括: |
| 53 | + * `DEPENDENCY`: 表示一个节点依赖另一个节点(例如函数调用、类型使用)。 |
| 54 | + * `IMPLEMENT`: 表示一个类型节点实现了一个接口节点。 |
| 55 | + * `INHERIT`: 表示一个类型节点继承了另一个类型节点。 |
| 56 | + * `GROUP`: 表示多个变量/常量在同一个声明块中定义。 |
| 57 | + |
| 58 | +### 2.5. UAST 核心结构图 |
| 59 | + |
| 60 | +下图展示了 UAST 核心数据结构之间的关系。 |
| 61 | + |
| 62 | +```mermaid |
| 63 | +graph TD |
| 64 | + subgraph Repository |
| 65 | + direction LR |
| 66 | + A[NodeGraph] |
| 67 | + M(Modules) |
| 68 | + end |
| 69 | +
|
| 70 | + subgraph Module |
| 71 | + direction LR |
| 72 | + P[Packages] |
| 73 | + F[Files] |
| 74 | + end |
| 75 | +
|
| 76 | + subgraph Package |
| 77 | + direction LR |
| 78 | + Funcs[Functions] |
| 79 | + Types[Types] |
| 80 | + Vars[Variables] |
| 81 | + end |
| 82 | +
|
| 83 | + subgraph Node |
| 84 | + direction TB |
| 85 | + ID[Identity] |
| 86 | + NodeType[Type: FUNC/TYPE/VAR] |
| 87 | + Rels[Relations] |
| 88 | + end |
| 89 | +
|
| 90 | + Repository -- Contains --> M |
| 91 | + M -- Contains --> Module |
| 92 | + Repository -- Contains --> A |
| 93 | + A -- "Maps ID to" --> Node |
| 94 | +
|
| 95 | + Module -- Contains --> P |
| 96 | + Module -- Contains --> F |
| 97 | +
|
| 98 | + Package -- Contains --> Funcs |
| 99 | + Package -- Contains --> Types |
| 100 | + Package -- Contains --> Vars |
| 101 | +
|
| 102 | + Node -- Has a --> ID |
| 103 | + Node -- Has a --> NodeType |
| 104 | + Node -- "Has multiple" --> Rels |
| 105 | +
|
| 106 | + Funcs -- Corresponds to --> Node |
| 107 | + Types -- Corresponds to --> Node |
| 108 | + Vars -- Corresponds to --> Node |
| 109 | +
|
| 110 | + style Repository fill:#f9f,stroke:#333,stroke-width:2px |
| 111 | + style Module fill:#ccf,stroke:#333,stroke-width:2px |
| 112 | + style Package fill:#cfc,stroke:#333,stroke-width:2px |
| 113 | + style Node fill:#fcf,stroke:#333,stroke-width:2px |
| 114 | +``` |
| 115 | + |
| 116 | +## 3. 从 CST 到 UAST 的转换流程 |
| 117 | + |
| 118 | +将特定语言的源代码转换为统一的 UAST,主要分为以下几个步骤。此流程的核心思想是 **“先收集实体,再建立关系”**。 |
| 119 | + |
| 120 | +### 3.1. 流程概览 |
| 121 | + |
| 122 | +```mermaid |
| 123 | +graph TD |
| 124 | + A[1. 源代码] --> B{2. CST 解析}; |
| 125 | + B --> C[3. 遍历 CST, 创建 UAST 实体]; |
| 126 | + C --> D{4. 填充 Repository}; |
| 127 | + D --> E[5. 构建 Node Graph]; |
| 128 | + E --> F((6. UAST)); |
| 129 | +
|
| 130 | + subgraph "Language Specific Parser (e.g., Tree-sitter)" |
| 131 | + B |
| 132 | + end |
| 133 | +
|
| 134 | + subgraph "UAST Converter (Go Code)" |
| 135 | + C |
| 136 | + D |
| 137 | + E |
| 138 | + end |
| 139 | +
|
| 140 | + style A fill:#lightgrey |
| 141 | + style F fill:#9f9 |
| 142 | +``` |
| 143 | + |
| 144 | +### 3.2. 步骤详解 |
| 145 | + |
| 146 | +1. **CST 解析**: |
| 147 | + * 使用 `tree-sitter` 或其他特定语言的解析器,将输入的源代码字符串解析为一棵具体语法树 (CST)。CST 完整地保留了代码的所有语法细节,包括标点和空格。 |
| 148 | + |
| 149 | +2. **遍历 CST, 创建 UAST 实体**: |
| 150 | + * 编写一个针对该语言的 `Converter`。这个转换器会深度优先遍历 CST。 |
| 151 | + * 当遇到代表函数、类、接口、变量声明等关键语法节点时,提取其核心信息(名称、位置、内容等)。 |
| 152 | + * 为每个识别出的实体创建一个对应的 UAST 结构(`Function`, `Type`, `Var`),并为其生成一个全局唯一的 `Identity`。 |
| 153 | + * 在此阶段,也会初步解析实体内部的依赖关系,例如一个函数内部调用了哪些其他函数,这些信息会被临时存储在 `Function.FunctionCalls` 等字段中。 |
| 154 | + |
| 155 | +3. **填充 Repository**: |
| 156 | + * 将上一步创建的所有 `Function`, `Type`, `Var` 实体,按照其 `Identity` 中定义的模块和包路径,存入一个 `Repository` 对象中。此时,我们得到了一个包含所有代码实体信息但关系尚未连接的“半成品”。 |
| 157 | + |
| 158 | +4. **构建 Node Graph (`Repository.BuildGraph`)**: |
| 159 | + * 这是将离散的实体连接成图的关键一步。调用 `Repository.BuildGraph()` 方法。 |
| 160 | + * 该方法会遍历 `Repository` 中的每一个 `Function`, `Type`, `Var`。 |
| 161 | + * 为每一个实体在 `Repository.Graph` 中创建一个 `Node`。 |
| 162 | + * 然后,它会检查每个实体的依赖字段(如 `Function.FunctionCalls`, `Type.Implements` 等)。 |
| 163 | + * 根据这些依赖信息,在对应的 `Node` 之间创建 `Relation`,从而将整个图连接起来。例如,如果 `FunctionA` 调用了 `FunctionB`,那么在 `NodeA` 和 `NodeB` 之间就会建立一条 `DEPENDENCY` 关系的边。 |
| 164 | + |
| 165 | +### 3.3. 转换时序图示例 |
| 166 | + |
| 167 | +以下是一个简化的时序图,展示了从 Java 代码到 UAST 的转换过程。 |
| 168 | + |
| 169 | +```mermaid |
| 170 | +sequenceDiagram |
| 171 | + participant C as Converter |
| 172 | + participant T as TreeSitter |
| 173 | + participant R as Repository |
| 174 | + participant N as NodeGraph |
| 175 | +
|
| 176 | + C->>T: Parse("class A { void b() {} }") |
| 177 | + T-->>C: 返回 CST Root |
| 178 | +
|
| 179 | + C->>C: Traverse CST |
| 180 | + C->>R: Create/Get Module("my-java-project") |
| 181 | + C->>R: Create/Get Package("com.example") |
| 182 | + C->>R: SetType(Identity_A, Type_A) |
| 183 | + C->>R: SetFunction(Identity_B, Function_B) |
| 184 | +
|
| 185 | + Note right of C: 此时实体已收集, 但未连接 |
| 186 | +
|
| 187 | + C->>R: BuildGraph() |
| 188 | + R->>N: SetNode(Identity_A, TYPE) |
| 189 | + R->>N: SetNode(Identity_B, FUNC) |
| 190 | + R->>N: AddRelation(Node_A, Node_B, DEPENDENCY) |
| 191 | +
|
| 192 | + Note right of R: 图关系建立完成 |
| 193 | +
|
| 194 | + R-->>C: UAST Graph Ready |
| 195 | +``` |
| 196 | + |
| 197 | +## 4. 如何使用 UAST |
| 198 | + |
| 199 | +一旦 UAST 构建完成,你就可以利用它进行各种强大的代码分析: |
| 200 | + |
| 201 | +* **依赖分析**: 从任意一个 `Node` 出发,沿着 `Dependencies` 关系,可以找到它的所有依赖项。反之,沿着 `References` 可以找到所有引用它的地方。 |
| 202 | +* **影响范围分析**: 当一个函数或类型发生变更时,可以通过 `References` 关系,快速定位到所有可能受影响的代码。 |
| 203 | +* **代码导航**: 实现类似 IDE 的“跳转到定义”、“查找所有引用”等功能。 |
| 204 | +* **重构**: 自动化地进行代码重构,例如重命名一个方法,并更新所有调用点。 |
| 205 | + |
| 206 | +## 5. FAQ 与开发建议 |
| 207 | + |
| 208 | +* **为什么不直接使用 CST?** |
| 209 | + * CST 过于具体且语言相关,直接使用它进行跨语言分析非常困难。UAST 提供了一个统一的、更高层次的抽象视图。 |
| 210 | +* **如何添加对新语言的支持?** |
| 211 | + * 1. 找到或构建一个该语言的 `tree-sitter` 解析器。 |
| 212 | + * 2. 实现一个新的 `Converter`,负责遍历该语言的 CST,并创建 UAST 实体。 |
| 213 | + * 3. 确保 `Identity` 的生成规则与其他语言保持一致。 |
| 214 | +* **LSP 的作用是什么?** |
| 215 | + * 虽然 `tree-sitter` 能提供语法结构,但很多语义信息(如一个变量的具体类型、一个函数调用到底解析到哪个定义)需要更复杂的类型推导。LSP 已经完成了这些工作,可以作为信息来源,极大地丰富 UAST 中 `Relation` 的准确性和语义信息。在转换流程中,可以集成 LSP 查询来辅助确定依赖关系。 |
0 commit comments