LSR 000 - Lamina 核心语言规范(草案)

1. 语言定位

Lamina 是静态强类型、表达式导向的数学 DSL/脚本语言,支持单位系统、代数表达和局部类型推导。

Lamina的主要核心为静态、模块化与数学。 一个 .lm 文件就是一个模块。 一个模块可以绑定一个动态库 [1]

2. 核心关键字

let , var , const , unit , func , return , in , not , loop , while , break , continue , if , else , match , sym , import , use , as , and , or , static

3. 运算符总览

3.1 基础算术与优先级

  • 算术运算符: + , - , * , / , // , % , ^

  • 优先级(高到低):

    1. ^ (幂运算)

    2. * (乘法), / (除法), % (取余), // (整除)

    3. + , -

3.2 逻辑与比较

  • 标量比较: == , != , < , <= , > , >=

  • 广播比较: .== , .!= , .< , .<= , .> , .>=

  • 逻辑: and , or , not|!

  • 规则:逻辑运算优先级低于算术运算

  • 规则:未加点的比较仅用于标量; vector / matrix 比较必须使用加点算子

  • 优先级(高到低, 低于3.1运算符):

    1. not , !

    2. or , and

    3. == , !=

    4. > , < , >= , <=

3.3 赋值与绑定

  • 赋值: =

  • 绑定: let (只读), var (可变)

  • 符号声明: sym (类型固定为 Expr

  • 优先级:最低

3.4 矩阵与向量运算符

  • 线性代数: * , \ , / , ^

  • 元素广播: .* , ./ , .+ , .- , .^

  • 关系广播: .== , .!= , .< , .<= , .> , .>=

  • 转置: ' (共轭转置), .' (非共轭转置)

  • 优先级: 线性代数暂定,元素广播同3.1,关系广播同3.2

3.5 集合相关运算符

  • 从属: in , not in

  • 子集: subset

  • 并交差: | , & , -

  • 对称差: xor

3.6 函数与流程运算符

  • Lambda 箭头: ->

  • match 分支箭头: =>

  • 管道: |>

  • 成员访问: .

# 管道符是语法糖
let a = b |> foo; # let a = foo(b);
# `.` 左边不是mod, 且右边为方法调用时,也是语法糖
let a = b.m1(); # let a = (typeof b).m1(b);

3.7 运算符分派与歧义约束

运算符

仅适用类型

语义

歧义处理

/

num

数值除法

若操作数不满足数值或矩阵规则,编译报错

\

matrix

矩阵左除(解 A X = B

左值必须可作为系数矩阵,维度不匹配或系统无解时报错

/

matrix

矩阵右除(解线性系统)

左值必须是矩阵,右值可用于右除,否则报错

^

num / Expr

幂运算

集合对称差使用 xor

^

matrix

矩阵幂(整数指数)

非方阵或非整数指数报错

xor

set

集合对称差

集合对称差统一使用 xor

< <= > >= == !=

标量( num / complex / text / bool

标量关系比较

操作数为 vector / matrix 时编译报错,要求使用加点比较

.* ./ .+ .- .^

vector / matrix 与标量或同形状容器

元素级广播 / 哈达玛运算

维度不兼容报错

.== .!= .< .<= .> .>=

vector / matrix 与标量或同形状容器

元素级关系广播

返回同形状 vector<bool> / matrix<bool> ,维度不兼容报错

3.8 一元运算符

4. 变量、类型与推导

4.1 类型系统

具备自底向上类型推导,多数情况推荐手动标注类型

4.2 局部类型推导规则

  • 允许推导:局部 let/var 绑定、for 推导式局部变量

  • 必须显式:函数参数类型、函数返回类型、模块导出符号类型

  • Lambda 与 match 的推导规则由 LSR-006 [4] 与 LSR-005 [3] 定义

  • 推导结果必须为确定类型;约束不足时编译报错

4.3 声明语法

变量声明统一 let|var|const name type = expr 或者 let|var|const name = expr

  • let :只读绑定, 当绑定为对象类型时,仅代表不可再次绑定另一个对象|值,而不是对象本身不可变

  • var :可变绑定

  • const :编译期常量声明(只读,要求初始化表达式可在编译期求值)

let a = 10
let pi_val real = 3.1415
let fraction = 3/2
const TAU = 2 * pi

let c = 5/4
let name = "LSR"

var counter = 0
counter = counter + 1

a = 20   # 编译报错

4.4 核心数据类型

类型

说明

字面量 / 示例

num

数值超集( int , real , frac

42 , 114.514 , 1/2

array

编程式数组类型

[2,5,3]

complex

复数

3 + 4i

text

文本

"i love lamina"

bool

布尔逻辑

true , false

set

无序去重集合

{1,2,3}

vector

一维同构数值数组(1 索引)

vec[1,2,3]

table

哈希表(任意可哈希键)

见 4.5

matrix

二维同构数值网络(1 索引)

mat[1,2;3,4]

Expr

CAS 符号表达式

x^2 + 1

  • 在当前定义中, int , bool , frac 为值类型,其他为对象引用类型

  • num 为代数表达式的类型,但其下 int , real , frac 均为实际类型,也是核心数据类型成员

  • Lamina没有浮点数,浮点数只存在于FFI操作中,解释器使用分数模拟有理数运算,无精度损失

4.5 可空类型

写法: T? ,表示可能为null, 用于判断价值是否为null 必须处理空情况才能正常使用

var s text? = null

if s != null {
  #s在此作用域内部,自动变为text类型
}

if s == null {

} else {
  #s在此作用域内部,也自动变为text类型
}

4.6 复合类型的值语义与引用语义

4.6.1 总规则

  • 复合类型实现采用共享引用,降低拷贝成本

  • 只读操作(读取、遍历、计算)不触发复制

  • 一旦发生可变写入( var 绑定上的突变),若对象被共享,必须先分离副本再写入

4.6.2 赋值语义

  • 赋值后两个变量指向同一底层对象( is 可能为 true

  • 对其中一个变量做可变写入时,触发 COW,另一个变量保持原值

var t1 = table{"x" => 1}
var t2 = t1

t2["x"] = 9

# t1["x"] == 1
# t2["x"] == 9

4.6.3 函数传参语义

  • 非值类型参数采用引用传递

  • 修改结果通过 return 新对象交付调用方

func bump(src table[text, int]) -> table {
    var t = src
    let v = t["n"] # 返回类型为int?可空类型
    # 若为值类型则产生复制,否则为引用
    if v != null {
        t["n"] = v + 1
    }
    t
}

var a = table{"n" => 0}
var b = a |> bump

# a["n"] == 0
# b["n"] == 1

4.6.4 与 let/var 的关系

  • let :只读绑定,COW 写路径仅由 var 触发

  • var :可变绑定,触发 COW 规则

  • 本规则统一适用于 vectormatrixtableset

4.7 容器齐次性约束

vectormatrix 强齐次,保证类型稳定与 BLAS 友好。

  • vector<T> 要求所有元素类型一致

  • matrix<T> 要求所有元素类型一致

  • 对数值容器,元素量纲签名也必须一致

示例:

let ok_v = vec[1<m>, 2<m>, 3<m>]          # 合法:同类型同量纲
# let bad_v = vec[1<m>, 2<s>]             # 非法:量纲不一致

异构数据建议:

  • 需要混合不同量纲(如 [pos, vel] )时,使用 table

  • 或使用具名复合类型规范

5. 量纲系统

5.1 声明与运算

单位通过尖括号 <> 附加于数值后形成复合类型。编译器执行量纲代数(约分、乘除)与加减一致性检查。

单位只能标注在 num 类型中,量纲属于类型系统。 量纲只能标在字面量后面,或在声明类型时使用例如 num<m> 的形式

let dist = 10<m>
let time = 2<s>
let spd = dist / time

# let error = 5<m> + 2<s>   # Dimensional Mismatch

5.2 显式单位转换

单位转换必须使用 as

let v = 20<m/s>
let v_kmh = v as <km/h>

5.2.1 用户自定义单位

用户可使用 unit 声明新单位,支持两种形式:

  • 抽象基单位: unit U

  • 派生单位: unit V = expr<...>

unit score
unit token

unit km = 1000<m>
unit min = 60<s>
unit N = 1<kg*m/s^2>
unit level = 100<score>

约束:

  • 单位名在当前命名空间内唯一

  • 单位定义要求无环(如 unit a = 2<b>unit b = 3<a> 形成环)

  • 抽象基单位可直接创建;派生单位需可归约到已知单位组合(SI 或抽象基单位)

  • 派生单位必须可归约到已知单位组合(SI 或已声明抽象单位)

  • 定义成功后可参与 as <...> 转换(仅限同量纲)

5.3 底层表示

  • 量纲签名在**编译期类型检查阶段**表示为“SI 七维 + 用户基单位”的稀疏指数映射

  • 无量纲纯数字对应零向量

  • 乘除运算对应向量加减

  • 相同量纲不同缩放单位仅允许通过 as 显式转换

实现约束:

  • 运行期普通数值不携带量纲元数据

  • 以便复用 BLAS/LAPACK 等高性能线代后端

  • Expr (CAS)允许保留量纲符号,用于符号推导中的一致性验证

5.4 量纲剥离(to scalar)

带量纲值剥离为纯数值的语法与边界由 LSR-008 [6] 定义。

let dist = 10<m>
let raw = dist as num              # SI 基准转换后剥离
let raw_keep = dist as scalar      # 仅剥离标签,保持当前刻度

let speed = 20<m/s>
let raw_kmh = (speed as <km/h>) as num

规则:

  • 默认剥离到 num 使用 SI 基准量值

  • as scalar 仅移除量纲标签,不进行数值缩放

  • 若需要特定单位下的纯数值,先做单位转换再剥离

  • as num<_> 在新规范中非法,必须使用 as num

6. 函数

6.1 函数签名

函数签名语法: func name(param Type, ...) -> ReturnType

func kinetic_energy(m num<kg>, v num<m/s>) -> num<J> {
    return 1/2 * m * v^2
}

func second2hour(s num<s>) -> num<h> {
    return ((s as num) / 3600) as num<h>
}

6.2 Lambda 与 match 的规范边界

  • 本文件不定义 Lambda 语法与类型推导细则

  • Lambda 的语法、类型推导、与管道交互由 LSR-006 [4] 单独定义

  • match 的语法、穷尽检查、模式系统由 LSR-005 [3] 单独定义

7. 控制流与推导式

7.1 表达式化 if

if 是表达式,必须有 else (或提前 return 完全覆盖)。

let abs_val = if x < 0 {
    -x
} else {
    x
}

7.2 loop 定次循环

lamina只有一种循环,就是无限loop循环,其他循环结构均为其语法糖

loop x { ... } 表示循环体执行 x 次。 x 必须是可确定为非负整数的表达式。

import std

loop {
  if true {
    break
  }
}


loop 5 {
    std.println("tick")
}
#####


let n = 3
loop n {
    counter = counter + 1
}

规则:

  • 循环次数在进入循环前求值一次

  • x == 0 ,循环体不执行

  • x < 0 或不是整数,编译报错

7.3 while 条件循环

while cond { ... }condtrue 时重复执行循环体。

var i = 0
while i < 5 {
    i = i + 1
}

规则:

  • 每轮迭代前重新计算 cond

  • cond 必须是 bool 类型,否则编译报错

  • 支持 breakcontinue

7.4 for 迭代与推导式

迭代: for 名字 in 可迭代对象 {...}

不论迭代还是推导式,其可迭代对象必须包含 begin() next(last iterator)end() 子方法

let person = vec[2,4,32,5,6]
for num in person {
  print(num)
}

# 等价写法:

let person = vec[2,4,32,5,6]
{
  let end = person.end()
  var iter = person.begin()
  var num = iter.get()
  while iter != end {

      print(num)

      iter = person.next(iter)
      num = iter.get()
  }
}

推导式语法: 表达式 for 变量 in 可迭代对象 if 守卫条件

# 向量推导
let squares = vec[x^2 for x in range(1, 10) if x % 2 == 0]

# 集合推导
let abs_set = {abs(x) for x in vec[-1, 1, 2, -2]}

# 矩阵推导
let M = mat[i * j for i in range(1,3); for j in range(1,3)]

规则:

  • 推导式内部为独立词法作用域

  • 外部变量访问范围:读取外部 let ;外部 var 通过显式返回更新

  • 迭代器单次求值

8. 向量与矩阵计算

8.1 定义、拼接与索引

let A = mat[1,2,3;4,5,6;7,8,9]
let v = vec[1,2,3]

let A_double = [A, A]
let A_stack = [A; A]
let element = A[2,3]

8.2 切片

let row2 = A[2,*]
let col3 = A[*,3]
let submat = A[1..2,2..3]

8.3 算术与关系广播定义

  • 标准线性代数算子: * , \ , / , ^

  • 元素广播算子: .* , ./ , .+ , .- , .^

  • 关系广播算子: .== , .!= , .< , .<= , .> , .>=

  • \/ 分别定义为左除与右除: A \ BA X = BB / AX A = B

  • \/^ 采用按类型分派;无法唯一分派时编译报错

  • 未加点的关系比较仅用于标量;容器比较必须使用加点算子

let X = A \\ B      # 线性系统 A X = B
let Y = B / A       # 线性系统 Y A = B
let B = A .+ 10
let C = A .* A
let D = A * A   # A 必须为方阵

let flags = vec[1,2,3] .> 1      # vec[false, true, true]
# vec[1,2,3] > 1                 # 编译报错(容器比较需使用 .>)

8.4 常用操作

dot(v1, v2)      # 点积
cross(v1, v2)    # 叉积(3维)
norm(v)          # 范数
v1 .+ v2         # 逐元素加法
v .* 2           # 标量广播
matmul(A, v)     # 矩阵-向量乘
matmul(A, B)     # 矩阵-矩阵乘
inv(A)           # 逆矩阵
det(A)           # 行列式

9. 集合与逻辑

运算符

含义

示例

in / not in

元素从属

2 in {1,2,3}

subset

子集关系

{1,2} subset {1,2,3}

| , & , - , xor

并、交、差、对称差

{1,2} xor {2,3}

预定义数学常量: pi , e , phi

预定义集合: {R} , {Z} , {Q} , {C}

10. 数值塔与提升规则

层级: Z Q R C Expr

  • Int (Z) :大整数,与 int 区别在于其为无限精度

  • Fraction (Q) :分数(分子分母均为 Int ), 与 frac 区别在于 frac 分子分母均为 int

  • Real (R) :实数

  • Complex (C) :复数

  • Expr :符号表达式

提升规则:

  1. 二元运算中,低层级自动提升到高层级

  2. Expr 降级需显式转换

  3. 除法只有在显式涉及 Real 时返回 Real

  4. 数值提升与单位推导互不干涉

let a = 2 + 1/2
let b = 1/2 + 0.5
let c = 2.0 + 3i

sym x
let d = 5/2 + x

11. Expr 边界与求值

Expr 为延迟求值 AST;参与数值运算时整体提升为 Expr

  let c = 5
  sym x
  let res = c * x


:code:`Expr` 降级必须显式:
  1. 代入求值: expr(symbol => value)

  2. 强制浮点: evalf(expr)

sym x, y
let equation = x^2 + 1 + y*2
let val1 = equation => (x => 3, y => 4)

let exact = sin(pi / 4)
let approx = evalf(exact)

12. 相等性语义

  • 结构相等: ==

  • 数学等价(仅 Expr ): === (由 LSR-007 [5] 单独定义)

let a = vec[1,2,3]
let b = a
b is a   # true

let c = vec[1,2,3]
c is a   # false

vec[1,2,3] == vec[1,2,3]   # true
[1,2;3,4] == [1,2;3,4]     # true

13. 序列与模块系统

13.1 序列生成

func square(x num) -> num {
    return x^2
}

let nums = range(1,10)
let squares = nums |> map(square)
range(start int, end int) -> vector

13.2 命名空间与模块

import :加载模块并绑定模块对象;标准库模块导入见 [2]use :从模块导入符号到当前作用域(冲突时报错)。

import std.math
import physics.quantum

let val = math.sin(math.pi / 2)

use std.math.{sin, cos, pi, e}
use std.linalg.{det}

let wave = sin(pi * x) + cos(e * x)
let D = det(matrix_A)

use std.constants.{G as GRAVITY}

14. 引用