# dlang **Repository Path**: da9527/dlang ## Basic Information - **Project Name**: dlang - **Description**: 用来学习编译原理 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-30 - **Last Updated**: 2026-05-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DLang DLang 是一个用 Go 语言实现的动态类型脚本语言解释器,设计简洁,支持丰富的内置类型、控制流(if/while/for/for-in)、函数、闭包、异常处理、模块系统和标准库。 ## 快速开始 ### 运行脚本文件 ```bash go run . examples/hello.dl # 或构建后运行 go build -o dlang.exe . ./dlang.exe my_script.dl ``` 脚本文件必须以 `.dl` 为扩展名。 示例脚本 `hello.dl`: ```dlang println("Hello, DLang!"); return 0; ``` ### 启动 REPL 不带任何参数直接运行即可进入交互式解释器: ```bash go run . ``` --- ## REPL 交互环境 REPL 支持以下功能: - **历史记录**:自动保存最近 500 条命令到 `~/.dlang_history`,支持 `!!`(重复上一条)和 `!n`(执行第 n 条历史)。 - **多行输入**:在行尾加 `\` 续行,未闭合的 `{}、()、[]` 会自动进入续行模式。 - **内置命令**(以 `:` 开头): | 命令 | 说明 | | ---------------------------- | ------------------------ | | `:help`, `:h` | 显示帮助 | | `:exit`, `:quit`, `:q` | 退出 REPL | | `:clear`, `:cls` | 清屏 | | `:history` | 显示命令历史 | | `:env` | 显示当前环境中的变量 | | `:load <文件.dl>` | 加载并执行外部脚本 | | `:type <表达式>` | 显示表达式的类型 | --- ## 语言参考 ### 注释 ```dlang // 单行注释 /* 多行注释 可以跨越多行 */ ``` ### 字面量与数据类型 | 类型 | 示例 | 说明 | | ---------- | ----------------------------- | ------------------------ | | 整数 | `42`, `-7` | 64 位有符号整数 | | 浮点数 | `3.14`, `2.0`, `-0.5` | 64 位浮点数 | | 布尔值 | `true`, `false` | | | 字符 | `'a'`, `'\n'` | Unicode 字符,支持转义 | | 字符串 | `"hello"`, `"line\nbreak"` | 支持转义:`\n \t \r \" \\` | | 空值 | `null` | | | 数组 | `[1, 2, 3]` | 元素可以是任意类型 | | 哈希表 | `{"name": "Alice", "age": 30}` | 键支持整数、浮点、字符串、布尔、字符 | | 异常对象 | `Error("Type", "msg")` | 可抛出的异常,支持 cause 链和位置信息 | ### 变量与赋值 ```dlang let x = 10; let name = "DLang"; let flag = true; // 重新赋值(变量必须已经声明) x = x + 5; ``` #### 解构赋值 `let` 支持数组解构和对象解构,将右侧值按结构拆解并绑定到左侧变量: ```dlang // 数组解构:按索引顺序绑定 let [a, b] = [1, 2]; // a = 1, b = 2 let [x, y, z] = [10, 20, 30]; // x = 10, y = 20, z = 30 // 右侧可以是任意表达式,包括函数调用和变量引用 let arr = [7, 8, 9]; let [p, q, r] = arr; // p = 7, q = 8, r = 9 fn getArr() { return [5, 10, 15]; } let [u, v, w] = getArr(); // u = 5, v = 10, w = 15 ``` 数组解构时,若变量数量多于数组元素,剩余变量绑定为 `null`;若变量数量少于数组元素,多余元素被忽略。 ```dlang // 对象解构:按属性名提取 let user = {"name": "小明", "age": 18}; let { name, age } = user; // name = "小明", age = 18 let h = {"x": 10, "y": 20}; let { x, y } = h; // x = 10, y = 20 ``` 对象解构时,左侧变量名即为要提取的属性名。若属性不存在,绑定为 `null`。 解构可以和索引赋值组合使用: ```dlang let data = {"items": [10, 20, 30]}; let { items } = data; let [first, second] = items; // first = 10, second = 20 ``` 数组和哈希表支持索引赋值: ```dlang let arr = [1, 2, 3]; arr[0] = 99; // arr 变为 [99, 2, 3] let map = {"a": 1}; map["b"] = 2; // 新增键值对 ``` ### 自增与自减 ```dlang let i = 0; i++; // i = 1 i--; // i = 0 ``` ### 复合赋值运算符 ```dlang x += 1; // x = x + 1 x -= 1; // x = x - 1 x *= 2; // x = x * 2 x /= 2; // x = x / 2 x %= 3; // x = x % 3 x &&= y; // x = x && y x ||= y; // x = x || y ``` ### 运算符 #### 算术运算符 `+` `-` `*` `/` `%` 整数与整数运算结果为整数,包含浮点数时结果为浮点数。除法或取模除数为 0 会报错。 #### 比较运算符 `==` `!=` `<` `>` `<=` `>=` 浮点数比较使用容差(`1e-9`)。不同类型的值比较通常返回 `false`,数值之间可跨整数/浮点比较。 #### 逻辑运算符 `&&` `||` `!` 支持**短路求值**: ```dlang false && (1 / 0) // 不会计算右侧 true || (1 / 0) // 不会计算右侧 ``` #### 字符串拼接 `+` 运算符可用于字符串之间,以及字符串与其他类型的拼接(自动转换): ```dlang "Hello " + "World" // "Hello World" "Value: " + 42 // "Value: 42" true + " is true" // "true is true" ``` #### 数组拼接 `+` 也可以拼接两个数组,返回新数组: ```dlang [1, 2] + [3, 4] // [1, 2, 3, 4] ``` #### 索引运算符 `[]` 用于数组、字符串和哈希表的读取,下标由`0`开始: ```dlang let arr = [10, 20, 30]; arr[1] // 20 let s = "hello"; s[0] // 'h'(返回字符) let map = {"x": 100}; map["x"] // 100 ``` #### in 运算符 `in` 用于判断元素是否在数组、字符串或哈希表中: ```dlang 3 in [1, 2, 3] // true "b" in "abc" // true "key" in {"key": 1} // true ``` #### 切片运算符 对数组和字符串进行切片操作,返回新数组或新字符串: ```dlang let arr = [10, 20, 30, 40]; arr[1:3] // [20, 30] (索引 1 到 2,不包含 3) arr[:2] // [10, 20] (从开头到索引 1) arr[1:] // [20, 30, 40] (从索引 1 到末尾) let s = "hello"; s[1:4] // "ell" ``` #### 运算符优先级(从低到高) 1. `=` `+=` `-=` `*=` `/=` `%=` `&&=` `||=`(赋值) 2. `||` 3. `&&` 4. `==` `!=` 5. `<` `>` `<=` `>=` 6. `in` 7. `+` `-` 8. `*` `/` `%` 9. `!` `-`(前缀)/ 函数调用 `()` / 索引 `[]` / 属性访问 `.` --- ### 控制流 #### if / else ```dlang if (x > 10) { return "big"; } else if (x > 5) { return "medium"; } else { return "small"; } ``` 条件可以是任意表达式,按照真假规则判断:`false`、`0`、`0.0`、`""`、`null` 为假,其余为真。 #### while 循环 ```dlang let i = 0; while (i < 5) { println(i); i++; } ``` #### for 循环 ```dlang // 完整形式 for (let i = 0; i < 5; i++) { println(i); } // 省略初始化 let i = 0; for (; i < 5; i++) { println(i); } // 省略条件(无限循环) for (;;) { // ... } ``` 初始化可以是 `let`、赋值、自增/自减语句;更新部分可以是赋值、自增、自减。 #### for-in 循环 遍历数组、字符串或哈希表: ```dlang let arr = [10, 20, 30]; for (let x in arr) { println(x); } // 声明新变量:x = 10, 20, 30 for (let c in "hello") { print(c); } // 遍历字符串:'h' 'e' 'l' 'l' 'o' let map = {"a": 1, "b": 2}; for (let k in map) { println(k); } // 遍历哈希表键:a, b // 使用已存在变量(省略 let),循环结束后变量保留最后一个迭代值 let x = 0; for (x in [10, 20, 30]) { } // x = 30 ``` #### break 与 continue 适用于 `while`、`for` 和 `for-in` 循环: ```dlang for (let i in [1, 2, 3, 4, 5]) { if (i == 4) { break; } // 退出循环 if (i % 2 == 0) { continue; } // 跳过本次迭代 println(i); // 输出:1 3 } ``` #### return ```dlang fn add(a, b) { return a + b; } ``` `return` 只能在函数体内使用,值会被包装并向外传递。 #### try-catch-finally 与 throw DLang 支持异常处理,使用 `try-catch-finally` 语句和 `throw` 关键字: ```dlang // 基本用法 try { throw "something went wrong"; } catch (e) { println(e.TypeName); // "Error" println(e.Message); // "something went wrong" } ``` `finally` 块始终执行,即使 `try` 中有 `return` 或 `throw`: ```dlang try { return 1; } finally { println("done"); } // 输出 "done",返回 1 // catch 和 finally 可以组合使用 try { risky(); } catch (e) { println(e.Message); } finally { cleanup(); // 始终执行 } ``` `throw` 可以抛出任意值,会被自动包装为 `ErrorObject`: ```dlang let err = Error("TypeError", "expected int"); throw err; ``` `ErrorObject` 属性: - `.TypeName` - 异常类型标识(如 "Error"、"TypeError") - `.Message` - 异常消息 - `.Cause` - 嵌套的上游异常(可为 null) - `.Line`、`.Col` - 抛出位置的行列号 - `.SourceLine` - 抛出位置的源码行 #### 异常链(cause) ```dlang try { try { throw Error("Inner", "inner error"); } catch (e) { throw Error("Outer", "outer error", e); } } catch (e) { println(e.Cause.Message); // "inner error" } ``` 未捕获的异常会输出包含位置信息和 cause 链的友好错误消息。 --- ### 属性访问 使用 `.` 运算符可以访问对象的属性: ```dlang // ErrorObject 属性访问 let e = Error("Test", "msg"); e.TypeName; // "Test" e.Message; // "msg" // 等价于使用索引语法 e["TypeName"]; // "Test" ``` 目前属性访问主要用于 `ErrorObject` 和哈希表。 #### 方法调用 使用 `obj.method(args)` 语法可以链式调用函数,它是 `method(obj, args)` 的语法糖: ```dlang import { map, filter } from "lib/array.dl"; fn double(x) { return x * 2; } fn isEven(x) { return x % 2 == 0; } [1, 2, 3, 4, 5].filter(isEven).map(double); // [4, 8] ``` 方法名在调用时从当前作用域中查找,因此需要先通过 `import` 将函数引入作用域。属性访问(无括号)仍等价于索引:`obj.name` 等价于 `obj["name"]`。 --- ### 函数 #### 定义与调用 ```dlang // 函数声明(相当于 let add = fn(...)) fn add(a, b) { return a + b; } // 函数表达式 let multiply = fn(a, b) { return a * b; }; add(2, 3); // 5 multiply(4, 5); // 20 ``` #### 参数默认值 函数参数可以指定默认值,调用时如果参数缺失或传入 `null` 则使用默认值: ```dlang fn greet(name, greeting = "Hello") { return greeting + ", " + name; } greet("World"); // "Hello, World" greet("World", "Hi"); // "Hi, World" fn inc(a, b = 1) { return a + b; } inc(5); // 6 inc(5, 3); // 8 inc(5, null); // 6(null 触发默认值) ``` #### 递归 ```dlang fn factorial(n) { if (n <= 1) { return 1; } return n * factorial(n - 1); } factorial(5); // 120 ``` #### 闭包 函数捕获定义时的环境: ```dlang fn makeAdder(x) { return fn(y) { return x + y; }; } let add5 = makeAdder(5); add5(3); // 8 ``` #### 函数作为一等公民 函数可以赋值给变量、作为参数传递、存储在数组或哈希表中: ```dlang let f = fn(x) { return x * 2; }; let arr = [f]; arr[0](3); // 6 fn apply(func, val) { return func(val); } apply(f, 10); // 20 ``` --- ### 复合类型 #### 数组 ```dlang let a = [1, 2, 3, 4]; len(a); // 4 push(a, 5); // 返回新长度 5,a 变为 [1,2,3,4,5] pop(a); // 5,a 变为 [1,2,3,4] a[0]; // 1 ``` #### 哈希表 ```dlang let h = {"name": "DLang", "version": 1}; h["name"]; // "DLang" h["year"] = 2025; // 添加新键 len(keys(h)); // 3 ``` 键可以是整数、浮点数、字符串、布尔值或字符。不同类型的相同数值视为不同键(如 `1` 和 `1.0`)。 #### 列表推导式 简洁地根据已有可迭代对象创建新数组,支持可选 `if` 过滤条件: ```dlang [x * 2 for x in [1, 2, 3]] // [2, 4, 6] [x for x in range(10) if x % 2 == 0] // [0, 2, 4, 6, 8] [toUpperCase(s) for s in ["a", "b"]] // [A, B] ``` 可迭代对象可以是数组、字符串或哈希表(遍历键)。循环变量作用域限于推导式内部,不会泄漏到外部。 #### 字典推导式 根据可迭代对象生成新哈希表: ```dlang {k: len(k) for k in ["a", "bc"]} // {a: 1, bc: 2} {k: k * 10 for k in [1, 2, 3, 4] if k % 2 == 0} // {2: 20, 4: 40} // 结合 toPairs 遍历哈希表键值对 let h = {"a": 1, "b": 2}; let p = require("lib/hash.dl"); {pair[0]: pair[1] * 2 for pair in p["toPairs"](h)} // {a: 2, b: 4} ``` --- ### 模块系统 DLang 支持从外部 `.dl` 文件导入模块。模块文件中的顶层 `let` 绑定和函数声明会被导出,内置函数不会被导出。 #### 命名导入 ```dlang import { PI, add } from "./math.dl"; println(add(PI, 2)); ``` 支持 `as` 别名: ```dlang import { add as sum } from "./utils.dl"; ``` #### 命名空间导入 导入整个模块为一个哈希表: ```dlang import * as math from "./math.dl"; math["add"](1, 2); ``` #### require 函数 内置函数 `require` 在运行时加载模块并返回模块哈希表: ```dlang let m = require("./math.dl"); m["add"](1, 2); ``` 模块加载结果会被缓存,多次导入同一个文件共享同一个模块对象。 --- ### 内置函数 以下函数无需导入,可在任何地方直接使用: | 函数 | 说明 | | ----------------------------- | -------------------------------------------------------------------- | | `print(arg1, arg2, ...)` | 打印参数,空格分隔,不换行 | | `println(arg1, arg2, ...)` | 打印参数,空格分隔,自动换行 | | `len(v)` | 返回字符串或数组的长度 | | `type(v)` | 返回类型名称字符串 | | `int(v)` | 转换为整数,失败返回 `null` | | `float(v)` | 转换为浮点数,失败返回 `null` | | `str(v)` | 转换为字符串(等价于 `Inspect()`) | | `bool(v)` | 按真假规则转换为布尔值 | | `push(array, value)` | 向数组末尾追加元素,返回新长度 | | `pop(array)` | 移除并返回数组最后一个元素,空数组返回 `null` | | `keys(hash)` | 返回哈希表所有键的数组 | | `values(hash)` | 返回哈希表所有值的数组 | | `range(stop)` | 返回 `[0, 1, ..., stop-1]` 整数数组 | | `range(start, stop)` | 返回 `[start, ..., stop-1]` | | `range(start, stop, step)` | 按步长生成数组(步长可为负数) | | `split(str, delimiter)` | 分割字符串,返回字符串数组 | | `join(array, delimiter)` | 用分隔符连接数组元素成字符串 | | `sqrt(num)` | 平方根(参数必须为非负数) | | `random()` | 返回 `[0, 1)` 的随机浮点数 | | `random(max)` | 返回 `[0, max)` 的随机整数(max 整数)或随机浮点数(max 浮点数) | | `require(path)` | 加载模块并返回模块哈希表 | | `Error(type, msg, cause?)` | 创建异常对象(type、message 为字符串,cause 可选) | | `now()` | 返回当前 Unix 时间戳(秒,浮点数,微秒精度) | | `sleep(ms)` | 暂停执行指定毫秒数(接受整数或浮点数) | | `formatTime(timestamp, format)` | 将时间戳格式化为字符串,支持占位符(见下方) | | `readFile(path)` | 读取文件内容,返回字符串 | | `writeFile(path, content)` | 写入内容到文件 | | `args()` | 返回命令行参数数组 | #### formatTime 格式占位符 `formatTime` 函数支持以下格式占位符: | 占位符 | 说明 | 示例输出 | | ------ | -------------- | -------- | | `yyyy` | 4位年份 | 2026 | | `yy` | 2位年份 | 26 | | `MM` | 2位月份 | 01-12 | | `M` | 1-2位月份 | 1-12 | | `dd` | 2位日期 | 01-31 | | `d` | 1-2位日期 | 1-31 | | `HH` | 24小时(补零) | 00-23 | | `H` | 24小时 | 0-23 | | `hh` | 12小时(补零) | 01-12 | | `h` | 12小时 | 1-12 | | `mm` | 2位分钟 | 00-59 | | `m` | 分钟 | 0-59 | | `ss` | 2位秒 | 00-59 | | `s` | 秒 | 0-59 | | `SSS` | 毫秒 | 000-999 | | `a` | AM/PM | AM/PM | | `E` | 星期缩写 | Mon-Sun | | `EEEE` | 完整星期 | Monday | 示例: ```dlang formatTime(now(), "yyyy/MM/dd HH:mm:ss"); // "2026/05/11 17:30:00" formatTime(0, "yyyy-MM-dd"); // "1970-01-01" formatTime(now(), "EEEE"); // "Monday" ``` --- ### 标准库 DLang 附带一组用自身语言编写的标准库,位于 `lib/` 目录下。使用 `import` 导入: #### `lib/string.dl` | 导出函数 | 说明 | | --------------------------------------- | ------------------------ | | `trim(s)` | 移除首尾空白字符 | | `startsWith(s, prefix)` | 是否以指定前缀开始 | | `endsWith(s, suffix)` | 是否以指定后缀结束 | | `replace(s, old, new)` | 替换子串(全部替换) | | `repeat(s, count)` | 重复字符串 | | `padStart(s, len, pad)` | 左侧填充至指定长度 | | `padEnd(s, len, pad)` | 右侧填充至指定长度 | | `toUpperCase(s)` | 转为大写 | | `toLowerCase(s)` | 转为小写 | #### `lib/array.dl` | 导出函数 | 说明 | | --------------------------------------- | -------------------------------- | | `map(arr, fn)` | 对每个元素应用函数,返回新数组 | | `filter(arr, fn)` | 过滤出函数返回真的元素 | | `reduce(arr, fn, initial)` | 折叠聚合 | | `find(arr, fn)` | 返回第一个满足条件的元素 | | `every(arr, fn)` | 是否所有元素都满足条件 | | `some(arr, fn)` | 是否存在元素满足条件 | | `unique(arr)` | 数组去重(保持顺序) | | `flatten(arr)` | 拍平嵌套数组(递归) | | `slice(arr, start, end)` | 切片 | | `reverse(arr)` | 反转数组 | | `sort(arr)` | 排序(整数/浮点数/字符串) | #### `lib/function.dl` | 导出函数 | 说明 | | ---------------------------------- | -------------------------------- | | `compose(f, g)` | 函数组合 `f(g(x))` | | `pipe(fns)` | 从左到右管道组合 | | `curry2(fn)` | 将二元函数柯里化 | | `memoize(fn)` | 带缓存的记忆化函数 | | `fib` | 斐波那契数函数(供 memoize 示例)| #### `lib/iterator.dl` | 导出函数 | 说明 | | ---------------------------------- | ------------------------------------------ | | `enumerate(arr)` | 返回 `[[index, value], ...]` 的数组 | | `zip(arr1, arr2)` | 将两个数组对应位置元素成对组合,返回数组 | #### `lib/hash.dl` | 导出函数 | 说明 | | ---------------------------------- | ------------------------------------------ | | `hasKey(hash, key)` | 检查哈希表是否包含指定键 | | `get(hash, key, default?)` | 安全获取值,可设置默认值 | | `merge(a, b)` | 合并两个哈希表,后者覆盖前者同名键 | | `invert(hash)` | 反转键值关系 | | `mapValues(hash, fn)` | 对值应用函数,返回新哈希表 | | `filter(hash, pred)` | 过滤键值对,保留满足条件的 | | `isEmpty(hash)` | 检查哈希表是否为空 | | `assign(target, source)` | 将源对象属性复制到目标(原地修改) | | `fromPairs(pairs)` | 将键值对数组转为哈希表 | | `toPairs(hash)` | 将哈希表转为键值对数组 | #### `lib/math.dl` | 导出函数 | 说明 | | ---------------------------------- | ------------------------------------------ | | `abs(x)` | 绝对值 | | `min(a, b)` | 最小值 | | `max(a, b)` | 最大值 | | `clamp(x, lo, hi)` | 限制在区间内 | | `pow(base, exp)` | 指数运算 | | `factorial(n)` | 阶乘 | | `gcd(a, b)` | 最大公约数 | | `lcm(a, b)` | 最小公倍数 | | `isPrime(n)` | 判断质数 | | `round(x)` | 四舍五入到整数 | | `floor(x)` | 向下取整 | | `ceil(x)` | 向上取整 | 使用示例: ```dlang import { map, filter } from "lib/array.dl"; import { toUpperCase } from "lib/string.dl"; let names = ["alice", "bob", "charlie"]; let loudNames = map(names, fn(n) { return toUpperCase(n); }); // ["ALICE", "BOB", "CHARLIE"] ``` --- ## 项目结构 ``` dlang/ ├── main.go # 入口,REPL 与文件执行 ├── lexer.go # 词法分析器 ├── token.go # Token 定义 ├── parser.go # 语法分析器(Pratt 解析) ├── ast.go # 抽象语法树节点 ├── evaluator.go # 树遍历解释器 ├── object.go # 运行时对象系统 ├── environment.go # 变量环境(作用域链) ├── builtins.go # 内置函数与模块加载 ├── main_test.go # 完整测试套件 ├── go.mod # Go 模块文件 └── lib/ # 标准库(.dl 文件) ├── string.dl ├── array.dl ├── function.dl ├── hash.dl ├── iterator.dl └── math.dl ``` ## 构建与测试 ### 构建 ```bash go build -o dlang.exe ``` ### 运行测试 ```bash go test -v ``` 测试覆盖了:基础类型、运算符、优先级、短路求值、控制流、函数、闭包、递归、数组/哈希操作、模块导入、标准库全部导出函数、错误异常路径等。 ### 运行 REPL ```bash ./dlang.exe ``` --- DLang - 简洁、可嵌入、易扩展的 Go 语言实现解释器。