# 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. 生成 .vpi(签名存根) │ │ │ │ 3. 构建结构体注册表 │ │ │ │ 4. 生成 .stub.ll(LLVM IR 声明) │ │ │ │ │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────── 阶段二:代码翻译 ───────────────┐ │ │ │ │ │ │ │ 1. 加载所有 .vpi 和 .stub.ll │ │ │ │ 2. 构建共享符号表 │ │ │ │ 3. 收集内联函数符号 │ │ │ │ 4. 翻译源文件 → .ll(含代码) │ │ │ │ 5. llc 编译 → .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` 生成签名存根文件 `.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 声明文件 `.stub.ll`。 **声明生成规则**: | 元素 | 生成的 LLVM IR | |------|---------------| | 普通函数 | `declare void @".foo"(i32)` — 未 `export` 的函数加 SHA1 前缀 | | `export` 函数(内部 `t.CExport`) | `declare i32 @main()` — 不加前缀,全局可见 | | 结构体 | `%".ClassName" = type { i32, i32* }` — 类型名加 SHA1 前缀 | | 全局变量 | `@varname = external global i32` | | `define` 常量(内部 `t.CDefine`) | **不生成声明**(编译时常量,直接内联展开) | | `typedef` 别名(内部 `t.CTypedef`) | **不生成声明**(类型别名,在类型解析时展开) | | 枚举 | 仅旧 Python `DeclarationGenerator` 行为:为每个枚举成员生成 `@__config_EnumName_member = external global i32`;现枚举由 `ClassGenEnum` 注册为编译期常量,不再生成 | **增量编译**:如果 `.vpi` 或 `.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. 输出 `.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 替换成功性未经完整测试,可能出现未定义行为。 ## 编译期常量与宏(`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` 等价,手写可用但不推荐。 ## `export` 深度解析(内部标记 `t.CExport`) `export` 软关键字控制函数的符号可见性,是 SHA1 命名空间的豁免机制(内部改写为返回注解中的 `t.CExport` 标记)。 ### 语义 | 写法 | LLVM IR 函数名 | 链接可见性 | 用途 | |------|---------------|-----------|------| | 无修饰 | `@.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` 为 `.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 代码实现(届时语义以标准库为准)。