语法规范
本文件定义 YaoXiang 编程语言的语法规范,包括词法结构、语法规则和运算符优先级。
第一章:词法结构
1.1 源文件
YaoXiang 源文件必须使用 UTF-8 编码。源文件通常以 .yx 为扩展名。
1.2 词法单元分类
| 类别 | 说明 | 示例 |
|---|---|---|
| 标识符 | 以字母或下划线开头 | x, _private, my_var |
| 关键字 | 语言预定义保留词 | Type, pub, use |
| 字面量 | 固定值 | 42, "hello", true |
| 运算符 | 运算符号 | +, -, *, / |
| 分隔符 | 语法分隔符 | (, ), {, }, , |
1.3 关键字
YaoXiang 定义了极少量的关键字:
pub use spawn
ref mut if else
else match while for return
break continue as in unsafe这些关键字在任何上下文中都具有特殊含义,不能用作标识符。
1.4 保留字
YaoXiang 的"保留字"分三层,分别由解析器(parser)和类型检查器(type checker)在不同阶段识别:
1.4.1 字面量保留字
解析器有独立 token 的字面量标识符,不能用作普通标识符:
| 标识符 | 所属类型 | 说明 |
|---|---|---|
Type | — | 元类型关键字 |
true | Bool | 布尔真值 |
false | Bool | 布尔假值 |
void | Void | Void 字面量(Unit 值)。小写 void 是值字面量;大写 Void 是类型名(见 §1.4.3)。 |
1.4.2 构造子表达式
以下构造子在模式匹配和表达式上下文中由解析器识别:
| 构造子 | 所属类型 | 说明 |
|---|---|---|
some(T) | Option | Option 值变体构造 |
ok(T) | Result | Result 成功变体 |
err(E) | Result | Result 错误变体 |
1.4.3 内建类型名
以下类型名由类型检查器预注册,无需导入即可在类型位置使用。解析器将它们视作普通标识符——不是保留字,可以被局部绑定 shadow(不推荐)。
| 类型名 | 逻辑对应 | 说明 |
|---|---|---|
Void | ⊤(真/Unit) | 零字段积类型,恰好一个居留者(void 字面量,见 §1.4.1) |
Never | ⊥(假/空类型) | 零变体和类型,零个居留者。无任何表达式能产生 Never 值。Never <: T 对所有 T 成立(爆炸原理)。 |
Int | — | 有符号整数 |
Float | — | 浮点数 |
Bool | — | 布尔值:true / false |
Char | — | Unicode 字符 |
String | — | 字符串 |
1.5 标识符
标识符以字母或下划线开头,后续字符可以是字母、数字或下划线。标识符大小写敏感。
特殊标识符:
_用作占位符,表示忽略某个值- 以下划线开头的标识符表示私有成员
1.6 字面量
1.6.1 整数
Decimal ::= [0-9][0-9_]*
Octal ::= 0o[0-7][0-7_]*
Hex ::= 0x[0-9a-fA-F][0-9a-fA-F_]*
Binary ::= 0b[01][01_]*1.6.2 浮点数
Float ::= [0-9][0-9_]* '.' [0-9][0-9_]* ([eE][+-]?[0-9][0-9_]*)?1.6.3 字符串
String ::= '"' ([^"\\] | EscapeSequence)* '"'
Escape ::= '\\' ([nrt'"\\] | UnicodeEscape)
Unicode ::= 'u' '{' HexDigit+ '}'1.6.4 集合
List ::= '[' Expr (',' Expr)* ']'
Dict ::= '{' String ':' Expr (',' String ':' Expr)* '}'
Array ::= '[' Expr (',' Expr)* ']' // 目标类型注解为 Array(T, N) 时字面量落定长数组字典字面量要求至少一个键值对:
{}不是空字典——它是空块(值Void,见 §2.9)。空字典请用构造子dict.new():yaoxiangempty = dict.new() // ✅ 空字典 d = { "a": 1 } // ✅ 字典字面量 wrong = {} // ❌ 这不是字典,是空块(Void)判据是内容:
Dict文法要求至少一个String ':' Expr,{}无内容可依据,故取块结构的零形态。非空形态由内容自描述({ "k": v }有键值对 → 字典)——这与f = { 5 }是Int值而非函数同源:类型由内容决定。
Set 无字面量文法、无运行时表示——集合类型规划中,需求出现时照 Dict 模式补全(std.set + HeapValue::Set)。List/Dict 字面量的落点由上下文类型注解决定:裸字面量与
List(T)注解落可增长列表;Array(T, N)注解直接作用于字面量时落定长数组。禁止隐式 List→Array 转换。Array 字面量语义:
- 元素个数必须等于 N,不等则编译期 E1002;空字面量配非零 N 同样拒绝
- 每个元素类型必须与 T 兼容,不符则编译期 E1002
- N 的文法形态:仅整数字面量(可负)或常量名;复合表达式(如
2+1)解析期拒绝- N 为符号常量(函数 const 参数,如
Array(Int, n))时个数校验推迟到精化类型阶段- v1 嵌套数组字面量(
Array(Array(Int,2),2) = [[1,2],[3,4]])编译期拒绝,需逐层显式构造;递归落点留待后续版本
1.6.5 列表推导式
ListComp ::= '[' Expr 'for' Identifier 'in' Expr (',' Expr)* ('if' Expr)? ']'行为收紧(迁移记录):迭代变量文法本就是
'for' Identifier 'in',但旧实现中 pattern 走完整 pratt 解析——'in'注册为中缀运算符后,x会把in items吞成 membership 表达式。修复后非标识符 pattern 直接解析失败,不再像旧实现 fallback 到_(静默吞错)。影响面:此前能解析的[x for (a, b) in pairs]这类写法现在报错——该形态从未有定义行为(变量恒为_),收紧方向正确,无语义迁移成本。
1.6.6 成员检测
Membership ::= Expr 'in' Expr
in是二元关系运算符,返回Bool——命中true、未命中false,不报错。语义切分:[]是断言存在并取值(失败报错),in是询问是否存在(未命中是正常false)。右操作数覆盖:List / Array / Dict(键集) / Tuple / String(子串) / Range(区间)。in是一等霍尔谓词,精化类型阶段作为编译期可证命题的基底。(Set 从右操作数列表除名——Set 无运行时表示,见 §1.6.4)
1.7 注释
// 单行注释
/* 多行注释
可以跨越多行 */1.8 缩进规则
代码必须使用 4 个空格缩进,禁止使用 Tab 字符。这是强制语法规则。
第二章:语法规则
2.1 表达式分类
Expr ::= Literal
| Identifier
| FnCall
| MemberAccess
| IndexAccess
| UnaryOp
| BinaryOp
| TypeCast
| RangeExpr
| ErrorPropagate
| RefExpr
| IfExpr
| MatchExpr
| Block
| Lambda2.2 运算符优先级
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 | () [] . ? | 左到右 |
| 2 | as | 左到右 |
| 3 | 一元前缀 ! - + | 右到左 |
| 4 | * / % | 左到右 |
| 5 | + - | 左到右 |
| 6 | .. | 左到右 |
| 7 | << >> | 左到右 |
| 8 | & | ^ | 左到右 |
| 9 | == != < > <= >= | 左到右 |
| 10 | and or | 左到右 |
| 11 | if...else | 右到左 |
| 12 | = += -= *= /= | 右到左 |
一元前缀运算符(
!-+)紧绑定:只低于调用与成员访问,高于所有二元运算符。因此!a == b≡(!a) == b(Zig 式语义);!是纯一元运算,不参与短路控制流,与and/or关键字(短路)正交(RFC-010 权威定义)。
Range 绑定力:
..绑定力 (6, 7)——左 6 低于加法(7),右 7 吞加法不吞同级..。变更前后对照:
表达式 变更前(级 1,右结合) 变更后((6,7),左结合) x in 1..10x in 1..10(in右操作数级 4,..级 1 吞不掉,实际无法解析)x in (1..10)——区间整体作为in右操作数0..n+2(0..n)+2(右结合陷阱:上界被吃掉,for循环直接 E3004)0..(n+2)——上界是算术表达式a == b..ca == (b..c)(..级 1 <==级 3,天然整体)a == (b..c)——语义不变,..仍高于比较级1..2*3(1..2)*31..(2*3)——上界是算术表达式a..b..ca..(b..c)(右结合链式,无意义 Range 套 Range)(a..b)..c——step 形态(c为步长)净效果:复合上界
for i in 0..n+2从「解析成功但 E3004」变为「直接可用」;x in 1..10从「无法解析」变为「区间检查」;a..b..c从「无意义嵌套」变为「step 分量」。级 6 落在+(级 5)与<<(级 7)之间,数学惯例:区间是紧绑定构造,上界自然是完整算术表达式。
2.3 函数调用
FnCall ::= Expr '(' ArgList? ')'
ArgList ::= Expr (',' Expr)* (',' NamedArg)* | NamedArg (',' NamedArg)*
NamedArg ::= Identifier '=' Expr命名实参用 name = value(RFC-010 §函数定义、RFC-011 §构造形式)。位置实参必须在命名实参之前;顺序指定的参数在命名实参中可任意排列:
add: (a: Int, b: Int) -> Int = a + b
add(3, 5) // 位置式
add(a = 3, b = 5) // 命名式
add(b = 5, a = 3) // 顺序任意
add(3, b = 5) // 混用,位置在前命名实参写错名字报 E1014,同一形参被位置与命名同时指定报 E1015,个数不符报 E1010(RFC-013)。
2.4 成员访问
MemberAccess::= Expr '.' Identifier2.5 索引访问
IndexAccess ::= Expr '[' Expr ']'三层语义(RFC-011b):
a[i]按a的类型分流——① 内建容器 (List/Vec/Array/Dict/Tuple)走原生索引指令(快路径);② 实现了Index接口的用户类型派发到其index方法(类型体内Index(Grid, Int, Float)实例化 +Grid.index方法);③ RFC-004 的f[0]位置绑定仅存在于绑定 声明,不走本文法。多维索引a[0, 1]的键按元组打包。未实现Index的 其他类型在类型层拒绝。
2.6 类型转换
TypeCast ::= Expr 'as' TypeExpr2.7 条件表达式
IfExpr ::= 'if' Expr Block ('else' 'if' Expr Block)* ('else' Block)?2.8 模式匹配
MatchExpr ::= 'match' Expr '{' MatchArm+ '}'
MatchArm ::= Pattern ('|' Pattern)* ('if' Expr)? '=>' Expr ','
Pattern ::= Literal
| Identifier
| Wildcard
| StructPattern
| TuplePattern
| EnumPattern
| OrPattern2.9 块表达式
Block ::= '{' Stmt* Expr? '}'语句终止规则:Stmt 之间的分隔与换行行为(
;显式分隔、换行终止、续行例外、行首(/[永不合并)由 RFC-038 定义。
2.9.1 { 的三种形态
{ 在表达式位置有且仅有三种解释,由内容一次判定:
| 形态 | 记法 | 类型 | 例 |
|---|---|---|---|
| 空块 | {} | Void | x: Void = {} |
| 字典字面量 | { "k": v, ... } | Dict(K, V) | d = { "a": 1 } |
| 块 | { Stmt* Expr? } | 尾表达式类型 | y = { 1 + 1 } |
判定顺序:
{}(无内容)→ 空块,值Void- 首元素是
String ':' Expr键值对 → 字典字面量 - 其余 → 块,值由尾表达式给出
为何
{}不是空字典:空字典的“空”无法自描述(它既可以是Dict(K, V)也可以是空块),而字典文法 §1.6.4 要求至少一个键值对。无内容可依据时取块结构的零形态:这与unsafe {}/spawn {}一致,不引入特例。空字典用dict.new()。为何函数需要注解:
f = { stmt }是值(尾表达式类型),不是函数。要定义函数就写明 Fn 注解:f: () -> Int = { 5 }。这与字典同一原则:类型由内容决定,不由注解存在与否决定。(此规则见 RFC-010a 附录 D。)
统一语义:所有 {} 块的值均由尾表达式给出,return 是 Never 型的非局部退出。
| 块类型 | 值出口 | 空块 {} |
|---|---|---|
普通 {} | 尾表达式 | Void |
unsafe {} | 尾表达式 | Void |
spawn {} | 尾表达式 | Void |
核心原则(详见 RFC-010a):
- 块的值 = 尾表达式(最后一个表达式),唯一出口,无例外
- 末位为赋值语句时块值为
Void;想要Void就显式写Void return退出最近的函数边界(穿出一切块,不「返回给块」),类型Never;Never <: T对任意类型成立(爆炸原理),故可出现在任何返回类型的位置- 表达式形式
= expr直接给出值
// 普通 {} 块:尾表达式给出值
result = {
x = compute()
x // 块的值
}
// unsafe {} 块:尾表达式给出类型定义
SqliteDb = unsafe {
SqliteDb: Type = {
handle: *Void
}
SqliteDb // 块的值
}
// spawn {} 块:尾表达式给出结果
(a, b) = spawn {
result1 = fetch("url1"),
result2 = fetch("url2")
(result1, result2) // 块的值
}
// return:穿透块,退出函数
f: (n: Int) -> Int = {
if n < 0 {
return 0 // 穿出 if 与函数体,退出函数
}
n * 2 // 尾表达式
}name = { ... } 是函数还是块值?
name = { ... } 既可能是函数定义(RFC-007「空参最简」)也可能是块值绑定。按注解优先、默认函数裁决(RFC-010a 附录D):
| 情形 | 结果 | 示例 |
|---|---|---|
值是 Lambda(=>) | 函数 | f = () => 5 → f() = 5 |
| 注解是函数类型 | 函数 | f: () -> Int = { 5 } → f() = 5 |
| 注解是非函数类型 | 块值 | x: Int = { 5 } → x = 5 |
| 无注解 | 函数 | f = { 5 } → f() = 5 |
注解即类型:x: Int = ... 声明 x 是 Int,则 { ... } 求值为 Int; f: () -> Int = ... 声明 f 是函数,则 { ... } 是函数体。
想要 { ... } 当场求值,把目标类型写上即可(不需要新语法):
// 块值:立即求值
x: Int = {
y = 5
y // x = 5
}
// 函数:无注解默认
f = { 5 } // f() = 5嵌套函数类型:柯里化还是返回函数?
-> 右侧的嵌套函数类型有两种读法,由括号区分(RFC-004):
| 写法 | 含义 | 调用 |
|---|---|---|
(a: Int) -> (b: Int) -> Int | 柯里化 | f(1)(2) |
(a: Int) -> ((b: Int) -> Int) | 返回函数 | g(1) 得函数 |
依据:注解即类型。g: (a: Int) -> ((b: Int) -> Int) 声明了 g(1) : (b: Int) -> Int,所以 g(1) 必须是那个函数,而不是“下一段参数”。
// 柯里化:两段参数逐层给
add: (a: Int) -> (b: Int) -> Int = { a + b }
add(1)(2) // → 3
// 返回函数:外层一段参数,返回的才是函数
adder: (n: Int) -> ((x: Int) -> Int) = (x) => x + n
adder(10)(5) // → 15
h = adder(10) // 返回的函数可存变量、可传递未加括号的嵌套 Fn 一律是柯里化(含 RFC-011 的类型参数形式):
identity: (T: Type) -> (x: T) -> T = (x) => x // 柯里化
identity(5) // → 5类型检查:括号声明了返回类型,body 必须产出该类型。 f: () -> (() -> Int) = { 7 } 是错误——期望 () -> Int,实际得到 Int(E1002)。
2.10 Lambda 表达式
Lambda ::= '(' ParamList? ')' '=>' Expr
| '(' ParamList? ')' '=>' Block2.11 错误传播运算符
ErrorPropagate ::= Expr '?'? 运算符是后缀运算符,优先级与 . 同级。对 Result(T, E) 类型:
Ok(v)时提取值v继续执行Err(e)时将错误向上传播(return Err(e))
process: (data: Data) -> Result(Data, Error) = {
validated = validate(data)? // 成功时提取值,失败时向上传播
transform(validated)
}2.12 范围表达式
RangeExpr ::= Expr '..' Expr ('..' Expr)?.. 创建范围值(Range 是一等值,非语法糖)。
for i in 0..10 { print(i) }
slice = array[0..5]
// Range 是值:绑定、传递、成员判断
r = 1..10
assert.assert(5 in r, "membership")
for i in r { print(i) }
// step 形态(第三分量,默认 1)
for i in 0..10..2 { print(i) } // 0, 2, 4, 6, 8
for i in 10..0..(-2) { print(i) } // 10, 8, 6, 4, 2step 语义:
a..b..c中c是步长。c = 0字面量编译期拒绝;动态c运行时零检查(E6001 家族;错误系统落地后升格 Result)。c < 0合法,区间方向随符号反转(10..0..(-2)递减)。
2.13 ref 表达式
RefExpr ::= 'ref' Exprref 创建共享持有。编译器自动选择 Rc(单任务)或 Arc(跨任务),用户不需要关心实现细节。
data = ref heavy_data
spawn { use(data) } // 跨任务:编译器自动选 Arc2.14 unsafe 表达式
UnsafeExpr ::= 'unsafe' Blockunsafe 块用于定义不透明类型和操作裸指针。使用 return 将类型定义返回给上一作用域。
语义:
unsafe {}中可以定义类型和操作裸指针- 返回的类型在
unsafe {}外可用 - 类型的字段访问需要 unsafe 权限
// 在 unsafe 块中定义不透明类型
SqliteDb = unsafe {
SqliteDb: Type = {
handle: *Void // 裸指针
}
return SqliteDb
}
// SqliteDb 在 unsafe 块外可用
db = sqlite3_open("test.db")2.15 作用域
基本规则:
- 每个
{}块创建一个作用域 - 内层作用域可以访问外层作用域的变量
- 外层作用域不能访问内层作用域的变量
- 变量声明遵循"赋值优先"原则
// 块作用域
{
x = 10
// x 在此作用域内可见
}
// x 在此作用域外不可见
// 函数作用域
add: (a: Int, b: Int) -> Int = {
result = a + b
return result
}
// result 在函数外不可见变量声明与遮蔽:
x = value:沿作用域链向外查找 x,找到则赋值,找不到则新声明mut x = value:显式新可变声明,禁止与外层同名- 同作用域内任何名字只能声明一次
详细定义:作用域的完整规则、变量声明和遮蔽机制详见 模块系统规范。
第三章:语句
3.1 语句分类
Stmt ::= LetStmt
| ExprStmt
| ReturnStmt
| BreakStmt
| ContinueStmt
| IfStmt
| MatchStmt
| WhileStmt
| ForStmt
| SpawnStmt3.2 变量声明
LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr3.3 return 语句
ReturnStmt ::= 'return' Expr?语义:return 是非局部退出,退出最近的函数边界(穿透一切块——包括 if / while / for / match / 裸块 / spawn / unsafe),把值交给调用者。它不「返回给块」。
类型:return e : Never(e : T)。Never <: T' 对任意 T' 成立(爆炸原理,见 类型系统 §2.2),故 return 可出现在任何返回类型的位置,无需额外规则约束。
与块求值的关系:块的值恒为尾表达式(见 §2.9)。{ return n } 作为块,其值是 n,类型 Never;同时 return 的作用是退出函数。两件事同时成立,靠爆炸原理共存。
return 与尾表达式共同使「提前返回」成立,无需 return 特指函数的额外规则——见 RFC-010a。
factorial: (n: Int) -> Int = {
if n <= 1 {
return 1 // 穿出 if,退出函数(类型 Never)
}
n * factorial(n - 1) // 尾表达式 = 块的值
}3.4 break 语句
BreakStmt ::= 'break'语义:立即终止所在的最内层 while/for 循环,控制流转到该循环体之后。
- 只出最近一层:
break恒作用于包含它的最内层循环。嵌套循环中需要一次跳出多层时,把内层循环提取为函数用return返回,或使用标志位(break/continue 不带标签;若未来引入循环标签,将按循环声明侧语法走 RFC 流程,与证明管道的多出口设计一并裁决) - 仅限循环体内:
break只能出现在while/for循环体内(含体内嵌套的块/if/match),出现在循环外编译报错(E1102'break' outside of a loop) - 不影响终止性证明:
break不参与终止性论证——它既不提供测度,也不构成测度的递减步骤;循环的终止义务与break无关,由精化类型触发(见 type-system §8.4)。 - 借用语义:break 的控制流边参与 RFC-009a 反向 BFS 活性分析的结构性切断(跳出的迭代不参与回边活性推导)
mut i = 0
while i < 10 {
i = i + 1
if i == 3 {
break // 控制流转到循环之后,i == 3
}
}
// 嵌套循环:break 只出内层
while j < 3 {
while k < 10 {
if k == 2 { break } // 只终止内层循环
}
j = j + 1 // 每轮外层迭代都会执行到这里
}3.5 continue 语句
ContinueStmt::= 'continue'语义:跳过本次迭代中剩余的语句,直接进入所在的最内层循环的下一轮—— while 回到条件重判,for 取下一个元素。
- 只作用最近一层:与
break相同,不带标签 - 仅限循环体内:出现在循环外编译报错(E1102)
mut sum = 0
mut n = 0
while n < 5 {
n = n + 1
if n == 3 {
continue // 跳过下面的累加,n == 3 不计入
}
sum = sum + n
}
// sum == 12(1 + 2 + 4 + 5)3.6 if 语句
IfStmt ::= 'if' Expr Block ('else' 'if' Expr Block)* ('else' Block)?3.7 match 语句
MatchStmt ::= 'match' Expr '{' MatchArm+ '}'3.8 while 语句
WhileStmt ::= 'while' Expr Block3.9 for 语句
ForStmt ::= 'for' 'mut'? Identifier 'in' Expr Block3.9.1 语义:每次迭代是绑定新值
YaoXiang 的 for 循环语义与传统语言不同:每次迭代是绑定新值,而不是修改同一个变量。
// 示例:for i in 1..5
for i in 1..5 {
print(i)
}执行过程:
| 迭代 | 循环变量的行为 |
|---|---|
| 第1次 | 创建新绑定 i = 1,循环体执行,打印 1 |
| 第2次 | 创建新绑定 i = 2(之前的绑定已销毁),循环体执行,打印 2 |
| 第3次 | 创建新绑定 i = 3,循环体执行,打印 3 |
| 第4次 | 创建新绑定 i = 4,循环体执行,打印 4 |
| 结束 | 循环体结束,绑定销毁 |
关键点:每次迭代结束后,当次迭代创建的绑定会被销毁。下一次迭代是一个全新的绑定,与上一次迭代的绑定没有任何关系。
3.9.2 for 与 for mut 的区别
| 语法 | 循环变量可变性 | 说明 |
|---|---|---|
for i in 1..5 | 不可变 | 循环体内不能修改绑定 |
for mut i in 1..5 | 可变 | 循环体内可以修改绑定 |
// 合法:每次迭代绑定新值,不需要修改
for i in 1..5 {
print(i) // 读取 i 的值
}
// 错误:不可变绑定,不能修改
for i in 1..5 {
i = i + 1 // 错误:不能修改不可变绑定
}
// 合法:使用 for mut 允许修改绑定
for mut i in 1..5 {
i = i + 1 // 允许修改
}3.9.3 遮蔽检查
YaoXiang 禁止变量遮蔽。for 循环变量不能与外层作用域中的变量同名:
// 错误:i 已经在外部声明
i = 10
for i in 1..5 {
print(i)
}
// 正确:使用不同的变量名
i = 10
for j in 1..5 {
print(j)
}此规则适用于所有代码块,详见 4.3 遮蔽规则。
3.9.4 与其他语言的对比
| 语言 | for 循环变量语义 |
|---|---|
| YaoXiang | 每次迭代绑定新值 |
| Rust | 修改同一个变量(需要 mut) |
| Python | 修改同一个变量(无需 mut) |
| C/C++ | 修改同一个变量(需要指针或引用) |
设计理由:YaoXiang 采用绑定语义是因为:
更符合自然语义 在自然语言中,"对于集合中的每个元素 x" 意味着每个 x 是独立的个体。YaoXiang 的
for i in 1..5读作"对于 1 到 5 中的每个 i",每次迭代的 i 是一个全新的绑定,这与人类的直觉理解一致。避免意外修改 默认不可变的绑定语义意味着循环体内无法意外修改循环变量。不需要担心在复杂循环体中某个地方不小心写了
i = ...导致难以追踪的 bug。高性能方案触手可及 当确实需要在迭代间复用变量时(例如累加器、缓存),使用
for mut声明即可切换到可变绑定模式。这比隐式共享状态更清晰——意图通过语法显式表达,而不是藏在运行时行为里。
3.10 spawn 语句
SpawnBlock ::= '(' Pattern (',' Pattern)* ')' '=' 'spawn' '{' Expr (',' Expr)* '}'
SpawnFor ::= Identifier '=' 'spawn' 'for' 'mut'? Identifier 'in' Expr '{' Expr '}'
SpawnStmt ::= SpawnBlock | SpawnForspawn 块:显式声明并发疆域,块内表达式并发执行。
(result_a, result_b) = spawn {
parse(fetch("url1")),
parse(fetch("url2"))
}spawn 循环:数据并行循环。
results = spawn for item in items {
process(item)
}spawn 块捕获外层变量(RFC-024 §2.3,值捕获语义):
- 块体引用外层变量 = Move 值捕获:值在 spawn 创建点快照进闭包环境,块体经 env 读取(LoadUpvalue)
- 原语(Int/Float/Bool/Char)值复制,外层变量不受影响
- 句柄类型(Struct/String/List 等)快照 = 句柄复制,共享底层对象;Embedded 运行时(默认)同线程同堆,句柄有效
- 多个任务间共享需显式
ref(§2.13,编译器自动选 Rc/Arc) - 块内
return引用的外层变量同样捕获
t1 = 1 + 1
t2 = 2 + 2
result = spawn {
return t1 + t2 // t1/t2 值捕获,result == 6
}3.11 程序入口与顶层语句
一个源文件在编译器眼里有两种角色,由是否存在 yaoxiang.toml 决定:
| 角色 | 判定 | 程序主体 |
|---|---|---|
| Script | 单文件直跑,无 yaoxiang.toml | 顶层语句(按书写顺序执行);main 是普通绑定 |
| Bin | 存在 yaoxiang.toml | main 函数;顶层不得有可执行语句 |
Script:顶层语句即程序
无 manifest 时文件是一个「脚本」,顶层语句按书写顺序执行:
use std.io
io.println("hello") // 直接执行
x: Int = { 42 } // 顶层绑定:运行时初始化
io.println(x) // 42main 在此模式下不特殊——它只是一个普通绑定。想让 main 跑,必须显式调用:
use std.io
main: () -> Void = { io.println("only runs if called") }
main() // ← 必须写这一行为何不自动调用
main? 顶层语句已经是程序主体。若再隐式调用main,显式写了main()的脚本会执行两次。两种规则不能共存,故 Script 下只有「顶层语句」一个执行入口。
Bin:main 是入口
有 manifest 时文件是「可执行目标」,此时:
- 必须定义
main,且它必须是函数(签名须可零参调用) - 顶层不允许可执行语句——程序主体就是
main
main: () -> Void = {
print("hello")
}缺 main 或 main 非函数均为编译错误(前者:无 main 时全部函数不可达;后者:值绑定不会被调用)。
库文件:被其他文件
use的文件、或[lib].path/[exports]指向的文件不要求main——它们不是程序入口。
顶层绑定的初始化
顶层绑定的初始化值在运行时求值,不要求是编译期常量:
answer: Int = { 42 } // 块值
inc: (Int) -> Int = (x) => x + 1
computed: Int = inc(41) // 函数调用初始化按依赖顺序执行,与书写顺序无关:
derived: Int = base * 3 // 引用后声明的 base
base: Int = 7 // 先初始化(拓扑排序)循环依赖是编译错误(会列出环上的名字):
a: Int = b + 1
b: Int = a + 1 // 错误:a → b → a设计依据:RFC-029f(文件角色模型)、RFC-010a 附录D(块绑定裁决)。
附录:语法速查
A.1 控制流
if Expr Block (else if Expr Block)* (else Block)?
match Expr { MatchArm+ }
while Expr Block
for 'mut'? Identifier 'in' Expr Block
break | continue // 仅循环体内(§3.4 / §3.5)A.2 错误处理
Expr '?' // 错误传播(Result 类型)A.3 match 语法
match value {
pattern1 => expr1,
pattern2 if guard => expr2,
_ => default_expr,
}