基于 Lexer (
ScriptLang/Lexer/) 与 Parser (ScriptLang/Parser/) 实现分析及ScriptLang.Demo/Samples/全部示例脚本整理。
SereinScript 是一门基于 .NET 平台的 表达式驱动 动态脚本语言。它采用 Pratt 解析器实现,一切皆为表达式(Expression),没有传统的语句/表达式二分。
源文件 → Lexer (词法分析) → Token列表 → Parser (语法分析) → AST (Expr树) → Compiler -> VM
- 源文件采用 UTF-8 编码
- 大小写敏感
- 标识符支持 Unicode 字母(通过
char.IsLetter())
- 空格
' '、回车'\r'、制表符'\t'被忽略 - 换行
'\n'用于行号计数,不作为语句终止符
仅支持单行注释,以 // 开头至行尾:
// 这是注释
let x = 10 // 这也是注释注意: 不支持多行注释
/* ... */。
标识符规则:以字母 [a-zA-Z]、下划线 _ 或 @ 开头,后续字符可为字母、数字或下划线。
name _private @class counter1 my_function
@前缀用于与 C# 关键字冲突的标识符。
以下为保留关键字,不可用做变量名:
| 关键字 | 用途 |
|---|---|
let |
不可变变量声明 |
var |
可变变量声明 |
if |
条件表达式 |
then |
if 条件分支引导 |
else |
if 条件备选分支 |
when |
模式匹配表达式 |
for |
循环遍历表达式 |
in |
for 循环遍历目标标记 |
return |
返回值表达式 |
import |
模块导入 |
from |
模块路径标记 |
true |
布尔真 |
false |
布尔假 |
null |
空值 |
| Token | 类型 | 说明 |
|---|---|---|
+ |
算术 | 加法 / 字符串拼接 / 数组拼接 / 日期时间运算 |
- |
算术 | 减法 / 一元取负 / 日期时间运算 |
* |
算术 | 乘法 / 字符串重复 |
/ |
算术 | 除法 |
% |
算术 | 取模 |
== |
比较 | 等于 |
!= |
比较 | 不等于 |
< |
比较 | 小于 |
<= |
比较 | 小于等于 |
> |
比较 | 大于 |
>= |
比较 | 大于等于 |
! |
逻辑 | 逻辑非 |
&& |
逻辑 | 逻辑与 |
| ` | ` | |
= |
赋值 | 赋值 / 对象属性定义 |
=> / -> |
函数 | Lambda 箭头 |
. |
成员访问 | 属性/方法访问 |
?. |
成员访问 | 安全空属性/方法访问 |
( ) |
分组 | 括号分组 / 函数调用参数 |
{ } |
分组 | 代码块 / 对象字面量 / import 列表 |
[ ] |
分组 | 数组字面量 / 索引访问 |
, |
分隔 | 参数/元素分隔 |
: |
分隔 | import 别名定义 |
SereinScript 支持多种数值字面量,通过后缀区分类型:
| 后缀 | 类型 | 示例 | 说明 |
|---|---|---|---|
| (无后缀) | int → long → double |
42 |
自动推断:优先 int,超出范围时 long,超大回退到 double |
L / l |
long (Int64) |
42L, 42l |
长整型 |
F / f |
float (Single) |
3.14f |
单精度浮点 |
D / d |
double (Double) |
3.14d, 3.14 |
双精度浮点(默认小数类型) |
M / m |
decimal |
10.5m |
高精度十进制 |
0xFF // 十六进制 → int 255
0b1010 // 二进制 → int 10- 十六进制:
0x/0X前缀 +[0-9a-fA-F]+ - 二进制:
0b/0B前缀 +[01]+ - 十六进制和二进制也支持后缀(如
0xFFL→long)
字符串使用双引号 "..." 包裹,支持转义序列:
"Hello World"
"line1\nline2" // \n 换行
"tab\there" // \t 制表符
"quote: \"text\"" // \" 双引号
"path\\to\\file" // \\ 反斜杠限制: 字符串不可跨行,不支持的转义字符会被原样保留(作为
\+ 字符两个字符)。
true // 布尔真
false // 布尔假
null // 空值[1, 2, 3, 4, 5] // 整数数组
["a", "b", "c"] // 字符串数组
[[1, 2], [3, 4]] // 嵌套数组(矩阵)
[ // 允许换行
{ type = "Button", text = "OK" },
{ type = "TextBlock", text = "Hi" }
]使用 { key = value, ... } 语法创建 Map 对象:
let person = {
name = "Alice",
age = 30,
active = true
}简写形式: 当属性值与同名变量相同时可省略 = value:
let name = "Bob"
let age = 25
let user = { name, age } // 等价于 { name = name, age = age }属性键支持标识符和字符串两种形式:
{
"key-with-dash" = "value",
normalKey = 123
}SereinScript 提供 DateTimeValue 类型用于表示日期时间。由于没有日期字面量语法,需通过内置函数创建:
let now = now() // 当前本地时间 → DateTimeValue
let dt = date("2025-06-15 12:00:00") // 解析字符串 → DateTimeValueDateTimeValue 内部以 UTC 存储,显示时自动转为本地时间。支持 + - 运算符与 TimeSpanValue 进行算术运算(参见 §5.4),通过 . 访问属性(参见 §10.4)。
TimeSpanValue 表示一段时间的长度(差值),通常由 DateTimeValue - DateTimeValue 或 timespan() 函数产生:
let ts = timespan(3, "days") // 3天 → TimeSpanValue
let diff = date("2025-06-20") - date("2025-06-15") // → TimeSpanValue (5天)
diff.totalDays // → 5.0let x = 10 // 不可重新赋值
let add = (a, b) => a + blet 创建的绑定不可被 = 重新赋值。但注意:若值为对象/数组,其内部属性/元素仍可修改。
var count = 0 // 可以重新赋值
count = count + 1
count = 100var 变量可以使用 = 多次赋值。
赋值语句 = 支持以下左值形式:
// 变量赋值
x = 42
// 索引赋值
arr[0] = 100
arr[i + 1] = value
// 成员赋值
obj.name = "NewName"
obj?.child?.prop = value // 安全空成员赋值- 变量在声明所在的代码块(
{...})内可见 - Lambda 闭包捕获外部变量(闭包语义)
- 同作用域内
let/var不可重复声明同名变量
从低到高(Pratt Parser 解析顺序):
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 (最低) | = (赋值) |
右结合 |
| 2 | ` | |
| 3 | && (逻辑与) |
左结合 |
| 4 | ==, != (相等) |
左结合 |
| 5 | <, <=, >, >= (比较) |
左结合 |
| 6 | +, - (加减) |
左结合 |
| 7 | *, /, % (乘除模) |
左结合 |
| 8 | !, - (一元) |
右结合 |
| 9 (最高) | 调用 / 索引 / 成员访问 | 左结合 |
10 + 5 // → 15
10 - 5 // → 5
10 * 5 // → 50
10 / 3 // → 3.333...
10 % 3 // → 1
-10 // → -10(一元取负)"Hello" + " World" // → "Hello World"(拼接)
"abc" * 3 // → "abcabcabc"(重复)[1, 2] + [3, 4] // → [1, 2, 3, 4](拼接)DateTimeValue 和 TimeSpanValue 支持 + - 运算符:
// DateTimeValue + TimeSpanValue → DateTimeValue
date("2025-06-01") + timespan(7, "days") // → 2025/06/08 00:00:00
// TimeSpanValue + DateTimeValue → DateTimeValue (交换律)
timespan(12, "hours") + now() // → 12小时后的时间
// TimeSpanValue + TimeSpanValue → TimeSpanValue
timespan(1, "days") + timespan(6, "hours") // → 1.06:00:00
// DateTimeValue - TimeSpanValue → DateTimeValue
date("2025-06-15") - timespan(5, "days") // → 2025/06/10 00:00:00
// DateTimeValue - DateTimeValue → TimeSpanValue
date("2025-06-20") - date("2025-06-15") // → TimeSpanValue (5天)
// TimeSpanValue - TimeSpanValue → TimeSpanValue
timespan(3, "days") - timespan(1, "days") // → 2.00:00:00比较运算符同样支持:
dt1 == dt2 // 等于(同一时刻)
dt1 != dt2 // 不等于
dt1 > dt2 // 晚于
dt1 >= dt2 // 不早于
dt1 < dt2 // 早于
dt1 <= dt2 // 不晚于a == b // 等于
a != b // 不等于
a < b // 小于
a <= b // 小于等于
a > b // 大于
a >= b // 大于等于!flag // 逻辑非
a && b // 逻辑与(短路求值)
a || b // 逻辑或(短路求值)person.name // 属性访问
person?.address?.city // 安全空属性访问(任一中间值为 null 则返回 null)
array.length // 内置属性
"hello".toUpper() // 方法调用arr[0] // 数组索引
arr[i + 1] // 表达式索引
matrix[0][1] // 嵌套索引add(1, 2) // 普通调用
makeAdder(1)(2)(3) // 链式调用(柯里化)
someFunction(arg1, arg2, arg3) // 多参数调用(a + b) * c // 改变默认优先级Lambda 使用 => 或 -> 箭头:
(param1, param2, ...) => body
param => body // 单参数省略括号
() => body // 无参数
let add = (a, b) => a + b
let greet = (name) => "Hello " + name
let pi = () => 3.14159// 表达式体(单行,隐式返回)
(a, b) => a + b
// 块体(多行,最后一个表达式为返回值)
(n) => {
var result = 1
for i in range(1, n + 1) {
result = result * i
}
result // 此行作为返回值
}Lambda 自动捕获外部变量(闭包语义):
let makeCounter = () => {
var count = 0
() => {
count = count + 1
count
}
}
let c1 = makeCounter()
c1() // 1
c1() // 2let add = (x) => (y) => (z) => x + y + z
add(1)(2)(3) // → 6
let add2 = add(10)(20)
add2(30) // → 60函数可作为参数和返回值:
let map = (arr, fn) => {
var result = []
for item in arr {
result = result + [fn(item)]
}
result
}
map([1, 2, 3, 4], x => x * x) // → [1, 4, 9, 16]对象的属性值可以是函数(Lambda),模拟面向对象的方法调用:
let ui = {
type = "Button",
onClick = () => counter.value = counter.value + 1
}
ui.onClick()if 是表达式,始终有返回值:
// 形式一:含 then 关键字
if condition then thenExpr else elseExpr
// 形式二:省略 then(直接跟表达式或块)
if condition thenExpr else elseExpr
// 形式三:块体
if x > 0 then {
print("positive")
x
} else {
print("non-positive")
0
}// 三元风格
let status = if score > 60 then "pass" else "fail"
// 多分支 (else if)
if x > 0 then "positive"
else if x < 0 then "negative"
else "zero"注意:
else分支可省略,此时默认返回null。
使用 when 进行模式匹配,类似其他语言的 match / switch:
when value {
pattern1 => body1, // 逗号分隔多个子句
pattern2 => body2,
_ => defaultBody // _ 为通配符/其他分支
}特性:
- 每个子句由
pattern => body组成 - 子句间用逗号分隔
_或 Lambda 作为通配符匹配- body 可以是表达式或块
{...}
let value = 25
when value {
1 => print("one"),
2 => print("two"),
_ => print("other")
}// 模式匹配与块体
when typeof(item) {
"array" => {
var items = []
for sub in item { items = items + [toJson(sub)] }
"[" + items.join(",") + "]"
},
"object" => { /* ... */ },
_ => item.toString()
}for varName in iterable body// 遍历数组
for item in [10, 20, 30] {
print("item:", item)
}
// 遍历 range
for i in range(0, 100) {
sum = sum + i
}
// 遍历对象的键
for k in keys(obj) {
print("key:", k)
}注意:
for是表达式,但其返回值通常为最后一个迭代的 body 值。
return expr // 带返回值
return // 返回 nullreturn 用于提前退出函数体。
代码块 { ... } 是一个表达式,其值为最后一个表达式的值:
{
let a = 1
let b = 2
a + b // 代码块的值 → 3
}let arr = [1, 2, 3, 4, 5]
// 访问
arr[0] // 1
arr.length // 5
// 拼接
arr + [6, 7] // [1, 2, 3, 4, 5, 6, 7]
// 索引赋值
arr[0] = 100内置属性/方法:
.length— 数组长度.add(item)— 添加元素.count— 元素数量
let obj = {
key1 = "value1",
key2 = 123,
method = () => key1 + "!"
}
// 属性访问
obj.key1 // "value1"
obj["key2"] // 123
// 深层访问
obj?.nested?.value // 安全空访问
// 属性赋值
obj.key3 = "newValue"
// 方法调用
obj.method()内置属性/方法:
.has(key)— 判断键是否存在.keys— 返回键列表
import { member1, member2 } from "path/file.script"
import { member1 : alias1, member2 } from "relative/path.script"- 文件路径使用字符串,相对于当前脚本位置解析
- 支持成员别名:
member : alias(使用冒号) - 源文件最后的返回值(通过
return或顶层表达式)作为被导入的模块对象 - 被导入模块通过
{ member1, member2 }解构获取成员
定义模块 (pinia.script):
let createStore = (options) => {
// ...
}
return { createStore }导入模块 (test-import.script):
import { createStore } from "pinia.script"
let store = createStore({ /* ... */ })导入带别名 (look.script):
import { store } from "test-import.script"
let look = () => {
print("x: ", store.state.x)
}
return { look }run-import.script
├── look.script
│ └── test-import.script
│ └── pinia.script
└── test-import.script (直接导入)
以下内置函数在示例中被使用,运行时环境提供:
| 函数 | 签名 | 说明 |
|---|---|---|
print |
print(arg1, arg2, ...) |
输出到控制台,支持多参数,自动空格分隔 |
| 函数 | 签名 | 说明 |
|---|---|---|
typeof |
typeof(value) |
返回值的类型字符串:"int", "string", "boolean", "null", "array", "object", "function", "datetime", "timespan" 等 |
bool |
bool(value) |
转换为布尔值 |
double |
double(value) |
转换为 double |
| 函数 | 签名 | 说明 |
|---|---|---|
range |
range(start, end) |
生成从 start 到 end-1 的整数序列(用作 for 迭代) |
keys |
keys(obj) |
返回对象的所有键列表 |
len |
len(collection) |
返回集合的长度 |
| 函数 | 签名 | 说明 |
|---|---|---|
now |
now() |
返回当前本地时间,类型为 DateTimeValue。示例:2026/06/11 13:30:00 |
date |
date(str) |
解析日期字符串为 DateTimeValue。无时区标记按本地时间处理。示例:date("2025-02-05 12:00:12") |
timespan |
timespan(num, unit) |
从数量+单位构造 TimeSpanValue。unit 支持:"days", "hours", "minutes", "seconds", "milliseconds", "ticks" |
DateTimeValue 和 TimeSpanValue 通过 + - 运算符实现算术运算:
let dt = date("2025-06-15")
let ts = timespan(3, "days")
let later = dt + ts // → DateTimeValue (3天后)
let sooner = dt - ts // → DateTimeValue (3天前)
let dt2 = date("2025-06-20")
let diff = dt2 - dt // → TimeSpanValue (日期差)
diff.totalDays // → 5.0
// 比较
dt2 > dt // → true| 成员 | 类型 | 说明 |
|---|---|---|
.year |
int |
年 (1-9999) |
.month |
int |
月 (1-12) |
.day |
int |
日 (1-31) |
.hour |
int |
小时 (0-23) |
.minute |
int |
分钟 (0-59) |
.second |
int |
秒 (0-59) |
.millisecond |
int |
毫秒 (0-999) |
.dayOfWeek |
int |
星期几 (0=周日, 6=周六) |
.dayOfYear |
int |
一年中的第几天 (1-366) |
.ticks |
long |
Tick 计数 (100ns 单位) |
.toString() |
string |
默认格式 yyyy/MM/dd HH:mm:ss |
.toString(format) |
string |
自定义格式,如 d.toString("yyyy-MM-dd") |
| 成员 | 类型 | 说明 |
|---|---|---|
.days |
int |
天数部分(整数) |
.hours |
int |
小时部分 (0-23) |
.minutes |
int |
分钟部分 (0-59) |
.seconds |
int |
秒部分 (0-59) |
.milliseconds |
int |
毫秒部分 (0-999) |
.totalDays |
double |
总天数(含小数) |
.totalHours |
double |
总小时数(含小数) |
.totalMinutes |
double |
总分钟数(含小数) |
.totalSeconds |
double |
总秒数(含小数) |
.totalMilliseconds |
double |
总毫秒数(含小数) |
.ticks |
long |
Tick 计数 (100ns 单位) |
.toString() |
string |
TimeSpan 标准格式 |
.toString(format) |
string |
自定义格式 |
| 函数/方法 | 说明 |
|---|---|
.split(delimiter) |
字符串分割 |
.join(separator) |
数组元素拼接为字符串 |
.toUpper() |
字符串转大写 |
.toString() |
转换为字符串表示 |
.has(key) |
对象是否包含指定键 |
.add(item) |
数组添加元素 |
.last() |
获取数组最后一个元素 |
.toList() |
转换为列表 |
SereinScript 嵌入 .NET 运行时,可直接与 CLR 对象交互:
let person = new_Person() // 创建 .NET 对象(具体工厂函数由宿主提供)
person.Name = "张三" // CLR 属性写入
person.Age = 18
let name = person.Name // CLR 属性读取
let nextAge = person.AddYears(1) // CLR 方法调用
let greet = person.Greet()Token:
LeftParen → (
RightParen → )
LeftBrace → {
RightBrace → }
LeftBracket → [
RightBracket → ]
Comma → ,
Dot → .
QuestionDot → ?.
Colon → :
Semicolon → ;
Plus → +
Minus → -
Star → *
Slash → /
Percent → %
Equal → =
EqualEqual → ==
BangEqual → !=
Less → <
LessEqual → <=
Greater → >
GreaterEqual → >=
Bang → !
And → &&
Or → ||
Arrow → => (或 ->)
Let → let 关键字
Var → var 关键字
If → if 关键字
Then → then 关键字
Else → else 关键字
When → when 关键字
For → for 关键字
In → in 关键字
Return → return 关键字
Import → import 关键字
From → from 关键字
Number_Int → 整数字面量 (Int32)
Number_Long → 长整数字面量 (Int64)
Number_Float → 单精度浮点字面量 (Single)
Number_Double → 双精度浮点字面量 (Double/默认小数)
Number_Decimal → 高精度十进制字面量 (Decimal)
String → 字符串字面量
True → true
False → false
Null → null
Identifier → 标识符
EOF → 文件结束
Unknown → 未知 token (错误)
Expr =
| ErrorExpr (错误占位节点)
| LiteralExpr (字面量: int/long/float/double/decimal/string/bool/null)
| IdentifierExpr (标识符引用)
| LetExpr (let 声明: let name = Value)
| VarExpr (var 声明: var name = Value)
| AssignExpr (变量赋值: name = Value)
| IndexAssignExpr (索引赋值: Target[Index] = Value)
| MemberAssignExpr (成员赋值: Target.Prop = Value)
| BinaryExpr (二元运算: Left Op Right)
| UnaryExpr (一元运算: Op Expr)
| ConditionalExpr (三元条件, 保留节点)
| ReturnExpr (返回: return Expr?)
| IfExpr (条件: if Cond then Then else Else)
| WhenExpr (模式匹配: when Value { clauses })
| WhenClause (when 子句: Pattern => Body)
| ForExpr (循环: for VarName in Iterable Body)
| LambdaExpr (Lambda: Params => Body)
| CallExpr (函数调用: Target(Args...))
| BlockExpr (代码块: { Statements... })
| ArrayLiteralExpr (数组: [Elements...])
| ObjectLiteralExpr(对象: { Properties... })
| ObjectProperty (对象属性: Key = Value)
| MemberAccessExpr (成员访问: Target.Property)
| IndexAccessExpr (索引访问: Target[Index])
| ImportStmt (导入: import { members } from "path")
| ProgramExpr (程序根节点: Statements...)
(* ===== 顶层 ===== *)
Program := (Declaration | Expression)* EOF
(* ===== 声明 ===== *)
Declaration := LetDeclaration
| VarDeclaration
| ImportDeclaration
| ReturnStatement
LetDeclaration := "let" Identifier "=" Expression
VarDeclaration := "var" Identifier "=" Expression
ReturnStatement := "return" Expression?
ImportDeclaration := "import" "{" ImportMembers "}" "from" StringLiteral
ImportMembers := ImportMember ("," ImportMember)*
ImportMember := Identifier (":" Identifier)?
(* ===== 表达式(按优先级递降) ===== *)
Expression := Assignment
Assignment := Or ("=" Assignment)?
(* 左值限制:Identifier / IndexAccess / MemberAccess *)
Or := And ("||" And)*
And := Equality ("&&" Equality)*
Equality := Comparison (("==" | "!=") Comparison)*
Comparison := Term (("<" | "<=" | ">" | ">=") Term)*
Term := Factor (("+" | "-") Factor)*
Factor := Unary (("*" | "/" | "%") Unary)*
Unary := ("!" | "-") Unary | Call
Call := Primary
( "(" Args? ")" (* 函数调用 *)
| "[" Expression "]" (* 索引访问 *)
| ("." | "?.") Identifier (* 成员访问 *)
)*
(* ===== 原子表达式 ===== *)
Primary := NumericLiteral
| StringLiteral
| "true" | "false" | "null"
| Identifier ( "=>" LambdaBody )? (* 单参数 Lambda *)
| "(" LambdaOrGroup ")"
| "{" ObjectLiteral "}"
| "[" ArrayLiteral "]"
| "if" Expression ("then")? (Block | Expression)
("else" (Block | Expression))?
| "when" Expression "{" WhenClauses "}"
| "for" Identifier "in" Expression (Block | Expression)
(* ===== Lambda ===== *)
LambdaOrGroup := ")" "=>" LambdaBody (* 无参数 Lambda *)
| Identifier ("," Identifier)* ")" "=>" LambdaBody (* 多参数 Lambda *)
| Expression ")" (* 普通括号分组 *)
LambdaBody := Block | Expression
(* ===== 数据结构 ===== *)
ArrayLiteral := (Expression ("," Expression)*)?
ObjectLiteral := (ObjectProperty ("," ObjectProperty)*)?
ObjectProperty := (Identifier | StringLiteral) ("=" Expression)?
(* ===== 控制流 ===== *)
WhenClauses := WhenClause ("," WhenClause)*
WhenClause := Expression "=>" (Block | Expression)
Block := "{" Statement* "}"
Statement := Declaration | Expression
Args := Expression ("," Expression)*
(* ===== 词法 ===== *)
NumericLiteral := [0-9]+ ("." [0-9]+)? Suffix?
| "0x" [0-9a-fA-F]+ Suffix?
| "0b" [01]+ Suffix?
Suffix := [lLfFdDmM]
StringLiteral := "\"" (任何非引号字符 | 转义序列)* "\""
Identifier := [a-zA-Z_@][a-zA-Z0-9_]*| 文件 | 涵盖特性 |
|---|---|
1.1-基础运算.script |
算术运算、字符串操作、内置方法 |
1.2-变量声明.script |
var 声明与变量重赋值 |
1.3-字符串操作.script |
字符串拼接 +、重复 * |
1.4-函数.script |
Lambda 定义与调用 |
1.5-对象.script |
对象字面量、属性访问、嵌套 |
1.6-复杂对象.script |
对象方法、Lambda 属性、链式调用 |
1.7-数组.script |
数组字面量、索引访问、拼接 |
1.8-数值数据类型.script |
完整数值类型、跨类型比较 |
2.1-逻辑运算.script |
&&, ||, ! |
2.2-条件表达式.script |
if-then-else 表达式风格 |
2.3-循环.script |
for-in、range()、keys() |
2.4-模式匹配.script |
when 表达式 |
3.1-闭包.script |
闭包、柯里化、高阶闭包、动态函数 |
3.2-高阶函数.script |
函数作为参数 |
3.3-递归.script |
递归函数(阶乘) |
3.4-快速排序.script |
递归、数组操作、算法实现 |
3.5-矩阵运算.script |
嵌套数组与矩阵加法 |
4.1-CLR对象.script |
CLR 对象互操作 |
test-*.script |
闭包递归、深层递归、极限递归、相互递归、栈深度测试、闭包内存 |
高级/linq/*.script |
复杂对象、链式 API、Linq 风格实现 |
高级/pinia/*.script |
模块导入、状态管理、闭包模式 |
文档版本: 1.0
生成日期: 2026-06-06
基于源码:ScriptLang/Lexer/Lexer.cs,ScriptLang/Lexer/TokenType.cs,ScriptLang/Parser/Parser.cs,ScriptLang/Parser/Ast.cs
示例来源:ScriptLang.Demo/Samples/(1-4 级 + test + 高级)
