Files
2026-10-13 15:36:42 +08:00

459 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 01 - 语言概述与编译流程
> 注:本章为原本的 `01-overview.md`
## 语言定位
`Viper` 是一种 **系统级编程语言**,其语法基于 `Python`,但编译目标为 `LLVM IR`(注意,在计划中,我们期望迁移到 `MLIR`,这点是 `Viper` 的预期受众方向所要求的,`Viper` 期望走 边缘 `AI`等路线,仅提及),最终生成原生机器码。`Viper` 不是 `Python` 的超集或子集,也不是"`C` 的语法糖"——它是一门拥有独立类型系统、独立编译模型、独立模块体系的语言。`Viper` 选择 `Python` 语法作为表达形式,是因为至少在作者角度认为,`ABC-LIKE` 的可读性比 `C-LIKE` 要高得多,作者想要证明 `ABC-LIKE` 比 `C-LIKE` 更好,不过这是来自于作者很早期的不成熟想法。实际上,两者并没有实际可比性,二者语法均可以映射同一层的语义,其工程表达力不受语法家族限制。语法家族不决定你能表达什么,但决定你多容易表达。Viper 的赌注是:`ABC-LIKE` 的可读性与低样板收益,值得用工程手段(软关键字、自研前端、`LSP` 存根)去换取底层语义的落地能力。
当前编译器 `TransPyC`(用 Python 实现)早期使用 Python 标准库 `ast` 解析源文件,将精力集中在语义翻译而非词法/语法分析上。**自举已完成**:`ViperC`(用 Viper 重写编译器自身)先由 TPC 编译出 `viperc.exe`,再由其自举编译自身(Phase A/B/C 已跑通),编译已脱离 Python 运行时——解析、类型检查、IR 生成均由原生机器码完成。解析器为 [includes/ast](../includes/ast) 的**完全自研词法/语法器**(词法器 + 语法器 + AST 构造,`AstTest` 综合测试通过),覆盖 CPython 几乎全部语法。详见 [13-bootstrapping.md](13-bootstrapping.md)。
而实际上,下文中所有提到的 `C` 语言都是为了便于使用 `C` 的底层开发者理解。由于历史原因,在旧版本中我们使用 `C` 来作为中间语言,如同旧版本的 `C++` 一样,但现在 `target="c"` 的路径已经完全删除,`C` 路径永久不再受维护。
> **成熟度与当前任务(重要)**:Viper 的**语法在最新版本已经相当稳定**,对**现实工程已经可用**——[17-flagship-project.md](17-flagship-project.md) 中的 ViperC(自举编译器)、ViperOS(裸机操作系统)、ViperMNIST(深度学习)等都是实证。因此当前的主要工作**不是**继续改动语法,而是**更正内部的存量代码**:把历史遗留的旧写法(`t.C*` / `c.*` / `T | t.CPtr` 等)迁移到新语法,详见 [02-type-system.md 类型书写规范](02-type-system.md#类型书写规范新语法推荐) 与仓库根目录的 `新规范`。
>
> **Viper 的前身是一个玩具项目**(把 Python 转成 C),**但我们期望 Viper 不再是**:它拥有独立的类型系统、两阶段编译模型、`SHA1` 命名空间模块体系与自举编译器,目标是成为一门可用于真实系统开发的编程语言。
### 核心设计决策
1. **Python 语法,LLVM 语义**:源文件语法**近似 CPython**(并**非**完全合法,差异见下文「与 CPython 语法的关系」),但注解中的 `int` / `uint` / `int64` 等类型名被赋予与 `Python` 完全不同的意义——它们不表示 Python 的动态整数,而是直接对应 `LLVM` 的定宽类型(如 `int` = `i32`、`int64` = `i64`)
2. **类型注解即编译指令**:Viper 的类型注解不是可选的提示,而是编译器生成 `LLVM IR` 的决定性依据。`x: int` 生成 `i32`,`x: int*` 生成 `i32*`,但无注解时会自动推导,详见下文。
3. **两阶段编译**:先从源文件提取声明接口(`.vpi` + `.stub.ll`),再使用声明接口翻译源文件为含代码的 `.ll`。这是 `Viper` 区别于简单"`Python` 转 `C`"工具的关键架构
4. **`SHA1` 命名空间**:每个源文件按内容 `SHA1` 哈希命名,非导出函数和结构体自动加上 `SHA1` 前缀,从根本上消除跨模块的符号冲突
5. **万物皆数据**:为便于底层开发,实际上所有的变量和类型一般并不适用于鸭子类型,但在语义层面我们会尽可能贴近鸭子类型。
### 与 `Python` 的关系
| 特性 | `Python` | `Viper` |
|------|--------|-------|
| 类型系统 | 动态类型 | 静态类型(由类型注解在编译时确定) |
| 内存管理 | `GC` 自动回收 | 手动管理,无 `GC`,无运行时 |
| 运行时 | 解释执行 + 字节码 | 编译为原生机器码(通过 `LLVM`) |
| 对象模型 | 万物皆对象 | 类内定义方法即启用 OOP(自动判定 `IsOOP`);`virtual class` 为多态(vtable),`novirtual class` 为非多态继承(C++ 风格,零运行时开销);普通 `class`(无方法)仅为结构体布局。`@t.Object` 默认不写、避免使用 |
| 标准库 | `Python` 标准库 | `Viper` 标准库(`includes/`)+ `ViperOS SDK` |
| 空值 | `None` | `None`(编译为 `NULL`/零值) |
| 模块系统 | 运行时 `import` | 编译时解析,插入 `.stub.ll`,`SHA1` 命名空间隔离 |
### 与 C 的关系
Viper 编译后的代码在底层等价于 `C` 编译后的机器码(都经过 LLVM),但 Viper 在语言层面提供了 C 所没有的能力:
- **SHA1 命名空间**:自动消除符号冲突,无需手动管理 `static`/命名前缀
- **类型书写**:短名别名(`int`/`uchar`/`uint64`/`bool`…)+ `T*` 指针 + 前缀描述符(`const`/`volatile`/`__attribute__(...)`),接近 C 且比链式 `t.CConst | t.CInt | t.CPtr` 更简洁
- **声明式内联汇编**:`__asm__(f"mov {asmout(x, ASM_DESCR.OUTPUT_REG)}, rdi")` 比裸 `__asm__` 更安全、更具可读性(旧写法 `c.Asm`/`c.AsmOut` 等价)。
- **结构化预处理**:原生关键字 `cif` / `celif` / `celse` 缩进块替代 `#if`/`#ifdef`/`#ifndef`(用 `defined(NAME)` 做宏存在性判断),块结束即 `#endif`(无独立的 `c.CEndif()`)。
- **面向对象**:类内方法即 OOP;`virtual class` 提供 `vtable` 支持的多态;`novirtual class` 提供 C++ 风格的非多态继承(零 vtable 开销);PEP 695 泛型 + 递归泛型继承实现强类型自引用结构
- **存根驱动的模块系统**:`.vpi` 存根文件实现跨模块类型解析,无需头文件
- **更多内容**:额外更多的不依赖操作系统的函数和语法
> **FFI/导出新写法**:推荐使用语言软关键字 `extern`/`export`——`extern def foo(...) -> T` 仅声明外部函数(实现来自外部),`export def` 以裸名导出符号。解析器会把它们改写为返回注解中的存储类标记(`extern` → `T | t.State`,`export` → `t.CExport`),因此 `t.State`/`t.CExtern`/`t.CExport` 标记并未废弃,仍是内部承载(手写可用)。
### C-LIKE 语法(逐步支持)
Viper 的语法形态以 `Python`(ABC 系)为蓝本,但自研语法器 [includes/ast](../includes/ast) **将逐步支持 C-LIKE 写法**(C 风格的分隔与声明形式)。作者认为这是**值得投入**的方向,因此**两种写法及其语义都会被长期维护、保持一致**——不存在弃用某一端的计划。
就**实际工程**而言,仍然**推荐 ABC-LIKE 写法**:它与 `Python` 习惯一致、可读性最好,也是本仓库全部工程与文档采用的形态。
## 两阶段编译流程
Viper 的编译由入口驱动(TPC:`Projectrans.py`;VPC:`App/main.vp`),分为两个阶段。这是 Viper 编译模型的核心,理解两阶段编译是理解 `t.CDefine`、`t.CExport`、`t.CInline` 等关键概念的前提。
```
源文件 (.vp) ──────────────────────────────────────────────────────
│ │
│ ┌─────────────── 阶段一:声明提取 ───────────────┐ │
│ │ │ │
│ │ 1. 计算源文件 SHA1 │ │
│ │ 2. 生成 <SHA1>.vpi(签名存根) │ │
│ │ 3. 构建结构体注册表 │ │
│ │ 4. 生成 <SHA1>.stub.ll(LLVM IR 声明) │ │
│ │ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │
│ │ │ │
│ │ 1. 加载所有 .vpi 和 .stub.ll │ │
│ │ 2. 构建共享符号表 │ │
│ │ 3. 收集内联函数符号 │ │
│ │ 4. 翻译源文件 → <SHA1>.ll(含代码) │ │
│ │ 5. llc 编译 → <SHA1>.o(目标文件) │ │
│ │ 6. ld.lld 链接 → 可执行文件 │ │
│ │ │ │
│ └────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
```
### 阶段一:声明提取(Phase1Generator)
阶段一的目标是从每个源文件提取**声明接口**,使其他模块在阶段二翻译时能够正确解析跨模块引用。
#### 步骤 1:可达文件发现与拓扑排序
从工程根出发,通过 `import` 语句递归遍历,找出所有可达的 `.vp` 源文件,再对文件进行拓扑排序,确保被依赖的模块先被处理。工程根由工程配置的 `source_dir` 指定(`ViperC` 与 `ViperC/Test` 用 `project.vpj`,`Test/*` 用同格式的 `project.json`);也可用 `--src <入口文件>`,此时构建配置由依赖闭包内的 `viperbuild` 提供(见 [12-compilation-system.md](12-compilation-system.md))。**不存在「只编一个文件」的单文件模式。**
```
main.vp → import serial → import drivers.serial.uart.serial → ...
```
#### 步骤 2:生成 `.vpi` 签名存根
对每个源文件,计算其内容的 SHA1 哈希(16位),然后调用 `PythonToStubConverter` 生成签名存根文件 `<SHA1>.vpi`。
**SHA1 计算方式**:
```python
sha1 = hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
```
**存根文件的内容规则**:
| 源文件元素 | 存根文件中的表示 |
|-----------|----------------|
| `define` 常量(旧写法 `t.CDefine`) | 保留原样(含赋值):`define MAX_SIZE = 1024` |
| 宏函数(`define F(...)`,或返回 `t.CDefine`) | 保留完整函数体(宏展开需要) |
| 普通函数 | 仅保留签名:`def foo(x: int) -> void: pass` |
| 类 / `struct` 定义 | 保留成员类型注解和方法签名 |
| 全局变量 | TPC 一代 `PythonToStubConverter` 会添加 `t.CExtern`(`extern x: int` 的旧标记形态);VPC 自研 `StubGen`/`Converter.vp` 不再添加——跨模块 `external declare` 由 `VpiReader` 依 vpi 全局自动注入 |
| `cif` / `celif` / `celse` 条件编译块(含 `defined(NAME)` 谓词) | 保留原样(旧的 `c.CIfdef` 等已删除) |
| `import` 语句 | 保留原样 |
**关键设计**:`define` 常量与宏函数在存根中**保留完整定义**(包括赋值和函数体),因为它们是编译时常量/宏,其他模块引用时需要完整展开。普通函数只保留签名(仅声明),因为函数体在阶段二生成。
#### 步骤 3:构建结构体注册表
扫描所有已生成的 `.vpi` 文件,提取:
- **结构体名称集合**:所有非枚举、非异常的 `class` 定义
- **枚举名称集合**:继承 `t.CEnum` 的类
- **异常名称集合**:继承 `Exception` 的类
- **结构体→SHA1 映射**:记录每个结构体定义在哪个模块中
这个注册表用于阶段一的声明生成和阶段二的类型解析,确保跨模块的结构体引用使用正确的 SHA1 命名空间。
#### 步骤 4:生成 `.stub.ll` LLVM IR 声明
对每个 `.vpi` 文件,由 `DeclarationGenerator` 生成对应的 LLVM IR 声明文件 `<SHA1>.stub.ll`。
**声明生成规则**:
| 元素 | 生成的 LLVM IR |
|------|---------------|
| 普通函数 | `declare void @"<SHA1>.foo"(i32)` — 未 `export` 的函数加 SHA1 前缀 |
| `export` 函数(内部 `t.CExport`) | `declare i32 @main()` — 不加前缀,全局可见 |
| 结构体 | `%"<SHA1>.ClassName" = type { i32, i32* }` — 类型名加 SHA1 前缀 |
| 全局变量 | `@varname = external global i32` |
| `define` 常量(内部 `t.CDefine`) | **不生成声明**(编译时常量,直接内联展开) |
| `typedef` 别名(内部 `t.CTypedef`) | **不生成声明**(类型别名,在类型解析时展开) |
| 枚举 | 仅旧 Python `DeclarationGenerator` 行为:为每个枚举成员生成 `@__config_EnumName_member = external global i32`;现枚举由 `ClassGenEnum` 注册为编译期常量,不再生成 |
**增量编译**:如果 `<SHA1>.vpi` 或 `<SHA1>.stub.ll` 已存在,则跳过生成(缓存命中)。只有源文件内容变化导致 SHA1 变化时才重新生成。
### 阶段二:代码翻译(Phase2Translator)
阶段二使用阶段一生成的声明接口,将源文件翻译为包含完整代码的 LLVM IR。
#### 步骤 1:加载声明接口
加载 `temp/` 目录中所有的 `.vpi` 和 `.stub.ll` 文件,构建:
- `sig_files`:SHA1 → `.vpi` 文件路径映射
- `stub_files`:SHA1 → `.stub.ll` 文件路径映射
- `sha1_map`:SHA1 → 源文件相对路径映射
#### 步骤 2:构建共享符号表
一次性构建所有文件共享的符号表数据,避免每个文件重复加载。将所有 `.vpi` 存根和 `.stub.ll` 声明中的类型信息注册到统一的 `SymbolTable` 中。
#### 步骤 3:收集内联函数符号
扫描 `includes/` 目录中被引用的模块,收集标记为内联的函数(旧标记 `t.CInline`)。内联函数的完整 AST body 被保存,在翻译调用点时直接展开。值得注意的是,和 C 语言不同,这种前端内联不是优化建议,而是强制性的。
> 注:上述 `_collect_inline_symbols` 预收集机制属 TPC Python 侧实现;ViperC 无对应实现——`t.CInline` 仅作为存储类标记,实际内联经 `@c.Attribute(t.attr.always_inline())`。
#### 步骤 4:翻译源文件
对每个源文件,使用对应翻译器(TPC `TransPyC` / VPC `lib/core/Gen`)将 AST 翻译为 LLVM IR:
1. 将对应模块的 `.stub.ll` 声明嵌入到生成的 `.ll` 文件头部
2. 所有被引用模块的 `.stub.ll` 声明也被嵌入
3. 翻译 AST 节点为 LLVM IR 指令
4. 输出 `<SHA1>.ll` 文件
#### 步骤 5:编译与链接
- `llc`:将每个 `.ll` 编译为 `.o` 目标文件
- 链接:将所有 `.o` 链接为最终可执行文件。链接器由工程配置 `linker.cmd` 决定——ViperC 宿主工程为 `clang++`;`ld.lld`/`x86_64-none-elf` 用于 ViperOS 内核工程
## SHA1 命名空间机制
SHA1 命名空间是 Viper 解决跨模块符号冲突的核心机制。
### 问题
在 C 语言中,多个模块可能定义同名函数或结构体,导致链接时符号冲突。C 的解决方案是 `static`(限制可见性)和命名前缀(手动避免冲突)。
### Viper 的解决方案
Viper 使用源文件内容的 SHA1 哈希作为命名空间前缀:
```
源文件 A.vp(SHA1 = a1b2c3d4e5f6g7h8)中定义:
struct Point: x: int; y: int
def draw(p: Point*) -> void: ...
源文件 B.vp(SHA1 = i9j0k1l2m3n4o5p6)中也定义:
struct Point: x: float; y: float # 不同的结构体!
def draw(p: Point*) -> void: ...
编译后:
A.vp 的结构体 → %"a1b2c3d4e5f6g7h8.Point" = type { i32, i32 }
A.vp 的函数 → declare void @"a1b2c3d4e5f6g7h8.draw"(%"a1b2c3d4e5f6g7h8.Point"*)
B.vp 的结构体 → %"i9j0k1l2m3n4o5p6.Point" = type { float, float }
B.vp 的函数 → declare void @"i9j0k1l2m3n4o5p6.draw"(%"i9j0k1l2m3n4o5p6.Point"*)
```
**没有符号冲突**——即使两个模块定义了同名类型和函数,它们的 LLVM IR 符号也是不同的。即便有两个内容完全相同的文件也不会冲突,他们会被识别为同一个文件,然后进行单次编译。
### SHA1 前缀的豁免:`export`
用 `export` 声明的函数(内部标记 `t.CExport`)**不加 SHA1 前缀**,保持原始函数名。这是模块向外部暴露 API/ABI 的机制:
```python
# main.vp — 入口函数,必须全局可见
export def main() -> int:
return 0
# 编译后:define i32 @main() — 无 SHA1 前缀
```
```python
# serial.vp — 驱动接口,对外暴露
export def init() -> void:
serial_puts("init\n")
# 编译后:define void @init() — 无 SHA1 前缀,其他模块可直接调用
```
### SHA1 与增量编译
SHA1 同时服务于增量编译:
- 源文件内容不变 → SHA1 不变 → `.vpi` 和 `.stub.ll` 缓存命中,跳过阶段一
- 源文件内容变化 → SHA1 变化 → 重新生成声明接口
- `temp/_sha1_map.txt` 记录 SHA1 → 源文件路径的映射(仅人类可读);阶段二映射在内存中(`Sha1Store`/`StubMergerStore`)
理论上,如果一个未经变动的模块的依赖路径上存在变动的模块,暂时的方法是将其简单做 SHA1 替换,暨将旧 SHA1 替换为新 SHA1,但不适用于签名改变的情况,不过签名改变这个未经变动的模块必然也需要改变,但是由于宏展开的存在,不能完全保证不会发生错误情况。且依赖路径的 SHA1 替换成功性未经完整测试,可能出现未定义行为。
<a id="tcdefine-深度解析"></a>
## 编译期常量与宏(`define` / `t.CDefine`)深度解析
`t.CDefine` 是编译时元指令(**推荐用软关键字 `define` 书写**),控制编译器的代码生成行为——它不是真实的数据类型。
### `define` 常量
```python
define MAX_SIZE = 1024
define PAGE_SIZE = 4096
define FA_READ = 0x01
define CPU_FEATURE_FPU = (1 << 0)
```
> 推荐写法是软关键字 `define X = v`(等价旧写法 `X: t.CDefine = v`)。
**编译行为**:
1. **阶段一**:存根生成器保留 CDefine 常量的完整定义(包括赋值值),因为其他模块可能引用此常量
2. **阶段一**:`.stub.ll` 声明生成器**跳过** CDefine 常量,不生成 `external global` 声明
3. **阶段二**:翻译器遇到 CDefine 常量的引用时,直接内联其值,不生成任何加载指令
这意味着 CDefine 常量在 LLVM IR 层面**不存在**——它们在编译时被完全展开,等价于 C 的 `#define` 宏。
### 宏函数(`define F(...)`)
当函数用 `define F(...)` 声明(或返回类型注解含 `t.CDefine`)时,被编译器视为**宏函数**:
```python
define MAKE_FLAG(bit):
return (1 << bit)
define GET_PAGE_ORDER(size):
return size // PAGE_SIZE
```
> `define F(...)` 会自动追加 `t.CDefine` 返回注解;等价旧写法 `def F(...) -> t.CDefine:`。注意与 `define X = v`(常量)区分:带 `(` 是宏函数、带 `=` 是常量。
**编译行为**:
1. **阶段一**:存根生成器保留宏函数的**完整函数体**(不像普通函数那样只保留签名)
2. **阶段二**:翻译器遇到宏函数的调用时,将调用内联展开为函数体的计算结果
宏函数在 LLVM IR 中**不生成函数定义**——它们是纯粹的编译时宏。
### `define X: T = v` 与类型组合
`define` 可以带类型注解,为常量指定底层类型(内部等价于把 `t.CDefine` 与具体类型组合):
```python
define CODE_SEG: uint16 = 0x08
define DATA_SEG: uint16 = 0x10
```
这表示常量在类型检查时被视为 `uint16`(`uint16_t`),但在代码生成时仍然内联展开。
> 旧写法 `CODE_SEG: t.CDefine | t.CUInt16T = 0x08` 等价,手写可用但不推荐。
<a id="tcexport-深度解析"></a>
## `export` 深度解析(内部标记 `t.CExport`)
`export` 软关键字控制函数的符号可见性,是 SHA1 命名空间的豁免机制(内部改写为返回注解中的 `t.CExport` 标记)。
### 语义
| 写法 | LLVM IR 函数名 | 链接可见性 | 用途 |
|------|---------------|-----------|------|
| 无修饰 | `@<SHA1>.funcname` | 模块内部 | 模块私有函数 |
| `export def f(...)` | `@funcname` | 全局可见 | 对外暴露的 API |
| `extern def f(...)` | `@funcname`(仅声明) | 外部定义 | 引用外部函数 |
> 手写等价形态:`def f(...) -> T | t.CExport`(导出)、`def f(...) -> T | t.State`(外部声明)。
### 使用场景
```python
# 入口函数 — 必须全局可见,链接器需要找到它
export def _start() -> int:
return kernel_main()
# 驱动接口 — 其他模块/应用需要调用
export def init() -> void:
uart_init()
# 内部辅助函数 — 不需要全局可见,自动加 SHA1 前缀
def _helper() -> int:
return 42
```
### `export` 在存根中的表现
在 `.vpi` 存根文件中,`export` 函数与普通函数一样只保留签名(不添加 `extern` 存储类,表示单纯声明),但内部 `t.CExport` 标记被保留在返回类型注解中。阶段一的 `DeclarationGenerator` 检查该标记来决定是否添加 SHA1 前缀。
## `inline` 深度解析(旧标记 `t.CInline`)
推荐写法是 **`inline` 软关键字**:`inline def f(...)` 等价于给函数加 `@t.attr.always_inline`(LLVM `alwaysinline`)——这是**编译期提示落到 IR 函数属性**,由 LLVM 决定实际内联,**不是**“调用点展开”。
```python
inline def fast_add(a: int, b: int) -> int:
return a + b
```
> 旧的 `-> T | t.CInline`(`t.CInline`)**不是**内联指令、也**不产生调用点展开**,它只是一个存储类标记,已不推荐;做真正内联请用 `inline`。
### `inline` 与 `define F(...)` 的区别
| 特性 | `inline def f(...)` | `define F(...)`(宏函数) |
|------|---------------------|--------------------------|
| 展开时机 | LLVM 优化阶段(`alwaysinline` 属性) | 阶段二翻译时(前端内联) |
| 类型检查 | 完整的参数和返回类型检查 | 返回类型为宏,类型检查较弱 |
| 存根表示 | 仅签名(不添加 `extern` 存储类) | 完整函数体 |
| LLVM IR | 生成函数定义 + `alwaysinline` 属性 | 不生成函数定义 |
| 适用场景 | 性能关键的短函数 | 编译时常量和宏计算 |
## 目标平台
默认目标三元组:`x86_64-none-elf`
数据布局:`e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128`
> **为什么暂时深度绑定 Windows?** 纯属**计划与精力有限**——工具链、链接器与运行时验证目前集中在 Windows(`llc` + mingw `clang++`),并非语言设计上的限制;**实际大量代码是可跨平台复用的**,后续按需逐步扩展到其它平台。
## 软关键字一览
以下均为**语言层软关键字**(由 [includes/ast](../includes/ast) 解析器识别,不是 `t` 模块的类型注解),内部改写为既有标记:
| 软关键字 | 形态 | 展开 / 语义 |
|----------|------|-------------|
| `extern` | `extern def f(...)` / `extern NAME: T` | 返回注解加 `t.State`;仅声明 / 外部全局(禁初值) |
| `export` | `export def f(...)` / `export NAME: T [= v]` | 加 `t.CExport`;裸名导出 |
| `static` | `static def f(...)` / `static x: T = v` | 类型联合最左插 `t.CStatic`;禁止跨模块 |
| `inline` | `inline def f(...)` | 合成 `@t.attr.always_inline` |
| `virtual` / `novirtual` | `virtual class C` / `novirtual def m` | 类级 = 该类**所有方法**的默认级别;方法级 = 只改**单个方法**(覆盖类级默认) |
| `const` / `volatile` | `const T` / `volatile T`(注解前缀) | 合成 `t.CConst` / `t.CVolatile`;volatile 落 LLVM `volatile` |
| `__attribute__(...)` | 注解前缀或 `@__attribute__(...)` | 脱糖为 `t.attr.x`(比 GCC 少一层括号) |
| `define` | `define X = v` / `define X: T = v` | 编译期常量(自动加 `t.CDefine`) |
| `define` | `define F(...) -> T` | 宏函数(自动加 `t.CDefine` 返回注解) |
| `typedef` | `typedef A = B` | 等价 `A: t.CTypedef = B` |
| `enum` / `union` / `renum` | `enum E:` / `union U:` / `renum R:` | 等价 `class X(t.CEnum / t.CUnion / t.REnum)` |
| `struct` | `struct S:` | 纯数据结构体:字段规则同 `class`,但禁 `virtual`/`novirtual`、禁 `def`;可嵌套、可在 `renum` 内作变体 |
| `array` | `array[E, N]`(仅注解位置) | 等价 `t.CArray[E, N]` |
| `[N]T` | 注解前缀数组 | 等价 `t.CArray[T, N]` |
> 变量级 `static` / `extern` / `export` 与函数级同源;`extern` 变量**不允许带初始值**。旧的注解位 `static`(`名字: static 类型`)已不再支持。
>
> `t.C*` / `c.*` 正在被逐步淘汰:类型改短名别名(`int`/`uchar`/`uint64`/`bool`…),指针改 `T*`,指针操作改 `&`/`*`,详见 [02-type-system.md 类型书写规范](02-type-system.md#类型书写规范新语法推荐)。
>
> **`inline` 关键字**:写 `inline def f(...)`,等价于给函数加 `@t.attr.always_inline`(LLVM `alwaysinline`)——它是**编译期提示落到 IR 函数属性**,**不是**“调用点展开”。旧的 `-> T | t.CInline` 只是存储类标记,不产生内联。
## 与 CPython 语法的关系(Vipeye)
Viper **原本的语法完全符合 CPython**——源文件是合法的 Python 语法,早期直接用 Python 的 `ast` 解析。转折点是 **Vipeye**(基于 **pyright** 的 VSCode Viper LSP 分析插件)的建立。
**这个转折是正向的**:正因为有了 Vipeye,Viper 的语法设计**不再受「必须保持为合法 Python」这一约束**,才敢于引入一批 Python 语法容纳不下的关键字与写法——软关键字 `extern`/`export`/`static`/`inline`/`virtual`/`novirtual`/`volatile`/`define`/`typedef`/`enum`/`union`/`renum`/`struct`/`array`,后缀指针 `T*`、前缀数组 `[N]T`、描述符 `const`/`volatile`/`__attribute__(...)`、指针算子 `&`/`*`。也正因为如此,**这批语法已经与标准 Python 有了部分出入**,而**这种出入是设计自由度的体现,而非兼容性缺陷**。
> **Vipeye 时刻跟进 Viper 的语法更改**:语言侧新增/调整关键字与语法后,Vipeye(经 `t.vpi` / `c.vpi` 存根)会同步跟进,保证 LSP 的诊断与补全与现行编译器一致。
>
> 结论:把 Viper 当 Python 写「大概率能解析」,但**不要假定完全等价**;语法请以本 wiki 为准。
## 附带项目与工具
| 项目 | 说明 |
|------|------|
| **Vipeye** | 基于 **pyright** 的 VSCode **Viper LSP** 分析插件(外部项目,本工作区不含其源码) |
| [VET](../VET) | Viper Environment Tool:校验产物确由 ViperC 工具链生成,并常驻扫盘发现 Viper 产物 |
| [Venom](../Venom) | Windows 桌面应用最小模板(UTF-8 控制台 + Hello World → `app.exe`) |
| [ViperTemplateProject](../ViperTemplateProject) | 同上定位的 Windows 桌面应用模板 |
| [ViperApps](../ViperApps) | 示例应用集(Qt6 / MNIST / FLTK / Requests / OpenSSL / [ViperOS](../ViperApps/ViperOS) 等) |
| [Test](../Test) | TPC 测试工程集(68 个,`project.json`);[ViperC/Test](../ViperC/Test) 为 VPC 自测套件 |
| [TransPyC](../TransPyC) | 第一代编译器(CPython 实现,自举**种子**) |
| [ViperC](../ViperC) | 第二代编译器(Viper 自举,**现行编译器**) |
| [Tools](../Tools) | 辅助脚本;`sendaiandsplit.py` 可合并/分割源码,`ai` 模式产出带**弱时效性**声明的 AI 快照 |
| [wiki](../wiki) | 本文档;`MergeWiki.py` 可把全部章节合并成 AI 快照 |
## 章节延伸
- 交互式 REPL(`--repl`)见 [15-repl.md](15-repl.md);
- 栈回溯与调试信息(`--traceback` / DWARF)见 [16-traceback.md](16-traceback.md);
- 用 Viper 写成的旗舰工程(ViperC / VET / ViperOS / ViperMNIST)见 [17-flagship-project.md](17-flagship-project.md)。
## Hello World 示例(标准写法)
```python
export def main() -> int:
print("Hello, World!")
return 0
```
> **不要**把下面这种旧写法当标准示例(它混用了 `t` 模块与旧注解):
>
> ```python
> import t, c
>
> def main() -> t.CInt | t.CExport:
> print("Hello, ViperOS!\n")
> return 0
> ```
>
> 标准写法以 `export def main() -> int:` 为准。
编译流程:
1. **阶段一**:计算 `main.vp` 的 SHA1,生成 `.vpi` 存根(`def main() -> int: pass`),生成 `.stub.ll` 声明 `declare i32 @main()`
2. **阶段二**:翻译 `main.vp` 为 `<SHA1>.ll`,嵌入 `print` 输出所需的声明,生成 `define i32 @main()` 函数体
3. **编译**:`llc` 将 `.ll` 编译为 `.o`
4. **链接**:按工程配置的 `linker.cmd` 链接为可执行文件
### `main` 的特殊处理
`main` **无论是否写 `export`**,都会被按**裸名**(不加模块 SHA1 前缀)处理、导出为 `@main`——因为链接器 / CRT 需要找到它。这是**编译器内建特例**,**后续可能会更改**(届时可能要求显式 `export`)。
### `print` 是编译器内建
`print(...)` 目前是**编译器内建**:做**类型感知的格式化输出**(`str`/`%s`、`int`、`float`、`struct` 各按类型选格式),默认追加换行,最终经 `printf` 族函数输出;它**不是**普通函数调用,也不直接映射到 `printf` 本身。后续计划把它**下降到标准库**、用 Viper 代码实现(届时语义以标准库为准)。