表达式与语句

本文档收敛 Dujie 的字面量、控制流、运算、推导边界、语句模型和语言级错误行为。

总体原则

  • Dujie 采用表达式优先的风格
  • if 和 block 都可以作为表达式
  • 赋值、breakcontinue 属于语句,不参与普通表达式求值
  • unit 是正式类型,用于表示“无值”

字面量

基础字面量

  • int
  • float
  • bool
  • string
  • rune

当前公开整数类型只有 int

string

  • 普通字符串使用 "..." 表示
  • 支持转义:\\\"\n\r\t\u{...}
  • string 是文本类型,不是字节数组

rune

  • rune 使用 'a' 形式
  • 支持转义:\\\'\n\r\t\u{...}
  • 转义展开后必须恰好表示一个字符,否则是词法错误

插值字符串

  • 插值字符串使用反引号:`hello ${name}`
  • 插值片段写作 ${expr}
  • 整体结果类型是 string
  • 插值允许任意类型

插值使用的是展示语义,不是序列化语义:

  • 基础类型按自然文本形式展示
  • none 在插值上下文里等价于空字符串
  • some(v) 等价于插入 v 的展示结果
  • list / map / struct / widget / iter / func 使用统一摘要展示,不展开结构

容器与聚合字面量

list

  • 语法:[][1, 2, 3]
  • 元素必须同类型
  • 不做 int / float 混合推导
  • T 可在上下文需要时提升到 opt<T>

例如:

[1, 2, 3]
["a", none]

map

  • 语法:{ "a": 1, "b": 2 }
  • key 必须同类型
  • value 必须同类型
  • map key 当前只允许:
    • int
    • bool
    • rune
    • string

第一阶段不引入联合类型。

struct 构造

  • 语法:Person { name: "snow", age: 18 }
  • 这是具名类型构造,不是匿名对象字面量
  • 所有字段必须显式提供
  • 未知字段、重复字段、缺字段都是编译错误

struct

struct 是名义类型,不是结构类型。

规则:

  • 只支持具名字段
  • 字段声明写作 name: Type
  • 不支持 tuple struct
  • 不支持按数字索引访问
  • 字段顺序不构成类型语义
  • 第一阶段不支持字段默认值

类型兼容:

  • 两个 struct 即使字段完全一致,只要名字不同,就是不同类型
  • 不支持 structmap 之间自动互转

字段类型:

  • 可包含基础类型
  • 可包含 list / map / opt
  • 可包含其他 struct
  • 可包含 widget
  • 第一阶段不建议 iter<T> 作为字段类型

递归:

  • 允许通过 opt<T>list<T>map<K, T> 间接递归
  • 不允许字段直接按值引用自身类型

多行格式与尾逗号

  • 单行 list / map / struct 可带尾逗号,也可不带
  • 多行 list / map / struct 必须带尾逗号

类型推导边界

  • let x = expr 只在 expr 类型唯一时允许推导
  • []{}none 不能独立推导类型
  • if 两个分支类型必须一致
  • 空容器和 none 需要依赖上下文或显式标注

例如:

let a = 1
let b: list<int> = []
let c: opt<string> = none

opt<T>

opt<T> 的语言级构造和判定:

  • none
  • some(x)
  • x is none
  • x is some(v)

规则:

  • opt<T> 不支持比较运算
  • 参数类型为 opt<T> 时允许省略
  • T 可自动提升为 some(T)

any

any 是边界类型,不是万能类型。

当前语义:

  • T -> any 允许
  • any -> T 不做隐式恢复
  • 第一阶段使用 is 做显式收窄

支持的最小 is 形式:

x is T
x is T(v)

当前稳定支持的收窄目标首先是基础类型:

  • int
  • float
  • bool
  • rune
  • string

表达式

匿名函数表达式

匿名函数是表达式。

形式与普通函数相同,只是没有名字:

func(x: int) -> string {
    `#${x}`
}

当前边界:

  • 允许捕获外部变量
  • 主要用于目标参数类型已知的回调位置
  • 不作为完整一等函数值系统单独展开

if 表达式

  • if 是表达式
  • ifelse 都必须存在
  • 条件必须是 bool
  • 两个分支结果类型必须一致

block 表达式

  • block 的结果类型由尾表达式决定
  • 如果没有尾表达式,则结果类型为 unit

例如:

let x = {
    let a = 1;
    a + 1
}

语句

let / var

  • let:不可变绑定
  • var:可变绑定

绑定可变性与更新规则:

  • let 绑定不能重新赋值
  • let 绑定也不能做原地更新
  • var 绑定允许重新赋值和原地更新

赋值

  • = 是语句,不是表达式
  • 左侧必须是可写左值
  • 当前合法左值:
    • 变量名 x
    • xs[i]
    • m[k]
    • p.name

容器更新

list 支持:

  • xs[i]
  • xs[i] = v
  • push(v)
  • pop()
  • remove(i)
  • clear()
  • len()

map 支持:

  • m[k]
  • m[k] = v
  • remove(k)
  • clear()
  • contains(k)
  • len()

统一规则:

  • 更新操作只能作用在 var 绑定的可写左值上
  • 临时值不能更新
  • 会修改接收者的方法只能对可写目标调用

运行时行为:

  • list[i] 越界时 panic
  • list[i] = v 越界时 panic
  • list.pop() 对空列表调用时 panic
  • list.remove(i) 越界时 panic
  • map[k] 缺 key 时 panic
  • map[k] = v 缺 key 时直接插入
  • map.remove(k) 缺 key 时 panic

return

  • 返回类型必须与函数声明一致
  • unit 函数可写 return;
  • unit 函数不能写裸 return;

break / continue

  • 都是语句,不是表达式
  • 只允许出现在 for 循环体中

for in

形式:

for x in expr {
    ...
}

当前可迭代类型:

  • list<T>
  • iter<T>

当前不支持:

  • for (i, x) in xs
  • for (k, v) in map
  • 字符串迭代
  • 解构绑定

panic

panic 是内建能力,用于主动中止执行。

当前规则:

  • 形式:panic(message: string)
  • 参数只接受 string
  • 它不会正常返回
  • 在类型检查上,允许出现在任意需要值的位置

运算

算术

  • + - * / % 只对数值类型开放
  • 不支持 int / float 混合运算
  • int op int -> int
  • float op float -> float
  • % 只支持 int
  • int / int -> int
  • float / float -> float
  • 一元负号支持 intfloat

比较

支持 == / !=

  • int
  • float
  • bool
  • rune
  • string

支持 < <= > >=

  • int
  • float
  • rune
  • string

不支持比较:

  • opt<T>
  • struct
  • list
  • map
  • iter
  • widget
  • any

逻辑

  • ! 只接受 bool
  • &&|| 只接受 bool
  • && / || 使用短路语义

显式转换

转换语法采用 Type(expr)

第一阶段只支持:

  • float(int_expr)
  • int(float_expr)

其中 float -> int 按向零截断处理。

成员访问、下标与调用

成员访问 x.y

  • 只对 struct 和语言内建对象开放
  • struct 字段不存在时,编译错误
  • 第一阶段不支持 map 的点访问

下标访问 x[y]

  • list<T>[int] -> T
  • map<K, V>[K] -> V

运行时行为:

  • list 越界时 panic
  • map 缺 key 时 panic

调用 f(...)

  • 调用结果类型由函数声明返回类型决定
  • 参数匹配规则见《函数与调用》

iter<T>

iter<T> 是遍历用的中间类型,不是常规稳定容器。

允许:

  • 作为函数参数或返回值
  • 用于 for in
  • 通过 collect() 显式收集为 list<T>

当前核心操作:

  • map
  • filter
  • collect

运算符优先级

从高到低:

后缀:x.yx[y]f(...)

一元:-x!x

乘法:* / %

加法:+ -

比较:< <= > >=

相等:== !=

逻辑与:&&

逻辑或:||

if 表达式

约束:

  • 不支持链式比较
  • 赋值不进入普通表达式优先级体系
  • 括号可显式改变优先级

语言级错误模型

编译错误

能在编译期确定的问题直接报错,例如:

  • 类型不匹配
  • 未知变量、字段、导入符号
  • 调用参数错误
  • let 做更新
  • 非法左值赋值
  • break / continue 出现在循环外
  • 非法字面量和非法转义

运行时 panic

依赖运行时值的问题进入 panic,例如:

  • map[k] 缺 key
  • list[i] 越界
  • 空列表 pop
  • 用户显式调用 panic("...")

总原则:

  • 能在编译期确定的问题,不留到运行时
  • 只有依赖运行时具体值的问题,才进入 panic