KSL 语法说明
概述
KSL 语法受到 Wolfram 语言与 LISP 的启发,采用前缀表示法,所有运算皆通过函数调用完成,不设中缀运算符。
语法围绕三种基本结构展开:基本元素、列表与函数调用。下文逐一说明。
基本元素
KSL 定义了五种基础数据类型:符号、原子、字符串、数字与对象。
符号
符号是变量名与函数名的标识符。
首字符必须为 Unicode 字母或下划线 _。后续字符可为字母、数字、_、-、@、$、'、?、!。以数字或标点开头均不合法。
符号长度上限为 23 个字符。
以下划线开头的符号为模块私有符号,无法被其他文件加载。
示例:a、Add、_private-func' 以及 _你好です!。
原子
原子是唯一且恒定的标识符,类似枚举值。以冒号 : 开头。
首字符必须为 Unicode 字母,后续字符规则同符号。长度上限同为 23 个字符。
示例::some-tag、:你好です!。
:true 与 :false 用作布尔值真与假的惯用表示。
字符串
字符串由双引号 " 包围,可跨越多行。引号内的空白、换行与缩进均原样保留。
KSL 不支持 \n 这类转义序列。要嵌入换行等特殊字符,有两种等价方式:使用 :码点 语法直接书写 Unicode 码点,或通过 Chn 函数将码点转为字符后拼接。
Let[s, "Hello, world🌏,
换行直接支持。
前置空白也会保留。"];
Let[s', Concat[s, :10, "新起一行。"]];
Let[s'', Concat[s, Chn[10], "新起一行。"]];
数字
数字可写为整数、小数或科学计数法。小数点前可省略 0,.5 即 0.5。
当整数与浮点数混合运算时,结果统一为浮点数。
Add[2, 3.0]; # => 6.0
Mul[3, 4.0]; # => 12.0
整数运算不会因溢出而自动提升类型。若需手动转换,可使用 Add[n, 0.0] 或 Mul[n, 1.0]。
字符
字符以 :十进制码点 语法书写,直接嵌入 Unicode 字符而无须转义序列。码点最多 8 位十进制数字。
:10 # => "\n"
:65 # => "A"
:128512 # => "😀"
Chn 函数可在字符与码点之间互转:传入单个字符返回其码点,传入码点返回对应字符。
Chn["A"]; # => 65
Chn[65]; # => "A"
对象
对象是带类型标签的键值容器,用于结构化数据。
创建与初始化
Object 函数至少需要一个符号作为类型标签。可通过第二个参数传入 {:key, value} 对列表来初始化属性。GetType 可获取对象的类型标签。
Let[obj, Object[Typ]];
Eq[GetType[obj], :Typ]; # => :true
Let[obj2, Object[Another, {
{ :k1, "v1" },
{ :k2, 2 }
}]];
不可变性与更新
KSL 中的对象严格不可变。Set 和 Delete 不会修改原对象,而是返回带有变更的新对象。因此必须重新绑定变量来保留变更。
KSL 提供两种绑定方式:
Let:在当前作用域创建新的局部绑定,遮盖外层同名变量。作用域退出后外层变量不变。Update:修改变量在声明作用域中的已有绑定,变更在当前块结束后仍然有效。
局部覆盖
Let[obj, Object[Typ]];
Block[
Let[obj, Set[obj, :key1, "val1"]],
Print[Has[obj, :key1]] # :true
];
Print[Has[obj, :key1]]; # :false
修改已有绑定
Block[
Update[obj, Set[obj, :key1, "val1"]],
Print[Has[obj, :key1]] # :true
];
Print[Has[obj, :key1]]; # :true
删除属性
Update[obj, Delete[obj, :key1]];
查看与访问
Keys[obj]返回对象所有键的列表。键的顺序未定义。Get[obj, :key]获取指定键的值。
Let[obj, Object[:MyType, {
{ :name, "KSL" },
{ :ver, 1 }
}]];
Keys[obj]; # => { :name, :ver }
Get[obj, :name]; # => "KSL"
列表
列表是以花括号 { } 包围的有序序列,元素以逗号分隔。列表可包含任意类型,支持嵌套。
{ 1, "2", :a3 };
{ 1, 2, { 3, { 4 } } };
函数调用
语法
KSL 仅使用前缀表示法:函数名后跟一对方括号,参数以逗号分隔。无参函数也须书写空方括号。
FunctionName[arg1, arg2]
命名惯例
函数名是普通符号。内置函数名通常以大写字母开头。
匿名函数
匿名函数通过 Fun 构造,可立即调用或绑定到符号以定义命名函数。
# 直接调用
Fun[{ n }, Add[n, 1]][2]; # => 3
# 绑定为命名函数
Let[add1, Fun[{ n }, Add[n, 1]]];
add1[3]; # => 4
Block 表达式
Block 依次执行内部所有表达式,返回最后一个表达式的值。内部表达式以逗号分隔,每个表达式仍以分号结尾。
Block[
Let[x, 10],
Let[y, 20],
Add[x, y]
]; # => 30
# 单表达式 Block
Block[
Mul[3, 7]
]; # => 21
解构绑定
Let 与 Update 支持通过列表一次性绑定多个符号。
Let[{ a, b, c }, { 1, 2, 3 }];
{ a, b, c }; => {1, 2, 3}
注释
KSL 支持三种注释形式:
行注释:# 到行尾的内容为注释。
Let[x, 1]; # 行内注释
# 整行注释
块注释:(* 与 *) 包围的内容为注释,可跨多行,支持嵌套。
Let[x, 1]; (* 块注释 *) Let[y, 2];
(*
多行块注释
(* 嵌套注释 *)
*)
文档注释:(** 与 *) 包围的内容为文档注释,同样可跨多行。
(**
文档注释内容,
可跨越多行。
*)