Rex 编程语言语法参考(一):语言基础语法
Rex 是一门静态类型、编译型编程语言,使用 Python 实现,基于 LLVM(llvmlite)作为编译器后端,采用 LLJIT 执行。解析器采用手写递归下降解析器配合 Pratt 表达式解析。并发采用真实操作系统线程模型(thread 命名空间,配套互斥锁与原子操作,见库语法文档)。
本文档覆盖语言核心语法(类型、字面量、运算符、控制流、函数、结构体、数组、错误处理、模块、FFI、泛型等)。 标准库与运行时库函数(
math/io/file/os/string/thread/map/set等命名空间)见《Rex 编程语言语法参考(二):库语法》。 实战陷阱、性能优化、AOT、包管理、LSP、DAP、CLI 等工具与实现内容见《Rex 编程语言工具与实现参考》。
⚠️ 程序入口(必读):Rex 的可执行代码必须写在函数内,并以
fn main() { ... }作为程序入口。 把let/fn main时程序同样无法运行。 最简单的完整程序:rexfn main() { print("Hello, Rex!") }下文各节的语法片段为节选,默认省略了外层
fn main,复制运行时请放进fn main内; 可直接运行的完整示例见第 24 章。
1. 数据类型
内置类型
| 类型分类 | 类型关键字 | LLVM 映射 |
|---|---|---|
| 有符号整数 | int(64位) | i64 |
| 浮点数 | float(64位,双精度) | double |
| 布尔 | bool | i1 |
| 字符串 | str | i8*(C风格) |
| 指针 | ptr | i8*(通用) |
| 空类型 | void | void(仅返回值) |
| 结构体 | struct Name { ... } | LLVM 结构体类型 |
| 数组 | T[] 注解 / [T, ...] 静态 / new T[N] 动态 | [N x T] / T* |
| 字典 | Map[str, V] 注解(V ∈ int/float/str) | i64(句柄) |
| 集合 | Set[T] 注解(T ∈ int/float/str) | i64(句柄) |
| 列表 | List[T] 注解(语法糖,等价 T[]) | T*(动态数组) |
类型系统精简:整数统一为有符号 64 位
int,浮点统一为 64 位float。 已移除int64、uint、uint64、float64、char类型(不再向后兼容)。 字符用单字符字符串"a"表达。字典类型:
Map[str, V]是语言级字典(哈希表),LLVM 层表示为i64句柄, v1 仅支持字符串键、int/float/str三种值类型(详见第 13 章「Map 字典」)。集合类型:
Set[T]是语言级集合(哈希表,元素去重),LLVM 层表示为i64句柄, 元素类型支持int/float/str(详见第 14 章「Set 集合」)。列表类型:
List[T]是T[]的语法糖(动态数组),解析期直接规范化为T[], 复用动态数组机制(自动扩容、push、length、帧级释放)(见第 12 章)。数组类型精简:数组变量用
T[]类型注解声明(如int[]、float[][]), 不再用标量类型int注解数组(已移除,不再向后兼容)。
各类型声明速览(入门第一段代码):
fn main() {
// 标量类型
let n: int = 42 // 64 位有符号整数
let f: float = 3.14 // 64 位双精度浮点数
let b: bool = true // 布尔
let s: str = "Rex" // 字符串(双引号定义)
let p: ptr = null // 通用指针(FFI 用,可判空)
// 复合类型(详见第 12~14 章)
var arr: int[] = [1, 2, 3] // 动态数组(自动扩容、越界检查)
var m: Map[str, int] = {} // 字典(字符串键 -> int 值)
var set1: Set[str] = set.create() // 集合(元素去重)
print(n, f, b, s) // 42 3.140000 1 Rex
print(arr.len) // 3
map.set(m, "k", 1)
print(map.get(m, "k")) // 1
set1.add("x")
print(set1.has("x")) // 1
}2. 字面量
整数字面量
42
0
-100
1_000_000 // 下划线分隔,提高可读性(等价于 1000000)下划线数字:
1_000_000等价于1000000,下划线必须位于数字之间(1_000、3_000.5合法,_1、1_、1__0非法)。
浮点字面量
3.14
0.5
.5 // 小数点前可省略布尔字面量
true
false字符串字面量
"hello"字符串引号精简(不再向后兼容):字符串统一只用双引号定义, 已移除单引号字符串(如
'world')。使用单引号会得到词法错误提示:use double quotes for strings, e.g. "hello" instead of 'hello'。
空值字面量
null数组字面量
[1, 2, 3]
[1.1, 2.2, 3.3]Map 字面量
{} // 空 Map
{ "name": "Rex", "age": 30 } // 键值对(键为字符串字面量/表达式)非空 Map 字面量必须配合显式类型注解(如
var m: Map[str, int] = { "a": 1 }), 编译器依据注解确定值类型;空 Map 字面量{}也建议显式注解。
结构体字面量
Point { x: 10.5, y: 20.5 }结构体名必须以大写字母开头。
3. 注释
// 单行注释
/*
多行块注释
可跨行
*/4. 标识符
- 以字母(支持 Unicode,如中文、希腊字母)或下划线
_开头 - 后续字符:字母、数字、下划线
- 示例:
var 变量: int = 42、var π: float = 3.14159
5. 关键字(共21个)
| 关键字 | 用途 |
|---|---|
let | 不可变变量声明 |
var | 可变变量声明 |
fn | 函数声明 |
return | 从函数返回 |
if | 条件分支 |
else | 备选分支 |
while | while 循环 |
break | 退出循环 |
continue | 跳过本次迭代 |
for | for 循环(for-in 和 range) |
import | 模块导入 |
from | 选择性导入 |
in | for-in 循环关键字 |
try | 异常处理块 |
catch | 捕获异常块 |
throw | 抛出异常 |
new | 堆数组分配 |
defer | 延迟执行(仅单行形式) |
struct | 结构体类型定义 |
extern | 外部函数接口(FFI) |
as | 类型转换运算符 |
关键字精简(不再向后兼容):
- 已移除
len关键字,改用属性访问arr.len获取数组长度- 已移除
match/case/default关键字,模式匹配改用if-else链实现defer仅支持单行形式defer statement,不再支持大括号块- 内存管理关键字
delete和scope已移除(仅保留new)- 堆内存只通过
new分配,生命周期自动绑定当前函数栈帧,函数返回时自动释放- 运行时函数返回的字符串同样由函数帧自动管理(帧内跟踪 + 帧末释放,详见第 12 章「字符串内存管理」)
- 返回堆数组需用内置
move()函数转移所有权- C 风格
for (...)循环已移除,统一用for i in range(start, end)代替(不再向后兼容)finally关键字已移除,清理逻辑统一用defer表达;try必须带至少一个catch
6. 运算符
算术运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 加法 | + | a + b |
| 减法 | - | a - b |
| 乘法 | * | a * b |
| 除法 | / | a / b |
| 幂运算 | ** | a ** 2 |
| 取模 | % | a % b |
比较运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 等于 | == | a == b |
| 不等于 | != | a != b |
| 小于 | < | a < b |
| 大于 | > | a > b |
| 小于等于 | <= | a <= b |
| 大于等于 | >= | a >= b |
逻辑运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 逻辑与 | && | a && b |
| 逻辑或 | || | a || b |
位运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 位与 | & | a & b |
| 位或 | | | a | b |
| 位异或 | ^ | a ^ b |
| 位非(前缀) | ~ | ~a |
| 左移 | << | a << 2 |
| 右移 | >> | a >> 2 |
前缀运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 逻辑非 | ! | !flag |
| 取负 | - | -value |
| 取正(空操作) | + | +value |
类型转换
| 运算符 | Token | 示例 |
|---|---|---|
| 类型转换 | as | x as float、f as int |
复合赋值运算符
| 运算符 | Token | 示例 |
|---|---|---|
| 赋值 | = | x = 5 |
| 加赋值 | += | x += 5 |
| 减赋值 | -= | x -= 5 |
| 乘赋值 | *= | x *= 2 |
| 除赋值 | /= | x /= 3 |
复合赋值精简(不再向后兼容):
- 已移除
%=、**=、&=、|=、^=、<<=、>>=七个复合赋值运算符,统一用完整表达式替代:x %= y→x = x % yx **= y→x = x ** yx &= y→x = x & yx |= y→x = x | yx ^= y→x = x ^ yx <<= y→x = x << yx >>= y→x = x >> y- 使用被移除的运算符会得到词法错误提示:
Compound assignment operator '%=' has been removed(附带替换建议)
成员访问和索引
| 运算符 | Token | 示例 |
|---|---|---|
| 成员访问 | . | p.x、rect.width、math.sin(1.0)(标准库命名空间调用) |
| 索引 | [...] | arr[0]、arr[i][j] |
| 错误传播(后置) | ? | let x = bar()?(详见第 15 章) |
| 箭头(返回类型) | -> | fn foo() -> int |
7. 运算符优先级(从低到高)
| 优先级 | 值 | 运算符 |
|---|---|---|
| 1 | P_LOWEST | 默认 |
| 2 | P_OR | || |
| 3 | P_AND | && |
| 4 | P_BIT_OR | | |
| 5 | P_BIT_XOR | ^ |
| 6 | P_BIT_AND | & |
| 7 | P_EQUALS | ==、!= |
| 8 | P_LESSGREATER | <、>、<=、>= |
| 9 | P_SHIFT | <<、>> |
| 10 | P_SUM | +、- |
| 11 | P_PRODUCT | *、/、% |
| 12 | P_EXPONENT | **(幂运算) |
| 13 | P_PREFIX | -、!、~ |
| 14 | P_AS | as(类型转换) |
| 15 | P_CALL | ( 函数调用 |
| 16 | P_INDEX | [ 索引 |
| 17 | P_DOT | . 成员访问 |
8. 控制流
if / else
if condition {
// 语句体
}
if condition {
// 语句体
} else {
// 备选语句
}while 循环
while condition {
// 语句体
break // 退出循环
continue // 跳过本次迭代
}range() 整数范围循环
for i in range(0, 10) {
// 语句体(i 从 0 到 9)
}
// 单参数形式:range(end) 等价于 range(0, end)
for j in range(5) {
// 语句体(j 从 0 到 4)
}range(end):生成[0, end)的整数序列(等价于原 C 风格for (var i: int = 0; i < end; i += 1))range(start, end):生成[start, end)的整数序列- range 参数在循环开始前求值一次(Python 语义)
- 循环变量默认为
let常量;可用for var i in range(...)声明为可变变量 - 已移除 C 风格
for (...)循环,统一用for-in+range()
for-in 循环(遍历数组)
// 简单遍历
for item in array {
// 语句体
}
// 带索引遍历
for i, item in array {
// 语句体
}
// 带 let/var 限定词
for let item in array { ... }
for var item in array { ... }条件分支链(if / else if / else)
原 match 模式匹配已移除,改用 if-else 链实现同样的分支逻辑:
if x == 1 {
// 语句体
} else if x == 2 || x == 3 {
// 多条件分支
} else {
// 兜底分支
}else if可连续使用,构成多路分支- 末尾
else相当于原default分支(可选)
defer 延迟执行
defer print("simple cleanup")- 仅支持单行形式:
defer statement(不再支持defer { ... }块) - 在包围作用域(函数/块)退出时执行
- 多个
defer按 LIFO(后进先出)顺序执行 - 在函数帧堆内存释放之前执行
- 异常退出:
try块内的 throw 被捕获时,先执行 try 块内已注册的 defer,再进入 catch;未捕获的 throw 会执行函数内所有待执行的 defer 后退出 - defer 体内控制流的定义行为(不产生编译期崩溃/无效 IR):
return:以该返回值直接终止函数(跳过剩余 defer;帧堆内存清理仍会执行)throw:处于异常清理期间时被忽略(错误消息以最后一次 throw 为准)try/catch:允许,作为独立捕获单元执行
break / continue
break:退出最内层循环(while/for-in)continue:跳到最内层循环的下一次迭代
return
return // 返回值,或从 void 函数返回
return expr // 返回表达式值9. 变量声明
// 不可变常量
let pi: float = 3.14159
// 可变变量
var count: int = 0
var temperature: float = 25.5
// 赋值
x = 10
x += 5
arr[0] = 100 // 数组索引赋值
p.x = 30.5 // 结构体成员赋值10. 函数声明
// 基本函数
fn add(a: int, b: int) -> int {
return a + b
}
// void 函数(无需返回类型)
fn greet(name: str) {
print("Hello,", name)
}
// 自动返回类型推断(省略 ->type)
fn factorial(n: int) {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
// 函数调用必须写在函数内(如 main)
fn main() {
let r: int = add(5, 3)
print(r) // 8
}11. 结构体
// 定义结构体
struct Point {
x: float
y: float
}
struct Rect {
width: int
height: int
}
// 创建结构体实例
var p: Point = Point { x: 10.5, y: 20.5 }
// 成员访问和赋值
print(p.x)
p.x = 30.5
// 表达式字段
var r: Rect = Rect { width: 100, height: 200 }
var area: int = r.width * r.height结构体字段可以是内置类型或其他结构体类型。结构体名必须以大写字母开头。
12. 数组
数组变量通过 T[] 类型注解声明(T 为元素类型,[] 表示数组;多维数组用 T[][]), 不再使用标量类型(如 int)注解数组。
静态数组(数组字面量)
fn main() {
let arr: int[] = [1, 2, 3, 4, 5]
let floats: float[] = [1.1, 2.2, 3.3]
// 索引
print(arr[0])
arr[2] = 30
}字面量 +
T[]注解 = 动态数组:var a: int[] = [1, 2, 3]生成的是真正的动态数组 (带头元数据的堆分配,等价new int[3]),具备push/自动扩容/越界检查/帧末释放等 完整动态数组语义(2026-08-01 批次修复,此前字面量是静态只读全局数组,push 不可靠)。 无数组注解时(如let a = [1, 2, 3])字面量保持静态语义。 多维限制:int[][]类型 + 数组字面量暂不支持转动态数组,回退静态语义。
List[T](动态数组语法糖)
List[T] 在解析期直接规范化为 T[],是 T[] 的别名语法:
var a: List[int] = [1, 2, 3] // 等价 var a: int[] = [1, 2, 3]
a.push(4) // 自动扩容
print(a.len) // 4
var b: List[str] = ["x", "y"] // List[str] -> str[]
var c: List[List[int]] // 嵌套:List[List[int]] -> int[][]动态数组(堆分配)
// 一维数组
var arr: int[] = new int[10]
arr[0] = 100
arr[5] = 500
// 函数返回时自动释放,无需 delete
// 多维数组
var matrix: int[][] = new int[3][4]
matrix[0][0] = 100
// 数组长度属性 .len
print("长度:", matrix.len) // 返回元素个数
// 自动扩容:超出容量的写入会自动扩容
var dynamic: int[] = new int[3]
dynamic[5] = 60 // 自动扩容,length 变为 6
print("扩容后长度:", dynamic.len) // 输出 6扩容的内存安全语义
自动扩容采用不搬移旧内存的策略,保证外部指针在函数执行期间不会悬垂:
- 扩容时分配新数据块,把旧数据(含数组头元数据)整体拷贝到新块,数组变量更新为新块指针
- 旧数据块不立即释放,而是进入当前函数帧的"延迟释放池",函数返回/未捕获异常退出时统一释放
- 因此,任何经 FFI 逃逸到外部的内部指针在函数执行期间不会悬垂、不会 use-after-free(内存安全),
但通过旧指针读取到的是扩容前的旧快照,与 arr[i] 读取的新数据可能不一致
- 借用参数(函数数组参数)禁止自动扩容:函数内对借用数组越界写入直接触发边界错误,不会搬移调用方的内存
数组作为函数参数/返回值
// 数组参数:按元素指针传递(动态数组)
fn sum(arr: int[], n: int) -> int {
var total: int = 0
for i in range(0, n) {
total = total + arr[i]
}
return total
}
let data: int[] = new int[5]
data[0] = 1
data[1] = 2
data[2] = 3
data[3] = 4
data[4] = 5
print("sum =", sum(data, 5)) // 15注意:数组参数按值传递(指针),函数内越界写入会触发边界错误,不会自动扩容。 调用方仍持有该数组的所有权,函数返回时由调用方释放。
内存管理
Rex 采用函数帧自动内存管理:new 分配的内存生命周期绑定当前函数栈帧,函数返回时自动释放。 运行时函数返回的字符串同样采用帧级管理(见下方「字符串内存管理」)。
// ✅ 推荐:new 分配,函数返回时自动释放
var arr: int[] = new int[10]
// 无需 delete,无需 scope
// 重新赋值:旧内存自动释放
arr = new int[20]
arr[0] = 100
// 可作为参数传给子函数(所有权仍在当前函数)
fn use(a: int[], n: int) -> int {
return a[n - 1]
}
print("last =", use(arr, 1))
// 返回堆数组:必须用 move() 转移所有权给调用方
fn make_array() -> int[] {
let result: int[] = new int[3]
result[0] = 42
return move(result) // 所有权转移,函数返回时不会释放
}
var got: int[] = make_array()
print("got[0] =", got[0]) // got 现在拥有该内存,函数返回时释放规则:
- 堆内存(
new数组)生命周期绑定当前函数栈帧,函数返回时自动释放 - 堆内存可以作为参数传递给子函数(调用方仍持有所有权)
- 堆内存禁止直接作为返回值(否则函数返回时会被释放,产生悬垂指针)
- 使用内置
move(arr)可将堆数组所有权转移给调用方,作为返回值 move()只能用于当前函数通过new分配的堆数组变量,不能用于函数参数
字符串内存管理
运行时函数返回的字符串与 new 数组一样采用函数帧自动内存管理(帧内跟踪 + 帧末释放):
fn make_greeting() -> str {
var s: str = string.concat("Hello, ", "Rex!") // 分配字符串,登记到当前帧
s = string.concat(s, " ") // 每次调用分配新字符串
s = string.concat(s, "world")
return s // 返回值所有权移交调用方帧
}
fn main() {
let g: str = make_greeting() // g 由 main 的帧接管,main 返回时释放
print(g)
// 循环追加模式:字符串不可变,每次 concat 都是全量拷贝(O(n²))
// 但内存不会泄漏——所有旧字符串在函数返回时统一释放
var acc: str = ""
var i: int = 0
while i < 100 {
acc = string.concat(acc, "x")
i = i + 1
}
print(string.len(acc))
}规则:
- 产生字符串的运行时函数(
string.concat/upper/lower/substr/replace/repeat/from_int/from_float/dup,以及io/file/os/path/time中返回字符串的函数)每次调用都会strdup分配新内存 - 这些字符串的生命周期绑定当前函数栈帧:函数返回/未捕获异常退出时自动释放,无内存泄漏
str函数返回值(fn f() -> str)的所有权自动转移给调用方帧,由调用方负责释放,可安全跨函数返回- 字符串字面量是编译期全局常量,不参与帧管理,永不释放
string.find_char/string.find_sub返回指向源字符串内部的指针,不分配新内存,不参与帧管理- 内部指针跨函数返回是安全的:返回时若发现返回值不是本帧池内的字符串(内部指针/字面量/借入指针),运行时会自动
strdup复制一份交给调用方帧,源字符串仍随本帧释放 - 函数内内部指针不悬垂:
find_char/find_sub返回的内部指针在本函数执行期间始终有效——字符串在函数帧末才统一释放(concat/replace等产生新串时不立即释放旧缓冲,与数组"扩容延迟释放池"语义一致)。因此let p = string.find_char(s, ...); s = string.concat(s, "...")之后p不会悬垂(但读取到的是旧快照,与s当前值可能不一致) string.copy/string.cat写入调用方提供的缓冲区,返回的是缓冲区指针,不参与帧管理- 与
new数组不同,字符串没有move()——返回字符串时所有权自动转移
已知限制:字符串不可变,
s = string.concat(s, "x")在循环中是严格的 O(n²) 全量拷贝。 不做"就地追加"优化是因为它会破坏别名安全(let a = s后若s被 realloc,a会悬垂)。 实战建议:需要循环拼接大字符串时,使用string.builder_*StringBuilder (追加零分配、tostr 一次性 O(n),见库语法文档),而非拆分逻辑。编译期警告(帧池陷阱治理):编译器会检测循环(
while/for-in/for range)体内s = string.concat(s, ...)的自我拼接模式并发出警告 (String concat in a loop reassigns 's' from itself: ...), 提示改用 StringBuilder。该警告不影响编译与运行,仅用于早期拦截 O(n²) + 帧内存累积陷阱。
中途释放(= [])
大数组不再需要时,可用空数组字面量立即释放旧内存(变为 length 为 0 的空数组):
// 分配 1GB 缓冲区(下划线数字可读性更好)
var buffer: int[] = new int[1_000_000_000]
buffer[0] = 100
// ... 处理前半部分 ...
// ⚡ 前半部分处理完,立即释放旧内存,削平内存峰值
buffer = [] // 等价于:释放旧内存 + buffer = new int[0]
// 后续逻辑内存占用极低
print("length =", buffer.len) // 0
// 声明即空数组
var empty: int[] = []buffer = []等价于buffer = new T[0]:先释放旧堆内存,再变为合法的空动态数组- 常用于长函数/服务器主循环中削平内存峰值(无需拆分子函数)
- 释放后的空数组可安全读取
.len,索引访问会触发边界错误
安全机制:
- 边界检查:越界访问触发运行时错误
- 空指针检测:释放/转移后的指针访问触发运行时错误
- 自动释放:
new分配的数组与运行时返回的字符串都在函数返回时自动释放,无内存泄漏- 扩容稳定:自动扩容不移动旧内存块(延迟到函数帧末释放),FFI 持有的内部指针在函数内不会悬垂
禁止变量别名(隐式移动)
堆数组始终只有一个所有者。把堆数组变量赋给另一个变量时,所有权自动转移(隐式移动),源变量立即失效(置 null):
var a: int[] = new int[3]
a[0] = 10
let b: int[] = a // 隐式移动:a 的所有权转移给 b,a 置 null
print("b[0] =", b[0]) // 10
// a[0] // ⚠️ a 已失效,访问触发空指针运行时错误
var x: int[] = new int[2]
var y: int[] = new int[2]
y = x // 隐式移动:x 置 null,y 旧内存自动释放let b = a/b = a(右侧是堆数组所有者)→ 隐式移动,源变量失效,杜绝双重释放- 被移动的变量再使用会触发运行时空指针错误
- 借用参数不能别名:
let c = arr(arr是函数参数,所有权在调用方)是编译错误 - 已移动的变量不能再次被别名(编译错误)
静态数组(字面量
[1, 2, 3])是不可变全局常量,可自由共享,不适用上述规则。
复制数组(copy())
需要多份独立数据(如 b/c/d 各自持有 a 的副本)时,用内置 copy(arr) 深拷贝。 copy() 是借用语义:数组以参数身份传入,所有权不动,源数组保持有效, 可以重复调用 copy(a) 生成任意多个副本,互不影响:
var a: int[] = new int[3]
a[0] = 10
a[1] = 20
a[2] = 30
let b: int[] = copy(a) // 深拷贝:b 持有独立内存,a 仍有效
let c: int[] = copy(a) // 再拷一份
let d: int[] = copy(a)
b[0] = 99 // 改副本不影响源与其他副本
print("a[0] =", a[0]) // 10(a 未失效)
print("c[0] =", c[0]) // 10
print("d[2] =", d[2]) // 30copy()只接受堆数组变量/参数(int[]/float[]/bool[],含多维;v1 暂不支持str[]等元素)- 返回的新数组所有权属于当前函数,随函数帧自动释放;生命周期与
new数组一致 - 需要返回副本给调用方时,与
new数组一致:let tmp = copy(a)后return move(tmp) - 只读共享数据不需要副本:直接把数组作为函数参数传递(借用)即可,零拷贝
类型匹配检查
编译器会检查类型注解与值的匹配,以下写法会产生编译错误:
let a: int = [1, 2, 3] // ❌ 错误:int 是标量,不能注解数组(应使用 int[])
let b: int = new int[5] // ❌ 错误:同上
let c: int[] = 5 // ❌ 错误:int[] 是数组,不能赋标量值13. Map 字典
Map[str, V] 是语言级字典(哈希表),LLVM 层表示为 i64 句柄。 v1 仅支持字符串键,值类型支持 int / float / str 三种;使用其他键/值类型会得到编译错误。
// 声明与初始化
var scores: Map[str, int] = {}
var config: Map[str, str] = { "name": "Rex", "mode": "prod" }
// 写入 / 读取(命名空间调用,见库语法文档「map 函数」)
map.set(scores, "alice", 100)
map.set(scores, "bob", 85)
print(map.get(scores, "alice")) // 100
// 查询 / 删除 / 长度
if map.has(scores, "bob") { ... }
map.delete(scores, "bob")
print(map.len(scores)) // 1
map.keys(scores) // 所有键(换行分隔的字符串)
map.clear(scores) // 清空
map.free(scores) // 提前释放(可省略,函数返回时自动释放)方法糖:
scores.set(k, v)/scores.get(k)/scores.has(k)/scores.delete(k)/scores.len()/scores.keys()/scores.clear()/scores.free()等价于上方命名空间调用(见第 21 章)。
规则:
- 生命周期绑定函数栈帧:Map 句柄随声明变量登记到当前函数帧,函数返回/未捕获异常退出时自动释放;
map.free(m)可提前释放(释放后句柄置 0,再次使用按空 Map 处理,不崩溃) - 单所有者(隐式移动):与堆数组一致,
let b = m隐式移动所有权,源变量m失效(句柄置 0) - 返回值自动移交:
fn f() -> Map[str, int]返回的 Map 所有权自动转移给调用方帧 map.get缺键返回默认值(int→0、float→0.0、str→空串),用map.has先判断- 函数参数:Map 参数按 i64 句柄传递;参数是借用值,不参与帧跟踪(调用方负责释放)
- 字符串值安全:
map.set拷贝存储(Map 持有所有权),map.get返回帧池副本(调用方帧负责释放),无悬垂风险
值类型约束:
Map[str, int]/Map[str, float]/Map[str, str]三种组合可用; 使用Map[int, ...](非字符串键)或其他值类型会得到编译错误。 完整map.*函数签名见库语法文档。
14. Set 集合
Set[T] 是语言级集合(哈希表,元素去重),LLVM 层表示为 i64 句柄。 v1 元素类型支持 int / float / str 三种;无集合字面量(避免与 {} Map 字面量歧义), 通过 set.create() 创建。
var s: Set[int] = set.create()
s.add(10) // 方法糖:等价 set.add(s, 10)
s.add(20)
s.add(10) // 重复添加不生效(去重)
print(s.len()) // 2
print(s.has(10)) // true(bool)
var removed: bool = s.remove(10) // true(返回是否原本存在)
s.clear() // 清空
s.free() // 提前释放(可省略,函数返回时自动释放)
// Set[str] / Set[float]
var names: Set[str] = set.create()
names.add("alice")
var fs: Set[float] = set.create()
fs.add(1.5)规则:
- 生命周期绑定函数栈帧:Set 句柄随声明变量登记到当前函数帧,函数返回/未捕获异常退出时自动释放;
set.free(s)可提前释放(释放后句柄置 0,再次使用按空 Set 处理,不崩溃) - 单所有者(隐式移动):与 Map/堆数组一致,
let b = s隐式移动所有权,源变量s失效(句柄置 0) - 返回值自动移交:
fn f() -> Set[int]返回的 Set 所有权自动转移给调用方帧 - 方法糖:
s.add(x)/s.remove(x)/s.has(x)/s.len()/s.clear()/s.free()
等价于 set.add(s, x) 等命名空间调用
- 字符串元素安全:
add拷贝存储(Set 持有所有权),has/remove按字符串内容比较,无悬垂风险
元素类型约束:
Set[int]/Set[float]/Set[str]三种组合可用; 其他元素类型会得到编译错误。 完整set.*函数签名见库语法文档。
15. 错误处理
// throw -- 抛出带有字符串消息的异常
throw "Something went wrong!"
// try-catch
try {
throw "error message"
} catch err {
print("Caught:", err)
}
// try-catch(无变量)
try {
throw "error"
} catch {
print("Caught an error")
}try必须带至少一个catch子句,否则是编译错误finally已移除(不再向后兼容),函数级清理统一用defer表达:
fn read_file() {
defer print("cleanup") // 函数返回/未捕获异常退出时执行
try {
throw "error"
} catch {
print("handled")
}
}异常语义(Bug 3 修复后):
- 被捕获的 throw 先执行 try 块内的 defer:
throw分支到 catch 前,会按 LIFO 执行 try 块内已注册的 defer(块正在退出);外层作用域(函数级)的 defer 不执行,函数继续运行 - 异常跨函数边界:
try块内调用函数时,若被调用函数抛出未捕获异常(它会先打印Error: ...并清理自身帧),调用方会检测到错误标志并跳转到本try的 catch —— 异常可以被跨调用捕获 - 调用点检查仅限 try 块内:try 块之外的调用不检查错误标志,被调用函数的未捕获异常不会向更外层传播
- catch 变量:错误消息最长 1023 字节,超长部分截断;catch 变量指向本 catch 块的独立副本,嵌套的 throw 不会互相覆盖(可重入)
错误传播(? 运算符 + Optional 返回类型)
深调用链的错误处理可用 ? 自动传播,避免层层手动 try-catch 包裹:
// Optional 返回类型:函数可能失败
fn read_score(name: str) -> int? {
if name == "" {
throw "empty name"
}
return name.len * 10
}
fn compute_total() -> int? {
let a: int = read_score("alice")? // 失败自动传播:本函数立即返回默认值 0
let b: int = read_score("bob")?
return a + b
}
fn main() -> int {
try {
let total: int = compute_total()?
print("total =", total)
} catch err {
print("caught:", err) // 传播的错误被 catch 捕获
}
return 0
}语义:
T?返回类型:表示函数可能失败。底层 LLVM 仍返回T(错误通过全局错误标志传播),T?只是类型层面的声明expr?后置运算符:仅可用于函数调用(f()?)。调用后检查错误标志:- 标志未置位(正常)→ 结果正常使用
- 标志置位(被调用函数抛出未捕获异常)→ 从当前函数传播:按 LIFO 执行所有 defer、清理帧内存、弹出回溯帧,以当前函数返回类型的默认值返回(int→0、float→0.0、str/ptr→null、void→无值)
- 错误标志保留:传播后标志不清零,调用方若用
?会继续向上传播;若调用方处于try块内,调用点检查会跳转到catch - 与 try/catch 的关系:
?在 try 块内使用时,错误由最近的catch捕获(不传播);try 外使用时自动传播。二者互补,按需选用 - 误用报错:对非函数调用使用
?(如x?、1 + 2?)是编译错误 - 对
?语义的建议:需要返回值区分"成功但为默认值"与"失败"时,用try-catch而非?;?适合"失败即提前退出"的快速路径
Backtrace(栈回溯):
Rex 在运行时自动记录函数调用链(函数名 + 当前语句行号)。当程序因未捕获异常或运行时错误终止时,在错误信息前打印栈回溯(Traceback),从 main 入口逐层定位到出错位置:
Traceback (most recent call last):
at main (line 9)
at inner_throw (line 3)
Error: inner error- 触发场景:未被
try-catch捕获的throw异常;数组越界访问([Rex Runtime Error] Array index out of bounds);空指针访问([Rex Runtime Error] Null pointer access);整数除零([Rex Runtime Error] Integer division by zero,2026-08-01 新增,防止 SIGFPE 崩溃;浮点除零为 IEEE 合法操作返回 inf,不触发) - 行号语义:最内层(出错)帧显示错误语句所在行号;外层帧显示调用出错子函数的调用点行号
- 不打印的场景:被
try-catch捕获的异常不打印回溯;但跨函数捕获时,被调用函数内未捕获的throw仍会先打印自身回溯与Error:(与上方"异常跨函数边界"语义一致) - 栈深度上限:回溯栈最多记录 256 帧,超深递归时超出部分被截断(不影响 push/pop 平衡)
- -O3 快速模式:
-O 3下纯计算函数(不含try/throw/?)省略回溯帧,
该类函数内未捕获异常/运行时错误的回溯退化为顶层链(调用方帧仍完整); 默认 -O 2 及以下与调试模式(DAP)全部保留。详见工具文档「生产级性能优化」。
16. 模块系统 / 导入
// 选择性导入用户自定义模块
import add, subtract from "math.rex"
import multiply from "math.rex"模块从 .rex 源文件编译,其导出的函数在导入作用域中可用。
包管理:
rex pkg install <目录>(源码运行等价python main.py pkg install <目录>) 可将含rex.toml的包安装到~/.rex/packages/,安装后可用包路径导入:import "包名/模块"(详见工具文档「包管理」)。
标准库无需导入:
math、io、file、os、path、string、random、collections、datetime、time、thread等标准库命名空间内置在编译器中,直接调用即可(如math.sin(1.0)、file.exists("a.txt")),不需要import。
17. FFI(外部函数接口)
// 带库名的 extern
extern "kernel32" fn Sleep(ms: int) -> void
// 不带显式库名(搜索默认库)
extern fn clock() -> int编译器生成 LLVM 函数声明,在链接时通过 C ABI 解析。
标准库函数内置:运行时库函数(
math.sin、io.write等)已内置为命名空间函数,直接调用即可,无需extern声明。extern仅用于调用真实 C 库函数,且必须显式指定返回类型(省略时默认void)。
FFI 返回值空指针判断:
ptr/str类型支持与null做==/!=比较(C 指针相等语义), 用于判断 FFI 函数返回值是否为 NULL:rexlet node: ptr = cJSON_Parse(text) if node == null { print("解析失败") }
FFI 返回字符串的内存语义:
extern函数返回的str指向 C 库分配的内存, 不参与 Rex 帧字符串池管理(不会被自动释放)。 需要保有时用string.dup()拷贝为帧管理字符串,并调用 C 库自身的释放函数 (如cJSON_Delete、或 shim 提供的free包装)释放原始内存,避免泄漏:rexlet raw: str = cJSON_Print(node) // cJSON 用 malloc 分配 let s: str = string.dup(raw) // 拷贝为帧管理字符串 rex_json_free(raw) // 释放 C 库内存 return s
FFI 指针类型限制:Rex 的
ptr是裸i8*,无法在语言内解引用 C 结构体字段。 需要访问 C 结构体内部数据时,应在 C 侧编写薄 shim(辅助函数)暴露只读访问器, 或使用 C 库提供的 getter 函数(如 cJSON 的cJSON_GetObjectItem/cJSON_GetStringValue), 或使用mem命名空间按偏移读写裸内存(见库语法文档)。
18. 字符串转义序列
基本转义
| 转义 | 含义 |
|---|---|
\\ | 反斜杠 |
\' | 单引号 |
\" | 双引号 |
\a | 响铃 |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 制表 |
\v | 垂直制表 |
\0 | 空字符 |
字符串统一使用双引号;单引号
'在双引号字符串内无需转义即可使用,\'转义仍然有效(等价于'),\"用于输出双引号。
高级转义
| 类型 | 格式 | 示例 |
|---|---|---|
| 八进制 | \ooo(1-3位八进制) | "\101" = A |
| 十六进制 | \xNN(2位十六进制) | "\x41" = A |
| Unicode 16位 | \uNNNN(4位十六进制) | "\u4e2d" = 中 |
| Unicode 32位 | \UNNNNNNNN(8位十六进制) | "\U0001F600" = emoji |
| Unicode 名称 | \N{NAME} | "\N{LATIN CAPITAL LETTER A}" |
字符串插值(2026-08-01 新增)
字符串支持 {表达式} 插值(编译期展开为 string.concat 链), 表达式可为标识符、算术、函数调用等,支持 str/int/float/bool (后三者自动转字符串):
let name: str = "Rex"
let age: int = 3
let msg: str = "Hello, {name}! You are {age} years old." // "Hello, Rex! You are 3 years old."
print("score={98.5}, ok={true}") // "score=98.5, ok=true"
print("a+b={5 + 7}") // "a+b=12"字面花括号:用
{{/}}转义(Python f-string 风格,无歧义):"{{literal}}"={literal}。JSON 等含花括号的文本同理:json_parse("{{\"k\": 1}}")。 插值表达式内暂不支持嵌套字符串字面量(如"{foo("x")}")。
19. 内建函数
| 函数/关键字 | 签名 | 描述 |
|---|---|---|
print(...) | print(args...) | 打印到标准输出(Python风格,可变参数,自动空格分隔) |
assert(cond, msg) | assert(int/bool, str) | 断言:条件为假时打印 [Rex Runtime Error] Assertion failed: msg + 栈回溯并以退出码 1 终止(测试断言) |
arr.len | array.len -> int | 数组长度属性(返回元素个数) |
range(start, end) | range(int, int) -> 整数序列 | 生成整数范围序列,仅用于 for i in range(start, end) 循环(也支持单参数 range(end)) |
new T[N] | 表达式 | 在堆上分配类型 T、N 个元素的数组(生命周期绑定当前函数栈帧,返回时自动释放) |
move(arr) | move(array) -> T[] | 将堆数组所有权转移给调用方(返回堆数组的唯一方式),转移后原变量不可再用 |
copy(arr) | copy(array) -> T[] | 深拷贝堆数组,返回一份独立的新数组;借用语义——源数组保持有效,可多次 copy 同一数组给多个变量。v1 支持 int[]/float[]/bool[](含多维),str[] 等元素暂不支持 |
true / false | 常量 | 布尔字面量 |
null | 常量 | 空指针 |
as | expr as Type | 数值类型间转换 |
20. 泛型(Generics)
Rex 支持泛型函数与泛型结构体,采用编译期单态化(monomorphization): 泛型定义作为模板保存,每次以具体类型实例化时生成独立的 LLVM 函数/结构体, 相同 (名称, 类型参数) 组合只编译一次(缓存去重)。类型安全、零运行时开销。
20.1 泛型函数
// 类型参数写在函数名后的 [ ] 中
fn identity[T](x: T) -> T {
return x
}
fn max_of[T](a: T, b: T) -> T {
if a > b {
return a
}
return b
}
// 泛型 + 数组类型参数
fn sum[T](arr: T[], n: int) -> T {
var total: T = arr[0]
for i in range(1, n) {
total = total + arr[i]
}
return total
}
fn main() {
// 显式类型参数
let a: int = identity[int](42)
// 类型推断(从实参推导 T)
let b: float = identity(3.14)
print(max_of(3, 7)) // 7(T = int)
print(max_of(2.5, 1.5)) // 2.5(T = float)
}- 调用支持两种形式:
- 显式类型参数:
identityint(解析器通过[ ... ]后紧跟(区分于数组索引) - 类型推断:
identity(x)从实参的 LLVM 类型推导类型参数;同一类型参数出现多处时须推断一致 - 泛型函数必须显式声明返回类型(
-> type):返回类型无法从类型参数推断时编译器会报错 - 泛型函数体内可声明类型参数类型的局部变量(
var total: T)、分配new T[n]、
实例化 Box[T] 等,编译期自动替换为具体类型
- 泛型函数支持递归:函数体编译前实例已注册,递归调用自动复用同一实例
20.2 泛型结构体
struct Box[T] {
value: T
}
struct Pair[A, B] {
first: A
second: B
}
struct Wrapper[T] {
inner: Box[T] // 嵌套泛型
}
fn get_value[T](b: Box[T]) -> T {
return b.value
}
fn main() {
var bi: Box[int] = Box[int] { value: 42 }
print(bi.value) // 42
bi.value = 100
var p: Pair[int, float] = Pair[int, float] { first: 7, second: 1.5 }
print(p.first, p.second)
var w: Wrapper[int] = Wrapper[int] { inner: Box[int] { value: 9 } }
print(w.inner.value) // 9(链式成员访问)
print(get_value(bi)) // 泛型函数接收泛型结构体(按值传递)
}- 结构体字面量必须显式给出类型参数:
Box[int] { value: 5 }(解析器通过]后紧跟{识别) - 类型注解/参数/返回值均支持
Box[int]、Box[int][]、Pair[int, float]、嵌套Box[Box[int]]等形式 - 泛型结构体实例可像普通结构体一样作为字段、成员访问、赋值、函数参数使用
20.3 规则与限制
- 类型参数名是标识符(如
T、A、B),作用于所属函数/结构体定义内 - 单态化:
identity[int]与identity[float]生成两个独立 LLVM 函数,互不影响 - 泛型函数要求显式返回类型;
new的元素类型支持类型参数(new T[n]),
但不支持结构体元素(new Box[int][n] 会报错)
- 支持类型参数约束(bound)(见 20.4 节);不支持运行时泛型、泛型 extern/FFI
- 结构体按值传参时,成员赋值只修改函数内的副本(C 风格值语义),不影响调用方
20.4 泛型约束
类型参数可声明约束,实例化时校验具体类型是否满足(显式类型参数与类型推断两条路径都校验):
fn add[T: int|float](a: T, b: T) -> T { // T 只能是 int 或 float
return a + b
}
struct Box[T: int|float] { // 约束同样适用于泛型结构体
value: T
}
add[int](3, 4) // OK:int 满足约束
add(2.5, 3.5) // OK:类型推断到 float
add("a", "b") // 编译错误:str 不满足约束 'T: int|float'- 语法:
T: int|float(|分隔多个允许类型);多个参数可各自带约束
(fn fT: int, U: str)
- 约束匹配比较类型基名:
T: Pair允许Pair[int]/Pair[float]等泛型实例 - 无约束的泛型参数行为不变(任意类型)
- 校验时机:泛型函数/结构体实例化时,拒绝时给出明确编译错误并列出允许类型
- 测试:
tests/generic_constraint_test.rex(通过)、
tests/generic_constraint_fail_test.rex(编译期拒绝)
21. 对象方法调用(Map / Set / 数组)
Map、Set 与动态数组支持方法语法(等价于既有命名空间调用):
var m: Map[str, int] = {}
m.set("k", 42) // 等价 map.set(m, "k", 42)
let v: int = m.get("k") // 等价 map.get(m, "k")
m.has("k") / m.delete("k") / m.len() / m.keys() / m.clear() / m.free()
var s: Set[int] = set.create()
s.add(10) // 等价 set.add(s, 10)
s.remove(10) / s.has(10) / s.len() / s.clear() / s.free()
var arr: int[] = new int[2]
arr.push(30) // 追加元素(自动扩容),等价 arr[arr.len] = 30arr.len属性见第 12 章;arr.push(x)语法糖为arr[arr.len] = x(复用自动扩容逻辑)
22. 多返回值
fn minmax(a: int, b: int) -> (int, int) {
if a < b { return a, b }
return b, a
}
let (mn, mx) = minmax(3, 7) // 解构:mn=3, mx=7
let (i, f, b) = mixed() // 支持 (int, float, bool)- 语法:
-> (t1, t2, ...)声明;return a, b返回;let (a, b) = f()解构。 - LLVM 层:返回类型为字面结构体
{i64, double, ...},调用方extractvalue取分量。 - v1 限制:仅支持标量分量(int/float/bool);不支持 str/ptr/数组/Map 分量
(多返回 str 与字符串帧池移交不兼容,编译报错);多返回函数不支持 ? 错误传播。
- 测试:
tests/multi_return_test.rex。
23. 整数除零检查
- 整数
x / y与x /= y在除数为 0 时触发
[Rex Runtime Error] Integer division by zero(含栈回溯,退出码 1), 防止 SIGFPE 崩溃;浮点除零为 IEEE 合法操作(返回 inf),不检查。
- 测试:
tests/div_zero_test.rex(通过)、tests/div_zero_fail_test.rex(期望错误)。
24. 完整代码示例
斐波那契数列
fn fib(n: int) -> int {
if n <= 1 {
return n
}
return fib(n - 1) + fib(n - 2)
}
fn main() {
let start = time.now()
for i in range(0, 35) {
print("fib(", i, ") =", fib(i))
}
print("time:", time.elapsed(start), "ms")
}运算符测试
fn main() {
var a: int = 10
var b: int = 3
print("a + b =", a + b)
print("a - b =", a - b)
print("a * b =", a * b)
print("a / b =", a / b)
print("a % b =", a % b)
print("a ** 2 =", a ** 2)
a += 1
print("a += 1 =", a)
a -= 1
print("a -= 1 =", a)
a += 5
print("a += 5 =", a)
}结构体使用
struct Point {
x: float
y: float
}
fn distance(p: Point) -> float {
return math.sqrt(p.x ** 2 + p.y ** 2)
}
fn main() {
var p: Point = Point { x: 3.0, y: 4.0 }
print("distance =", distance(p))
}错误处理
fn divide(a: int, b: int) -> int {
if b == 0 {
throw "Division by zero"
}
return a / b
}
fn main() {
defer print("Done") // 替代 finally:函数退出时始终执行
try {
print(divide(10, 0))
} catch err {
print("Error:", err)
}
}条件分支链(if / else if / else)
fn classify(n: int) -> str {
if n == 0 {
return "zero"
} else if n == 1 || n == 2 || n == 3 {
return "small"
} else {
return "large"
}
}defer 延迟执行
fn main() {
defer print("third")
defer print("second")
defer print("first")
print("start")
}
// 输出:start, first, second, third
// defer 常用于确保函数返回前的清理(堆内存由函数帧自动管理,无需手动释放)
fn example() {
var arr: int[] = new int[100]
defer print("cleanup before return")
if some_condition {
return // defer 会自动执行
}
// 其他代码...
}