编译 运行;完整语义见 语言规格。官网下载为纯 zhlang 自举原生版。1. 安装与第一个程序
zhlang 的编译器是单个可执行文件 —— 纯 zhlang 自举的原生编译器:直接生成机器码,不需要任何外部工具链、包管理器或运行时依赖。到下载页取对应平台的编译器,解压后给它执行权限:
chmod +x 编译
./编译 --version
写第一个程序,存成 你好.zh(源文件是 UTF-8 文本):
打印("你好,zhlang!")
编译并运行:
./编译 你好.zh -o 你好
./你好
你好,zhlang!
入口:顶层语句与 main
没有 main 时,入口模块的顶层语句就是程序。也可以只写一个无参
main(或中文名 主函数):入口模块没有顶层可执行语句时,初始化完成后自动调用它,
整数返回值作为进程退出码(非整数或无返回按 0):
函数 main()
打印("从 main 启动")
返回 7
从 main 启动
运行后 echo $? 得到 7。若入口模块还有顶层可执行语句,则照常执行顶层语句,
main 不会被自动调用(仍可手动调用)。main 与 主函数 不能同时定义。
示例/ 目录里的真文件 —— 贴出的「输出」就是实际跑出来的结果。Windows 用户把
./编译 换成 编译.exe 即可,中文文件名全链路可用。2. 变量、常量与类型标注
变量 声明可变的绑定,常量 声明不可变的绑定 —— 改常量、对常量取地址都是编译错,但容器内容仍可变。类型是动态的,也可以可选地写类型标注:内置类型位(整数/浮点/字符串/布尔/通道/数组/字典/套接字/函数/指针)或结构体名,声明与每次赋值都做运行时校验。(让 是旧关键字,已改名为 变量/常量 —— 撞见会给出指引。)字符串拼接用 +,模板串用反引号 `…${表达式}…`:
变量 年 = 2026
常量 温度 = 36.6
常量 名字 = "小明"
常量 是学生 = 真
常量 名单 = ["甲", "乙"]
常量 分数 = {"语文": 92, "数学": 88}
打印(`我是${名字},${年} 年,${温度} 度`)
打印("名单:", 名单, "数学:", 分数["数学"])
打印("拼接:", "你好," + 名字)
如果 是学生
打印("是学生")
年 = 年 + 1
打印("明年:", 年)
我是小明,2026 年,36.6 度
名单: ["甲", "乙"] 数学: 88
拼接: 你好,小明
是学生
明年: 2027
常量只冻结绑定,装进容器的内容照改不误;类型标注是约束而非注释 —— 写错的类型会在运行时被拦下(可被 尝试/捕捉 接住,报错带行号):
/* 常量:绑定不可变、容器内容仍可变;类型标注:声明与每次赋值运行时校验 */
常量 圆周率 = 3.1416
常量 名单 = ["甲", "乙"]
名单[0] = "丙" /* 合法:改的是内容,不是绑定 */
变量 命中数 整数 = 0
命中数 += 2
结构体 点
x
y
常量 原点 点 = 点(0, 0)
打印(圆周率, 名单, 命中数, 原点.x + 原点.y)
尝试
命中数 = "不是整数" /* 类型标注不符 → 运行时错误 */
捕捉 错误
打印("拦下:", 错误)
3.1416 ["丙", "乙"] 2 0
拦下: 类型标注不符:声明为 整数,得到的是 字符串
真值规则与 Python 相同:空串、空数组、空字典、0、假 为假,其余为真(带原型链的字典恒为真)。打印 会按「转字符串」规则输出所有类型:
打印("整数:", 42, "浮点:", 3.5, "布尔:", 真)
打印("数组:", [1, [2, 3]], "字典:", {"a": 1})
整数是 64 位、浮点是双精度;还支持位运算 & | ^ ~ << >>、0x/0b/0o 进制字面量与数字分隔符 _:
打印(0xFF, 0b1010, 1_000_000, (5 & 3) | (1 << 4))
3. 控制流
缩进就是块,像 Python。条件与循环的关键字是中英双份的:如果/如果否则/否则、当(while)、循环 i 从 A 到 B(区间,含两端)、循环 x 在 数组/字典(遍历),跳出(break)与 继续(continue):
循环 i 从 1 到 15
如果 i % 15 == 0
打印("FizzBuzz")
否则如果 i % 3 == 0
打印("Fizz")
否则如果 i % 5 == 0
打印("Buzz")
否则
打印(i)
变量 n = 10
当 n > 0
n = n - 3
打印("n 停在", n)
变量 和 = 0
循环 x 在 [3, 4, 5]
如果 x == 4
继续
和 = 和 + x
打印("跳过 4 的和:", 和)
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
n 停在 -2
跳过 4 的和: 8
循环 i 从 A 到 B 在 A > B 时会降序迭代
(空区间先想清楚),要跳过整轮就用 当;行内可以用分号连写短句(变量 a = 1; 变量 b = 2),
语句以换行分隔,不需要分号。4. 函数、闭包与函数式
函数用 函数 定义。函数是一等值:可以赋给变量、传参、返回;箭头函数 x => x * 2 写起来更短。捕获是创建时按值快照,而当闭包改写了外层变量时,该变量自动装箱为共享单元(按引用捕获):
函数 阶乘(n)
如果 n <= 1
返回 1
返回 n * 阶乘(n - 1)
打印("5! =", 阶乘(5))
常量 双倍 = x => x * 2
常量 加 = a => b => a + b
打印("双倍 21 =", 双倍(21), "; 加(3)(4) =", 加(3)(4))
常量 nums = [1, 2, 3, 4, 5, 6]
打印("平方:", 映射(nums, n => n * n))
打印("偶数:", 过滤(nums, n => n % 2 == 0))
打印("总和:", 归约(nums, 0, (a, x) => a + x))
打印("排序:", 排序(["banana", "apple", "cherry"],
(a, b) => 长度(a) - 长度(b)))
变量 计数 = 0
常量 累加 = 值 =>
计数 = 计数 + 值
累加(3)
累加(4)
打印("闭包改写外层:", 计数)
5! = 120
双倍 21 = 42 ; 加(3)(4) = 7
平方: [1, 4, 9, 16, 25, 36]
偶数: [2, 4, 6]
总和: 21
排序: ["apple", "banana", "cherry"]
闭包改写外层: 7
映射 / 过滤 / 归约 / 排序 是内置的高阶函数;排序默认升序且稳定,传比较函数时它须返回整数(负/零/正)。箭头函数体也可以是一个缩进块(=> 后换行)。
5. 容器:数组与字典
数组与字典都是引用类型(赋值不改内容、别名互见)。字典保持插入序,取 带缺省值,遍历字典得到键:
变量 a = [1, 2, 3]
追加(a, 4, 5)
打印("a:", a, "长度:", 长度(a))
打印("弹出:", 弹出(a), "现在:", a)
常量 d = {"名": "小明", "岁": 12}
d["城市"] = "杭州"
打印("取:", 取(d, "城市"), "缺键默认:", 取(d, "电话", "无"))
打印("包含:", 包含(d, "岁"), "; 删除:", 删除(d, "岁"))
循环 键名 在 键(d)
打印(键名, "=", d[键名])
a: [1, 2, 3, 4, 5] 长度: 5
弹出: 5 现在: [1, 2, 3, 4]
取: 杭州 缺键默认: 无
包含: 真 ; 删除: 真
名 = 小明
城市 = 杭州
切片 c[起:止]:半开区间(起含、止不含),两端都可省略,负数从尾部数,越界钳位不报错;字符串按码点、数组按元素。切片是只读表达式(s[1:2] = "x" 是编译错):
常量 s = "床前明月光"
打印(s[2:4], s[:3], s[-2:], s[3:99])
打印([1, 2, 3, 4][1:3])
打印(类型(s), 类型(1), 类型(1.5), 类型([]))
明月 床前明 月光 月光
[2, 3]
字符串 整数 浮点 数组
常用内置还有:长度、子串、查找、替换、分割、连接、去空白、转大写/转小写、转整数/转浮点/转字符串、排序、键/值、编码JSON/解码JSON、类型(值的类型名)。
6. 结构体、类与对象
结构体 声明一组字段;两种写法等价:结构体 点 后缩进列字段,或
结构体 点 { x, y }。构造可以按位置传值,也可以命名构造(键是字段名,顺序任意,
缺省字段补 假):
结构体 点 { x, y }
常量 p = 点{x: 3, y: 4} // 命名构造
常量 q = 点(1, 2) // 位置构造
打印(p.x, p.y, q.x + q.y)
3 4 3
字段用点访问(点访问会校验字段名,写错报编译错/运行时错)。方法就是挂在结构体模板上的函数,
第一个参数约定叫 本:
结构体 点
x
y
变量 p = 点(3, 4)
打印("p =", p.x, p.y)
p.x = 10
打印("改后 p.x + p.y =", p.x + p.y)
点.长度 = 本 => 平方根(本.x * 本.x + 本.y * 本.y)
打印("到原点距离 =", p.长度(p))
打印("是 点 的实例:", 是(p, 点))
p = 3 4
改后 p.x + p.y = 14
到原点距离 = 10.770329614269007
是 点 的实例: 真
对象模型是原型链:继承(原型, 添加字典) 造新对象,读走链、写落自身、枚举只看自己;是(对象, 原型) 做身份判断。任何字典都能当对象用 —— 继承() 只是给它挂上一条隐藏只读的原型指针。
类、初始化与继承
类 把字段、方法和构造收在一起。类体内可以写字段 名字 [类型] [= 默认]、
实例方法 函数 方法(参数)(隐式 本),以及初始化方法
函数 初始化(参数 [= 默认], …)(至多一个):构造时先按字段声明填默认值,再执行初始化,
整个表达式的结果恒为对象本身:
类 人类:
名字 字符串
年龄 整数 = 18
函数 初始化(名字, 年龄 = 18)
本.名字 = 名字
本.年龄 = 年龄
函数 问候(前缀)
打印(前缀 + 本.名字)
类 学生(人类):
学校 字符串 = "?"
函数 初始化(名字, 学校, 年龄 = 20)
本.学校 = 学校
函数 问候(前缀) // 覆盖父类方法(多态)
打印("学生", 前缀 + 本.名字)
常量 a = 人类("张三", 30) // 位置构造 → 调 初始化
常量 b = 人类{名字: "李四"} // 命名构造:键对应初始化参数
常量 c = 学生("小明", "一中") // 子类初始化前自动先调父类初始化
a.问候("你好,")
b.问候("你好,")
c.问候("你好,")
打印(a.年龄, b.年龄, c.学校, 是(c, 人类))
你好,张三
你好,李四
学生 你好,小明
30 18 一中 真
要点:
- 初始化方法体内
返回只能不带值(带值编译错);构造结果恒为本。 - 命名构造的键在有初始化方法时对应初始化参数,没有初始化时对应字段名;缺省用参数默认值。
- 子类初始化执行前自动调用父类初始化,父参数按同名匹配(父类必填参数在子类缺失 → 编译错)。
- 子类可以重写父类方法,
是(实例, 父类)沿类模板原型链判定;字段类型标注在声明与每次赋值后校验。 - 类体内的
@私有表示整个类仅当前文件可用(模块外限定访问与选择性引入都报错)。
静态方法与跨模块链式调用
写在类体里的 函数 是实例方法(自动注入 本)。实例方法也可以写在顶层,
用 @类方法(类名) 标注;静态方法用 @类函数(类名) 标注(不注入 本,
以 类名.函数(…) 调用,参数个数须严格相等):
类 计数器
属性 就绪 布尔 = 真
@类方法(计数器) // 实例方法(注入 本)
函数 加一(n)
返回 n + 1
@类函数(计数器) // 静态方法(无 本)
函数 版本()
返回 "2.23"
常量 c = 计数器()
打印(c.加一(41), 计数器.版本())
42 2.23
其他模块里用别名引入后,可以一路点到类的静态方法 —— 模块.类.静态方法(…)
(静态调用在编译期绑定):
// 计数器.zh 里有上面的 类 计数器
// 引入必须写在所有语句之前;同一模块可多次引入不同名字
@引入 "计数器.zh" 为 计数器库
@引入 "计数器.zh" 只 计数器
打印(计数器库.计数器.版本())
打印(计数器.版本())
@类函数 必须写在类体外(顶层),类体内只允许字段、实例方法与 @私有。
函数值是命名函数/箭头函数,静态方法本身不能直接当值;需要作值时可包一层箭头函数。
7. 模块
一个文件就是一个模块:顶层 @引入 "路径.zh",用 模块.函数() 限定调用。模块文件名(去掉 .zh)就是模块名。想要短名字用 为 别名,想把个别函数带进来用 只:
// 工具.zh
@私有
函数 内部换算(度)
返回 度 * 9 / 5 + 32
函数 摄氏转华氏(度)
返回 内部换算(度)
// 主.zh
@引入 "工具.zh"
打印("30 度 =", 工具.摄氏转华氏(30), "华氏")
30 度 = 86 华氏
细节:引入要写在文件顶部;@私有(@private)只影响模块外可见性;重复引入、互相引入、菱形依赖都按真实路径去重,幂等;模块只能放函数/结构体/引入,顶层语句只在入口文件(构成 main)。
标准库模块:编译器自带一层用 zhlang 写的标准库(标准库/)。可引入的模块用裸名即可,不用写路径与扩展名(@引入 集合 ≡ @引入 "集合.zh"):
@引入 集合 只 反转, 最大值
常量 名单 = [3, 1, 2]
反转(名单) // 原地反转
打印(名单, 最大值(1, 9))
[2, 1, 3] 9
可引入层还有 文件、JSON、正则、进程、终端、时间、随机、路径工具,以及 同步、错误、网络;函数清单见 API 参考。
另有一批内置函数本身就是 zhlang 源码实现(分割/连接/转大写/映射/绝对值/哈希SHA256/编码URL 等 20 个),隐式可用、行为与规格逐字一致 —— 想读源码直接看仓库的 标准库/;只有被引用到的标准库模块才会进入产物(按需发射)。
8. 错误处理
所有运行时错误(越界、除零、缺键、读文件失败……)都可以用 尝试 / 捕捉 接住;捕捉变量就是一段消息字符串。主动报错用 抛出:
尝试
变量 a = [1, 2]
打印(a[99])
捕捉 e
打印("捕获:", e)
函数 检查年龄(岁)
如果 岁 < 0
抛出("年龄不能是负数:" + 转字符串(岁))
返回 岁
尝试
检查年龄(-1)
捕捉 e
打印("捕获:", e)
打印("继续往下走:", 检查年龄(18))
捕获: 下标越界:99(长度 2)
捕获: 年龄不能是负数:-1
继续往下走: 18
未捕获的错误会打印 运行时错误(第 N 行):消息 并以退出码 1 结束;协程里未捕获的错误按 Go 语义终止整个进程。
8.1 类型化错误
普通 抛出 与全部运行时错误仍然是消息字符串;需要错误类型时,
用内置 抛出对象 保留原值,配合标准库 标准库/错误.zh 的基类与工厂:
@引入 "错误.zh" 只 新建错误类型, 是错误, 错误类型
尝试
抛出对象(新建错误类型("参数错误", "年龄必须为正", 400))
捕捉 e
如果 是错误(e)
打印(错误类型(e), e.消息, e.代码)
参数错误 年龄必须为正 400
- 捕捉变量拿到的是对象:可以
是(e, 错误基类)、读e.消息/e.代码;普通抛出/运行时错误仍得到字符串,可用类型(e)区分。 - 未捕获的
抛出对象优先打印对象的消息字段;没有该字段时按显示规则打印整个值。 标准库/错误.zh还提供新建错误(消息)、新建错误码(消息, 代码)、错误文本(e)。- 语言目前只支持同一模块内的类继承;跨模块错误类型用原型链 +
类型标签。需要在同一文件里定义错误基类与子类时,类继承、初始化和是都可用。
8.2 最终块(清理)
尝试 可以只写 最终(不要捕捉),也可以两者都写。
离开尝试语句的每条路径都会执行最终块——正常完成、异常被捕捉、异常未被捕捉
(先执行最终块,再按原行号重抛)、返回、跳出、继续:
函数 读取(模式)
尝试
打印("打开")
如果 模式 == 1
抛出("失败")
返回 "内容"
最终
打印("关闭")
函数 main()
打印(读取(0))
尝试
打印(读取(1))
捕捉 e
打印("捕获", e)
最终
打印("最终")
打开
关闭
内容
打开
关闭
捕获 失败
最终
- 最终块不能访问
捕捉变量(捕捉作用域在它之外)。 - 最终块里的
返回/抛出/跳出/继续会覆盖原先的退出路径(例如 finally 里返回 2会压过 try 里的返回 1)。 - 自举原生版(x86_64/aarch64 ELF、Windows PE)三端一致支持
最终。
9. 并发:协程、通道与锁
启动 函数(参数) 开一个协程(几 KB 栈,可以开上万个);通道() 传值,通道(n) 是缓冲通道。接收 从关闭且空的通道读到 假,这是约定的「没有更多」:
函数 生产者(ch, 件数)
循环 i 从 1 到 件数
发送(ch, i * 100)
关闭(ch)
常量 ch = 通道()
启动 生产者(ch, 3)
当 真
常量 v = 接收(ch)
如果 v == 假
跳出
打印("收到", v)
常量 完成 = 通道()
循环 名 在 ["甲", "乙", "丙"]
启动 干活(名, 完成)
函数 干活(名, 完成)
休息(10)
发送(完成, 名 + " 完工")
循环 i 从 1 到 3
打印(接收(完成))
收到 100
收到 200
收到 300
丙 完工
乙 完工
甲 完工
多路等待用 选择:每个臂是「接收 通道 为 变量」「发送 通道, 值」或兜底的「否则」;多个臂同时就绪时随机取一个(与 Go 相同)。协程由运行时调度到多核上跑;阻塞式 I/O(网络收发、接受连接、休息)会让出 worker,不会卡住其他协程。纯计算忙协程不抢占(分点式抢占只在 C 版运行时;自举原生版靠多 worker 并行,忙协程占满全部 worker 时其他协程要等它结束)。
选择
接收 ch1 为 v
打印("ch1:", v)
接收 ch2 为 w
打印("ch2:", w)
否则
休息(1)
9.1 共享内存:互斥锁与读写锁
通道适合转移所有权;如果确实要共享可变容器,标准库提供
标准库/同步.zh(纯 zhlang 用通道实现,自举原生版可用,需要手动引入)。语言本身没有内置锁语法;
Go 的 sync.Mutex 同样走标准库路线。互斥锁让「读改写」成为原子临界区:
@引入 "同步.zh" 只 互斥锁, 等待组
函数 累加(锁, 计数, wg)
循环 i 从 1 到 1000
锁.加锁()
计数[0] = 计数[0] + 1
锁.解锁()
wg.完成()
函数 main()
常量 锁 = 互斥锁()
常量 计数 = [0]
常量 wg = 等待组()
wg.添加(2)
启动 累加(锁, 计数, wg)
启动 累加(锁, 计数, wg)
wg.等待()
打印(计数[0])
2000
要点:
- 互斥锁:
加锁()阻塞、解锁()释放、尝试加锁()非阻塞;加锁执行(f)无论 f 正常返回还是抛错都会解锁(返回 f 的结果)。没有finally,临界区可能抛错时优先用它。 - 读写锁:
读锁()/读解锁()、写锁()/写解锁();读者可共享,写者独占,且有写者在等时新读者会等待(写者优先)。同样有读锁执行/写锁执行作用域方法。 - 等待组:
添加(n)/完成()/等待();计数归零时唤醒全部等待者,归零后可复用。 - 一次:
执行一次(f)只让首个调用者执行 f(返回真),其余调用者等它结束后返回假。 - 锁不是重入锁,也不校验解锁者身份;共享数组/字典内部仍然无锁,通道也只保护队列、不保护负载。并发读写同一容器,要么加锁,要么把数据交给单个协程独占。
- 同步模块是纯 zhlang 实现(基于通道),自举原生版可直接使用;锁不重入,也不校验解锁者身份。
失败路径也要释放锁;例如把捕获到的局部数组交给闭包,再走作用域锁:
函数 加一(锁, 计数)
常量 增加 = () =>
计数[0] = 计数[0] + 1
锁.加锁执行(增加) // f 抛错也会解锁
10. 文件与 JSON
写文件("名单.json", 编码JSON({"名字": ["甲", "乙"], "人数": 2}))
常量 数据 = 解码JSON(读文件("名单.json"))
打印("人数:", 数据["人数"], "第一个:", 数据["名字"][0])
打印("文件存在:", 文件存在("名单.json"))
删除文件("名单.json")
人数: 2 第一个: 甲
文件存在: 真
文件族内置:读文件 / 写文件 / 追加文件 / 删除文件 / 文件存在 / 文件大小 /
列目录 / 建目录 / 真实路径(realpath,解析符号链接);JSON 是
编码JSON / 解码JSON(对象 ↔ 字典、数组 ↔ 数组,null 解码为 假)。
11. 网络与 https
HTTP 客户端一行就能用,https 内置(自包含 TLS,带证书链与主机名校验;证书不符必报错,不静默放行):
常量 r = 网页请求("https://example.com") // 也可 GET/POST/自定义头
打印("状态:", r["状态"], "内容长度:", 长度(r["体"]))
打印(取网页("https://example.com")[0:0]) // 取网页 直接返回正文
TCP 是「监听 / 接受连接 / 连接到 / 套接字发送 / 套接字接收 / 关闭套接字」。下面这条程序一条进程里既当服务端又当客户端(示例 网络.zh):
函数 回音服务(端口)
常量 服务 = 监听(端口)
常量 c = 接受连接(服务)
常量 请求 = 套接字接收(c, 1024)
套接字发送(c, "回音:" + 请求)
关闭套接字(c)
启动 回音服务(18930)
休息(80)
常量 c = 连接到("127.0.0.1", 18930)
套接字发送(c, "你好")
打印(套接字接收(c, 1024))
关闭套接字(c)
回音:你好
12. 正则表达式
八个正则内置(匹配 / 全匹配 / 查找 / 全部 / 分组 / 替换 / 分割 / 转义),语义对齐 Python re:码点级、支持 i s m 选项、替换串里用 $1…$9。写模式用原始字符串 r"…" 免二次转义:
常量 文本 = "订单 A1024 与 B2048 已发货"
打印("全部编号:", 正则全部(文本, r"[A-Z]\d{4}"))
打印("替换:", 正则替换(文本, r"\d+", "****"))
常量 m = 正则分组("2026-10-16", r"(\d{4})-(\d{2})-(\d{2})")
打印(`年=${m[1]} 月=${m[2]} 日=${m[3]}`)
打印("分割:", 正则分割("甲, 乙;丙", r"[,;]\s*"))
全部编号: ["A1024", "B2048"]
替换: 订单 A**** 与 B**** 已发货
年=2026 月=10 日=16
分割: ["甲", "乙", "丙"]
13. 指针(进阶)
&x 取地址、*p 读写目标。指针与 C 一样危险:悬垂即未定义行为;GC(阶段 2,保守标记-清除)不追踪指针目标的生命周期——指向栈上变量槽的指针,栈帧返回后照样悬垂,拿不准就别用。
函数 加倍(p)
*p = *p * 2
变量 x = 21
变量 p = &x
加倍(p)
打印("加倍后:", x)
*p = *p + 1
打印("自增后:", *p, x, "; p 指向 x:", p == &x)
加倍后: 42
自增后: 43 43 ; p 指向 x: 真
拿到指针后可以用 -> 读写字段、调用方法:基是指针时先解引用一层;基已经是对象引用(类/结构体/字典,包括函数与方法的返回值)时直接访问。可链式,ELF / PE / aarch64 三端一致。
类 计数器
属性 n = 0
函数 初始化(起 = 0)
本.n = 起
函数 加(k)
本.n = 本.n + k
函数 自身() -> 计数器
返回 本
变量 c = 计数器(40)
变量 p = &c
p->加(2)
p->n = 100
打印(p->n) // 100
打印(c.自身()->n) // 100
14. 交叉编译与 REPL
同一条命令就能出别的平台的原生程序。官网下载的最新自举版内建 x86_64 / aarch64 / Windows PE 后端:编译器源码全部是 zhlang,自己编译自己,产物直接生成机器码,不需要任何外部交叉工具链:
./编译 你好.zh -o 你好 # 本机
./编译 你好.zh -o 你好-arm --target aarch64-linux-gnu # 静态 ELF,arm64 Linux
./编译 你好.zh -o 你好.exe --target x86_64-windows-gnu # PE64,Windows x64
./编译 你好.zh -d # 调试运行:临时编译并直接执行,不留二进制
REPL 在终端下支持行编辑与语法着色:方向键/Home/End/Delete、上下键翻历史、Tab 缩进、Ctrl-C 取消当前行、Ctrl-D 退出,关键字/字符串/数字/注释实时着色;历史保存在 ~/.zhlang_history(ZHC_REPL_HISTORY 可改路径)。
调试断点:源码里写 @断点调试(等级, 内容),用 -d 运行会在命中处停下,提示符带 文件:行;命令 c/继续、q/退出、b/栈、?。等级 错误=0、警告=1、信息=2、调试=3,ZHLANG_DEBUG_LEVEL 可过滤。
15. 深入:自举与原生后端
zhlang 的编译器用 zhlang 重写并以自己编译自己(仓库 自举/:词法、语法、名字解析、x86_64/aarch64/PE 代码生成、ELF/PE 组装、CLI 与 REPL)。make selfhost 要求 stage1 与 stage2 逐字节相同;产物直接是机器码,不依赖外部工具链。
原生运行时(字符串/数组/字典/闭包/协程/通道/网络/TLS)由 自举/原生运行时.zh 与用户程序一起编译;编译器与本机程序都不依赖外部工具链。
自举/ 下的编译器源码本身,就是 zhlang 代码。15.1 能力边界
官网下载的纯 zhlang 自举版GC 已实现阶段 2(保守标记-清除 + 空闲链复用:分配按 32MB 增量阈值触发回收,堆回收/堆统计/堆整理 可观测;保守扫描只多保留、不会错回收)。Windows PE 的目录列举与进程 IO 均已支持。
已实现并在三端验证:协程 / 通道 / 选择、TCP / HTTP / HTTPS(Linux 上纯 zhlang TLS 1.3,含 P-521、名称约束、CRL 吊销与 OCSP 钉接;Windows 走系统 TLS/WinHTTP,撤销检查由系统负责)、接口、闭包、结构体与类、@断点调试 与 -d 调试运行、文档注释与 HTML 文档生成。
16. 下一步
完整的函数签名、别名与错误语义见 API 参考;语言规格见
《zhlang 语言规格》。仓库 examples/ 的 15 个示例
已全部改用面向对象写法(类 / 初始化 / 静态方法),可直接对照阅读。
编译器自身的 zhlang 源码(v2.22 起)也全部面向对象化:词法器、语法器、名字解析器、
代码生成器、编译器驱动、两个原生后端与原生运行时都是类;详见
源码仓库 的 自举/README.md。
- 语言手册 —— 词法、文法、标准库、错误约定的完整条文(编译器的唯一依据)。
- 示例程序 —— 随站点发布的
示例/目录;仓库examples/里还有万协程、并发服务器、词频统计等 15 个。 - 工具链 ——
--target三端、-d调试运行、--repl交互会话、tools/文档生成.zh文档生成器。 - 常见问题 —— Windows 中文路径已全链路支持;交叉 aarch64 / Windows 开箱即用;自举原生版 GC 已实现阶段 2(标记-清除/空闲复用),暂未实现 C 混写。