规格

zhlang 语言规格

词法、文法、标准库、错误约定的完整条文,是编译器实现的唯一依据。本页由 docs/spec.md 生成。

说明 · 本页是纯 zhlang 自举编译器的语言规格;速查内置函数请用 API 参考。

zhlang 语言规格(规范)

版本记录(当前 v2.40)

版本:2.40 · 2026-11-02 · 本文档是 zhlang 语言与自举编译器实现的唯一依据。v2.40(整数溢出默认检查):+ - * 与一元 - 的有符号 64 位溢出在运行期抛「整数溢出」(此前静默回绕);新增 环绕加/环绕减/环绕乘 提供显式模 2^64 语义;INT64_MIN % -1 不再触发 #DE 崩溃;修复 GC 只扫 64KB 全局区(v2.39 已扩到 256KB)的误回收风险。以下为原 v2.39 发布说明——官网发布最新自举版(纯 zhlang 原生编译器,直接生成机器码)。v2.39(网络库增强):新增网络内置——域名解析(主机)(IPv4 字面量/hosts/DNS 全部 A 记录,ZHC_DNS 支持 IPv4:端口)、UDP 三件套 UDP绑定(端口[, 地址])/UDP发送到(套接字, 主机, 端口, 数据)/UDP接收自(套接字[, 最多])(接收返回 {"数据","地址","端口"})、套接字设超时(套接字, 秒)(Linux 非阻塞套接字由运行时按每次收发独立计时,Windows 走 SO_RCVTIMEO/SO_SNDTIMEO)、本端地址/对端地址(→ {"地址","端口"})与 关闭写端(SHUT_WR);三端 x86_64/aarch64/PE 的 WinSock 表补齐 sendto/recvfrom/getsockname/getpeername/shutdown。标准库——网络.zh 新增 解析网址/拼查询/表单编码/取网页跟随/网页请求跟随[最多]/取JSON/提交表单[完整]/提交JSON[完整]/请求头/下载/读全部[最多]/解析Cookie/Cookie头;新模块 网络增强.zh 封装 UDP/DNS/超时/地址与 UDP请求[超时];服务.zh 支持 multipart/form-data(表单文件/表单值/上传文件)、SSE(事件流/发事件[名/编号]/发注释)与连接接管(请求["连接"]/请求["余"],处理函数置 请求["接管"]=真 后框架不再写 HTTP 响应,供 WebSocket 等协议使用)。HTTP 客户端原生后端补上 10 秒收发空闲超时。顺带修复 PE 两个潜伏 bug:字符串字面量缺 C 终止符导致 GetProcAddress("recvfrom") 失败、手写 imul 漏 REX.R 把分配指针乘坏(Windows 超时失效)。套件 316/0;三端网络用例输出逐字一致,自举与交叉自举见开发记录。服务端加固(2026-11-01):服务.zh 增加读超时、最大连接 + 503 背压、停止/已停止/活动连接 与停机排空;静态文件支持二进制、ETag/If-None-Match、单区间 Range;访问日志按状态码/耗时着色并统一为 [状态] 标签;新增 彩色 配置与 NO_COLOR/ZHLANG_COLOR 环境变量。顺带修复原生运行时全局暂存区重叠:终端探测 termios 工作区由 0x150、保存区由 0x110 迁到 0x210/0x250,不再覆盖 GC 表指针/计数(0x168/0x170)——真实终端下调用 是终端() 会段错误。前端改为 @引入 可写在任意顶层位置、装饰器按声明处源码顺序应用。三端一致性补丁(2026-11-01):aarch64 后端 / 补除零检查(此前 1/0 静默得 inf,x86/PE 均抛「除以零」;回归 tests/原生/浮点除零.zh);终端原始模式标志由 0x1B8 迁到 0x208,不再与 0x1A0 的 sigaction(32B)重叠;服务框架补 Windows PE(wine)三项回归(纯内存路由/静态/装饰器、multipart/SSE、真实 WinSock 503 背压与停止排空);多 worker 时钟暂存由共享全局 0x80D0 改为每 worker +96(旧实现会和 nanosleep 的 timespec 互相覆盖,hub 睡掉数天并卡死 GC 停世)。标准库补强——数学/集合/时间/随机/文件/路径工具/编码/摘要/进程新增一批 API(见 §6);格式化时间 补齐 %j/%w/%z/%a/%A/%b/%B 等;非 ASCII HTTP 头值统一 %XX;执行 真正透传 stderr,-d 断点回 stderr;建目录 递归与 真实路径 errno 原因;随机种子混入单调时钟/栈地址。完整套件 343/0。v2.38(REPL 隔离与 TLS 诊断):REPL 会话目录按微秒时间戳 + 随机数命名——原生运行时的 随机数 只以秒级时间做种子,同一秒启动的两个 REPL 会共用 /tmp/zhc-repl-自举-<N> 的 会话.zh/会话.bin,符号与输出跨会话污染(实测 4/4 复现);现目录名带 当前时间() 的微秒部分,进程间彻底隔离。REPL 命令在续行中生效——:清/:重置(:reset/:clear)、:帮助、:会话、:退出 不再要求「不在续行中」;全角冒号 : 与英文别名同义,续行中 :清 会放弃未完成输入并清空会话,旧符号不再残留。TLS 告警细分——服务器告警解析为 TLS:服务器告警 <名称>(<编号>,<级别>)(RFC 8446 告警表,close_notify 按正常关闭处理);记录类型不符/未知内层类型带实际编号;证书错误区分自签名不受信、链无法到受信根(含叶/签发者 CN)与主机名不匹配(含期望主机与证书 CN);服务器未选 ChaCha20/TLS 1.3 带收到的密码套件/版本。v2.37(原生后端诊断):未捕获运行时错误带行号与调用栈——每个非运行时函数帧保存函数名、声明行与当前语句行;未捕获错误打印「运行时错误(第 N 行):消息」,随后按「调用栈(由内向外)」打印 zhlang 函数帧(最内层 64 帧,更深注明省略),每协程独立。行号与帧元数据放在机器栈帧里([rbp-16] 魔数 / [rbp-32] 函数名 / [rbp-48] 声明行 / [rbp-64] 当前语句行),运行时函数不写元数据,错误沿 rbp 链向上取最近用户/标准库帧,因此无全局槽与并发竞争,协程切换与 捕捉 恢复 rbp 天然隔离。只有「最终」块原样重抛时,rt抛出 在发现处理者时把行号与帧快照进协程 +416 捕获区(1080 字节),重抛沿用最初快照;普通新抛出即使复用同一字符串字面量也重拍活动栈。被 捕捉 的消息改为纯文本(去掉「运行时错误:」「原生后端:」内部前缀),与 C 版语义一致;未捕获打印器全程只用原写、不分配堆内存,内存不足时也能输出。x86_64(ELF/PE)与 aarch64 三端一致。实测三端同一用例输出逐字相同;套件 307/0;自举全过(stage1==stage2、8 worker、Windows 交叉/自举);6000 项链输出正确。明细见 开发记录/2026-10-31-v2.37-ZL17-错误行号与调用栈.md。v2.36(编译器修复):超长二元链不再栈溢出——原生后端 生成二元 改为显式帧栈后序生成:左结合(a+b+c…)与右深括号(1+(1+(…)))的递归深度都不再等于表达式项数,求值顺序与递归版完全一致;&&/|| 短路子树仍交给 生成逻辑。前端同时给表达式嵌套设 3000 层上限,超过报编译错「表达式嵌套过深(超过 3000 层,请拆分或改用中间变量)」,不再在约 1 万层时以裸「协程栈溢出」崩溃。实测单条 31,500/32,000/50,000 项 q0() + q1() + … 表达式均可编译:32k 143s、50k 196s(峰值 3.7GB),产物求和正确;x86/aarch64/PE 三端 6000 项链输出一致。v2.35(编译器修复):恢复局部变量遮蔽模块级全局——v2.34 把函数入口灌入作用域的全局名误当作同作用域声明,函数内 变量/常量 与全局同名时误报「变量「x」在当前作用域重复声明」(违反 §2.3 对遮蔽的规格承诺,并使教程 网络.zh 与 HTTP 服务用例编译失败);现 x86/aarch64 两后端判重时放行既有全局条目,同作用域内真正的重复声明仍报错。版本字符串同步——--version 由滞后的「规格 v2.32」改为 v2.35。v2.34(语言/原生后端):指针/对象成员访问 ->——p->字段、p->方法(…):基若为指针(&变量、返回指针的函数/方法)先解引用一层,基若已是对象引用(类/结构体/字典,含函数与方法返回值)则直接访问;可链式(p->a->b、f()->x),属性可读写与复合赋值;三端(ELF/PE/aarch64)一致。平台修复(三端对齐)——aarch64 栈守护序言把「TLS 就绪标志」当指针二次解引用、cmp sp 用错编码(移位寄存器形式的 31 是 XZR),导致所有 a64 程序启动即崩;通道改用独立运行时标签(9,原与指针共用 8)后 类型(通道(…)) 返回「通道」、打印 输出「通道:0x…」,指针打印「指针:0x…」、函数值打印 <函数>;真值规则对齐 §2.2(数组按长度、字典按条目或原型为真,函数/指针/通道恒真);类型标注补齐 指针/函数/通道;aarch64 补复合成员赋值(p->x += …、d.x += …)。v2.32(原生运行时/编译器):协作式网络与睡眠——休息 恢复为毫秒语义并接入调度睡眠队列;Linux 套接字(监听/连接/accept 产物)改非阻塞,接受连接/套接字收发/HTTP 收全部/纯 zhlang TLS 读写遇 EAGAIN 时 休息(1) 让出 worker,单 worker(ZHC_THREADS=1)下服务端并发可用;aarch64 补 fcntl/clock_gettime 系统调用号翻译,Windows 休息 走 Sleep。调试断点等级名——@断点调试(信息, …) 与 error/warn/info/debug 等名称等价于整数(运行时按名称显示/过滤)。中文切片与负索引——切片按码点取边界,字符串与数组负索引读写。启动 对函数值/内置函数给明确编译错(规格只允许命名函数)。死锁检测——无就绪、无睡眠且无 worker 在跑协程时报「死锁——所有协程都在等待」。除零文案去掉内部 原生后端: 前缀。aarch64 常量压缩——MOVZ/MOVK 只发非零块,空程序产物 514KB→403KB、宿主编译器 17.3MB→14.2MB;发32/发4/发8 热路径。v2.31(原生后端):模块级全局变量——入口模块顶层 变量/常量 对所有具名函数、类方法、闭包可见并共享同一存储(闭包/方法按地址捕获,读实时值);函数参数/局部变量同名时遮蔽;全局槽位于 BSS 0x9000 起,GC 根扫描三端统一 64KB(PE 原只扫 4KB)。函数级按需发射——只生成入口/显式引入模块及其引用到的运行时、隐式标准库函数与类方法挂载;空程序产物 1.99MB → 385KB,stage0 编译 7.6s → 2.3s;1 万行程序 50.5s → 10.0s。字节发射快路径——发字节 实例方法直写缓冲,替换 1600+ 处静态 发 调用;自举 stage1 编译自身 4 分 15 秒 → 54.6 秒。v2.30(原生运行时/编译器):GC 性能与停世修复(原生后端)——回收预算改为自适应:每次回收后预算 = max(存活 × 2, 32MB)(与 §5.4 既定口径一致;此前原生实现误为固定 32MB,编译期反复全量扫描,原生编译器编译冒烟程序需 260s);页索引由 64KB 页改为 4KB 页(页内平均块数约 127 → 8,单字地址查找链缩短一个数量级);修正 GC 代际竞态——上一轮被迫停住的 worker 在新一轮 GC 中不再计数,收集者等不齐停稳数 → 全体 worker 挂在 futex 上的死锁(现按 GC 代际号逐代计数)。实测原生编译器编译冒烟程序 260s → 17.3s(15 倍,环境见开发记录)。原生后端 + 对齐 §3——任一侧为字符串、另一侧整数/浮点自动转串拼接(此前仅「串+串」);布尔/数组/字典与字符串混搭报「运行时错误:「+」不支持 X 与 Y」。M:N 并发异常隔离——异常处理链头与异常值改为协程级状态(此前为全局槽, 多 worker 并发 尝试/捕捉 会互相踩踏:最小复现修复前 16/20 错乱/崩溃,修复后 0/20);x86(ELF/PE)与 aarch64 三套后端一致。 错误体验——未定义变量/函数/结构体按编辑距离给出「你是不是想写「X」?」;新增 大写/小写 别名(同 转大写/转小写)。v2.29(原生后端):JS 后端移除——实验性的 --emit-js(zhlang→JavaScript)从维护侧删除(自举驱动分支、JS生成.zh、runtime/js 内嵌、js-test 对拍);标准库/JS.zh(用 zhlang 生成 JS 源码的库)不受影响;冻结的 C 版 stage-0 引导器仍保留其内嵌实现。GC 阶段 2(保守标记-清除 + 空闲链复用)——分配登记(阶段 1)保留,每次分配按阈值(32MB,按距上次存活的增量)触发回收:根 = 全局区 + 各协程活跃栈区间 + 协程上下文寄存器,全局/栈保守扫描(8 字节粒度,凡落在已登记块内即视为指针),协程栈整块跳过改按活跃区间扫描;未标记块进按大小分级的空闲链表(16..4096B 256 类 + 大块单链),后续分配优先复用(小块复用清零);页节点/标记栈溢出本轮不回收,表满永久停用 GC;堆统计 的 字节 改为当前存活字节;新内置 堆回收(heapcollect)(强制触发一次完整回收)与 原栈指针()(GC 根扫描用:当前栈指针,无英文别名);类字段声明支持 变量/常量 前缀(与 属性、裸名等价;纯 zhlang 前端,C 版 zhc/标准库仍用 属性)。见 §5.4。v2.28:文档注释与 HTML 文档——/** … */ 文档注释(紧邻声明或作为模块文档),标签 @作者/@时间/@说明/@参数/@返回/@示例/@弃用;tools/文档生成.zh 生成自包含响应式 HTML(自动/桌面/移动布局、侧栏、搜索、深色模式),见 §3.5;v2.27(原生后端):调试断点——@断点调试[(等级, 内容)] 指令(等价函数 调试断点(...);等级 错误=0/警告=1/信息=2/调试=3),-d/--debug 调试运行(编译到临时文件直接执行、不留产物;加 -o 则产出调试二进制)才生成代码,运行时向 stderr 打印 断点 文件:行 函数 [等级] 内容,并按 ZHLANG_DEBUG/ZHLANG_DEBUG_LEVEL/TTY 决定过滤与命令循环(见 §6.10);缩进允许 Tab(按 8 列制表位,与空格按视觉宽度对齐);v2.26(原生后端):新增无符号 64 位 / 64×64→128 intrinsic(原乘128/原乘高64/原乘低64/原无符号右移/原无符号比/原无符号除/原无符号余,含英文别名;见 §6.9),SHA-512/SHA-384 改用单 64 位字实现(逻辑右移走 intrinsic),哈希吞吐约 3–4 倍;v2.25(原生后端):接口——顶层 接口 名[(父接口, …)] 只写方法签名(方法行可写 名字(参数[ 类型]),可省略 函数),Go 式结构化匹配,在带接口标注的变量声明/赋值与 是(值, 接口名) 处运行时检查(方法名 + 参数个数;类方法按含隐式 本 的个数,普通函数值按原个数);接口本身无方法体,不需要 implements。参数类型标注——函数/方法与接口签名里的参数可写 名字 类型,整数/浮点/字符串/布尔/数组/字典 按标签、结构体按原型链、接口按方法集,在被调函数入口检查(错误文案与变量标注同口径);接口匹配仍只看方法名与参数个数,签名里的类型由实现方法自己把关。类可以没有属性(纯行为类/接口实现)。原生网络(三端):连接到/套接字发送/套接字接收/关闭套接字/监听/接受连接 与 取网页/网页请求(HTTP/1.1,支持 chunked);Linux(x86_64/aarch64)走纯 syscall,https 走纯 zhlang TLS 1.3(证书链/主机名校验,信任库 = ZHLANG_CA_FILE/SSL_CERT_FILE → 系统 CA → 内嵌 ca-bundle.crt;ZHLANG_HTTPS=curl 可强制系统 curl);Windows PE 走 WinSock,https 经内置 WinHTTP(系统 TLS/CA,无需 curl)。套接字 Linux 为文件描述符整数、Windows 为 SOCKET 句柄;Linux DNS 内置(IPv4 字面量 → /etc/hosts → ZHC_DNS → resolv.conf 多路径含 systemd/Android/$PREFIX(Termux) → 8.8.8.8 兜底),Windows 用 gethostbyname。原生运行时不支持 GC,网络资源由显式 关闭套接字 释放。原生并发(三端):协程/通道/选择在 Windows PE 也已启用(此前编译期拒绝)。自定义 User-Agent:网页请求 的头字典传 User-Agent(键不区分大小写)即覆盖默认 zhlang-native/1.0;未传时 Linux HTTP/curl 与 Windows WinHTTP 都发默认值。v2.24:C 版 zhc 是 stage-0 引导器,不实现 §3.5 的 C 混写。v2.24:块语句可选冒号(Python 风格)——如果/否则如果/否则/当/循环/尝试/捕捉/最终/选择 及其臂(接收/发送)的条件或参数之后可写可选冒号 :,与换行等价;三端(zhc/自举/原生)解析器一致。v2.23 增补:原生编译器自举与 Go 式多端——纯 zhlang 原生编译器(自举/原生.zh)可编译自身(stage0→stage1→stage2 不动点,make selfhost),同一二进制内建 x86_64 ELF/PE 与 aarch64 目标;--target 接受 Go 风格 系统/架构,ZHC_TARGET/ZHC_GOOS+ZHC_GOARCH 环境变量与 --targets 目标表(自举/驱动/目标.zh,含能力说明);Windows PE 目标补齐文件 IO/参数/环境变量/程序路径,路径与命令行全部走 UTF-16 宽字符 API(中文源码名/参数/文件名可用),PE 堆改为 bump 分配器;Windows PE 交叉自举与「Windows 宿主 → Linux ELF」已验证(见 §6.8)。v2.23:C 混写(自举编译器)——@C … @C结束 内嵌 C、@C "文件.c" 引入 C 文件、@外部("C符号") 函数 按 zhv_t ABI 调用 C;C 侧可用运行时 API,并可通过函数值 + zh_call 回调 zhlang(见 §3.5;C 版 zhc 与原生后端不支持)。标准库目录与命名迁移——仓库标准库目录由 std/ 改为顶层 标准库/;隐式模块改名为 字符串.zh/编码.zh/容器.zh/数学.zh/摘要.zh(模块名随基名),可引入层新增 网络.zh(网络内置的命名空间包装:同名函数内部用 httpget/httpreq/tcpconn 等英文别名调用内置,避免同名递归;原生后端尚未移植网络)。裸名标准库引入——@引入 名字 [为 别名 | 只 名字, …] ≡ @引入 "名字.zh" …:先相对引入文件找同名 .zh,找不到再走标准库查找路径(磁盘 标准库/ + 内嵌清单);C 版、自举版与原生后端解析器一致。跨模块类方法挂载——顶层 @类方法(模块.类名) / @类函数(模块.类名) 可给已加载模块中的类挂实例/静态方法(模块可写真实名或本文件别名);解析期不要求同类声明,名字解析期校验目标是类、非私有、非标准库模块;同一方法跨模块重复挂载为编译错(同模块保持后者覆盖);挂载在模块初始化阶段执行,所有结构体/类模板先于模块初始化创建。v2.22:编译器与运行时源码全面面向对象化(语言行为不变)——自举编译器拆为 类 词法器(词法)、类 语法器(语法/AST/转储)、类 解析器(名字解析)、类 生成器(C 代码生成)、类 驱动(CLI/REPL),纯辅助函数为 @类函数 静态方法;原生后端为 类 原生x86后端 / 类 原生a64后端 / 类 原生驱动,原生运行时高层函数(浮点/JSON/正则/文件 IO/高阶字符串)为 类 原生运行时,底层字符串/数组/字典原语保留模块级(类方法挂载自身依赖字典原语,存在循环依赖);标准库此前已 OOP。配套语言能力:模块.类.静态方法(…) 链式调用、引入 … 为 别名(允许别名等于模块自身名)、入口 main/主函数 的整数返回值作退出码、类初始化方法 函数 初始化 与命名构造。主协程栈提升到 256MB(虚拟预留,按需提交)以适配 OOP 解析器更深的递归。examples/ 全部改为面向对象写法;回归微用例保持按特性编写(测试目的不是演示风格)。v2.22 增补:可引入标准库 标准库/同步.zh——纯 zhlang 通道实现的互斥锁/条件/读写锁/等待组/一次(见 §6.5);抛出对象 + 标准库/错误.zh 类型化错误(见 §3.4/§6.6;跨模块类继承仍不支持,错误类型走原型链 + 类型标签);最终(finally)块——离开尝试语句的每条路径(正常/捕捉/重抛/返回/跳出/继续)都会执行(见 §3.4;x86_64 原生后端同样支持);Windows 控制台的 REPL 行编辑/语法着色——运行时用 ReadConsoleInputW 取键并开启控制台 ANSI/VT 处理,旧系统或输出重定向时回退 读入。v2.21:类的初始化方法(构造)——类体内可写至多一个 函数 初始化(参数 [= 默认], …):构造 类名(实参…) 先按字段声明填默认值/零值,再执行初始化方法,整个表达式的结果恒为 本;初始化方法体内 返回 只能不帶值(带值编译错),参数支持默认值,缺参/未知/重复参数为编译错;有初始化方法时命名构造 类名{键: 值} 的键对应初始化参数(无初始化时仍对应字段名);子类初始化方法执行前自动先调父类初始化(父参数按同名匹配、缺失且无默认值则编译错),子类没有自己的初始化时继承父类签名;跨模块构造改写为类所在模块的合成工厂并补参数默认值(跨模块命名构造仍不支持)。初始化方法挂载在隐藏名 __初始化_类名 上,不可显式 对象.初始化(…);合成工厂名 __构造_类名(__ 开头为内部保留名)。三端(zhc/自举/原生)一致。v2.20:结构体声明支持花括号——结构体 点 { x, y } 与缩进式声明等价(字段至少 1 个、不得重复;花括号内换行被忽略,字段以逗号或空白分隔);命名构造 点{x: 1, y: 2}(键为字段名,标识符或字符串,顺序任意):缺省字段补零值(结构体)或类默认值(类),未知字段/重复字段为编译错,命名与位置构造不可混用;命名构造目前只支持本文件已声明的结构体/类(跨模块用位置构造),解析期展开为普通构造调用,三端(zhc/自举/原生)一致;位置构造 点(1, 2) 不变。标准库改为面向对象实现——标准库/*.zh 内的实现改为隐式模块中的私有工具类 + 静态方法,公开内置名保留薄包装(行为、错误文案、行号口径不变);结构体注册 id 改按实际发射顺序重编(按需发射跳过标准库模块时不再出现 id 空洞)。模块.类.静态方法(…) 链式调用与 引入 … 为 别名 同名写法(允许别名等于模块自身名)加入;链式调用在编译期绑定静态方法,三端语法一致(原生后端仍受「模块内结构体/类未移植」限制)。可选入口 main/主函数——入口模块没有顶层可执行语句时自动调用无参入口函数(整数返回值作退出码,非整数/无返回按 0);有顶层语句时不自动调用(三端一致;main 与 主函数 不能同时定义,自动调用时不得带参数)。v2.19:类语法移植到 C 版与自举编译器(继承/封装/多态)——类 名[(父类)] + 缩进类体:字段可写 名字 [类型] [= 默认],也兼容 属性 名字 [类型] [= 默认];类体内 函数 方法(参数) 为实例方法(隐式 本);类体内的 @私有 表示该类仅当前文件可用(模块外限定访问与选择性引入均报编译错)。顶层新增 @类方法(类名)(实例方法,注入 本)与 @类函数(类名)(静态方法,不注入 本,以 类名.函数(…) 调用);函数 类名:方法(…) 旧写法保留。构造调用自动补零值/默认值(无类型→假,数值/整数→0,浮点→0.0,字符串→"",数组/字典→空);子类字段含父类字段且可重写继承字段的默认值;类模板原型指向父类模板,方法沿链查找/覆盖,是 沿链判定;对象.方法(…) 自适应接收者(声明参数数 = 实参数+1 时自动传对象为 本,显式写法仍可用)。类可定义在被引入的模块文件中(类方法挂载是模块内唯一允许的顶层语句);修复自举编译器选择性引入结构体时把 [假,假] 误当函数值的旧 bug。C 版/自举/原生后端三端对拍一致,ELF、PE、aarch64 验证。v2.18:原生后端类语法(实验,先于 C 版)——类 名[(父类)] + 属性 名字 [类型] [= 默认] + 类内 函数 方法(参数);可选冒号;也支持顶层 函数 类名:方法(参数)。解析器在编译期脱糖为现有 结构体 + 类.方法 = 本 => …:构造调用自动补零值/默认值,类模板原型指向父类模板(方法查找沿链、可覆盖),对象.方法(args) 自适应接收者(声明参数数 = 实参数+1 时自动把对象作为 本 传入,兼容 对象.方法(对象, …) 旧写法),是(x, 父类) 沿原型链判定。x86_64 ELF 与 PE 已验证;C 版 zhc 与自举编译器尚未移植该类语法(bootdiff 跳过带「原生专用」标记的用例)。v2.17:原生后端正则(实验)——新增 正则匹配/正则全匹配/正则查找/正则全部/正则分组/正则替换/正则分割/正则转义(字节级回溯引擎:字面量、.、^$、字符类与 \d\w\s、分组、|、* + ? {n} {n,} {n,m}、懒惰量词;空匹配遵循 Python finditer 口径;替换支持 $0..$9/$$),ELF 与 PE 输出与完整版逐字节一致。同轮把跨类型 ==/!= 改为直接不等(C 语义,不再报类型错);REPL 在编译/运行期间保持终端原始模式,避免 ^C 触发 SIGINT 冲刷输入队列丢键。官网 14 个示例中 13 个可编译运行。v2.16:原生后端文件 IO/JSON/REPL(实验)——x86_64-linux 目标新增 读文件/写文件/追加文件/文件存在/文件大小/删除文件/建目录(直接 syscall)与 编码JSON/解码JSON(紧凑格式、转义/\u 转义、null→假、插入序),Windows 目标文件 IO 未移植(调用时明确报错,JSON 可用);原生 --repl 与完整版同款「整段会话重跑」:TTY 行编辑(方向键/Home/End/Delete)、上下键历史(落盘 ~/.zhlang_history)、Tab 缩进、Ctrl-C 取消、Ctrl-D 退出、关键字/字符串/数字/注释着色,表达式自动打印,块/未闭合字面量续行,运行失败的行不计入会话。官网 14 个示例中 12 个可编译运行。v2.15:原生后端语法扩展(实验)——x86_64 后端补齐模块(@引入/为 别名/只/@私有)、浮点(/ 恒为浮点、混合数值运算/比较/转换/类型标注、15 位有效数字显示)、模板字符串、结构体(声明/构造/字段读写/方法/是/结构体标注)、闭包与一等函数(值捕获、lambda 内改写外层变量、具名函数作值、映射/过滤/归约/排序)、指针(&/* 读写与相等)、容器与浮点打印、字符串内置(分割/连接/查找/替换/去空白/转大写/转小写/开头是/结尾是/重复)以及裸名 标准库/ 模块查找;x86_64-linux ELF 与 x86_64-windows PE 均通过(前者本机、后者 wine),官网 14 个示例中 11 个可编译运行且输出与完整编译器逐字节一致。aarch64 后端仍为子集(模块已支持;容器/浮点/闭包/结构体未移植)。已知限制:浮点显示 15 位有效数字(完整编译器为最短往返,个别数值会少末位)、闭包按地址捕获仅在外层栈帧存活期有效、协程/正则/网络/JSON/通用文件 IO 未移植。v2.14:原生后端 P0(实验)——自举/原生.zh 用纯 zhlang 把 AST 直接编成 x86_64-linux 静态 ELF(自编机器码/ELF 写出/直接 syscall,不经过 TinyCC),覆盖整数/布尔/字符串、算术/比较/位运算/逻辑、如果/当/跳出/继续、函数与递归、打印;未覆盖特性明确报「原生后端暂不支持」;新内置 写字节(writebytes)(0..255 整数数组 → 原始二进制;ELF 等含 NUL 内容必需,写文件 的字符串以 NUL 结尾)。v2.13:导入唯一写法 @引入——裸 引入 退役(报「已改名:请写「@引入」」),@引入 … 为/只 与裸名标准库引入照旧;REPL 交互升级——TTY 下自管行编辑(方向键/Home/End/Delete、上/下历史、Tab 缩进、Ctrl-C 取消当前行、Ctrl-D 退出)、输入按语法着色(关键字/字符串/数字/注释),历史落盘 ~/.zhlang_history(可用 ZHC_REPL_HISTORY 覆盖);新增四个内置支撑原语:终端原始模式/读键/显示宽度/字符(§6;Windows 控制台回退系统自带行编辑)。v2.12:标准库迁移第二波——哈希SHA256/哈希MD5/编码URL/解码URL/转整数 也改由 zhlang 实现(共 20 个内置,标准库/摘要.zh 加入隐式层);标准库按需发射——隐式标准库模块整模块粒度按需生成,未被引用的模块不进入产物(不调用迁移内置的程序,其产物与 ZHC_NO_STDLIB=1 逐字节相同)。v2.11:标准库用 zhlang 编写——《规格》§6 中的 15 个内置(字符串/编码/容器/数学)改由 zhlang 源码模块实现(标准库/,隐式装载进每个模块,非限定名直接可用;不可重定义/不能当值/内置式 arity 文案与从前完全一致),C 实现保留作 ZHC_NO_STDLIB=1 回退;模块装载「磁盘优先、内嵌兜底」(发行版内嵌标准库文本,ZHC_STDLIB 可指目录或强制内嵌);另加用户可引入的标准库模块层(标准库/集合.zh:反转/最大值/最小值,裸名 引入 "集合.zh" 在编译器旁 标准库/ 查找),新内置 类型(type)(返回值的类型名:整数/浮点/字符串/布尔/通道/数组/字典/套接字/函数/指针)。实现于标准库的内置,其运行时错误消息正文逐字不变,行号指向标准库实现行(见 §7)。v2.10:切片 c[起:止]——半开区间(起含止不含),起/止可省略(s[:3]/s[3:]/s[:])、负数从尾部数、越界钳位不报错;字符串按码点、数组按元素,其他类型运行期报错;不可作赋值目标(编译错)。网络解析兜底——系统解析失败时用自写最小 UDP DNS 客户端自解析(nameserver 来源 ZHC_DNS 环境变量 → /etc/resolv.conf → $PREFIX/etc/resolv.conf,先 A 后 AAAA;musl 静态二进制在 Android/Termux 上零配置可用;ZH_FORCE_DNS 强制走兜底(测试用));失败报文区分「解析失败/连接失败」。clang/bionic 兼容——aarch64 协程上下文改 void* 字段(clang 的 int-conversion 硬错误)、补 <netinet/in.h>/<arpa/inet.h>、去 strtok(bionic 弃用)。v2.9:声明改名 变量/常量(让 退役:可变绑定用 变量,不可变绑定用 常量 —— 绑定不可变、容器内容仍可变,常量性随闭包捕获传播;旧写法报「已改名」指引);类型标注(可选)——声明可写内置类型位(整数/浮点/字符串/布尔/通道/数组/字典/套接字/函数/指针)或结构体名,声明与每次赋值运行时校验;全开箱——三目标 sysroot 以 gzip 归档内嵌进编译器发行版(磁盘 sysroot 优先,缺档时按需解压到缓存;自写 gzip/DEFLATE/ustar 解包器 runtime/zhccgz.c;构建侧 --embed-sysroot),新内置 确保归档(ensurearchive);生成的 C 可交 clang/macOS 直接编译(spawn.h 缺席时 执行捕获 回退 fork/execvp);v2.8:Windows 中文路径全链路——参数()/程序路径() 改从 GetCommandLineW 取(CRT 的 argv 是 ANSI 码页字节,中文参数变 ??);内置 C 后端(tcc)的产出先写 ASCII 临时名再用宽字符 MoveFileExW 挪到目标,编译C 读 .c 也走宽字符 —— 中文的源码名、产物名、.c 名在 Windows 上从此可用(此前「你好.exe」会写成乱码名);v2.7:内置 真实路径(realpath)(规范绝对路径,解析符号链接);自举编译器的模块装载对齐到与 C 版逐字(去重真按 realpath——同一文件经符号链接等不同写法只算一次;入口/引入打不开带 strerror 原文;目录报「内存不足」同款;--tokens/--ast 只看入口文件、不碰 引入),并补齐 C 版漏掉的七个 v2.6 英文别名(compilec/execcap/isterm/ignoreint/exepath/inputeof/output);v2.6:进程与终端内置(执行捕获/是终端/忽略中断/程序路径/输入结束/输出,REPL 与 CLI 全部用语言自身写成);自举编译器的 CLI 与 C 版 zhc 逐字对齐(--help/--version/--targets/--from-c/--gcc/--repl 等全部搬进 zhlang 侧,见 自举/README 与 tools/clidiff.sh);v2.5:内置 C 后端编译C(把 C 源编译成产物,自举编译器据此自带后端);v2.4:协程栈溢出守护在 Linux 上真正生效(守护页 mprotect + 每线程备用信号栈)、主协程栈 32 MB;v2.3:内置 打印错误(eprint)(同 打印 的拼接规则但写标准错误并立即冲刷,供诊断/日志);v2.2:分点式抢占(忙协程占满 worker 时不再饿死睡眠/长运算协程);v2.1:HTTPS/TLS(内置 HTTP 客户端支持 https:自包含 mbedTLS 客户端 + 证书链/主机名校验);v2.0:正则表达式(八内置,自包含回溯引擎,码点级,逐项对齐 Python re)与原始字符串 r"…";v1.9:按引用捕获(被闭包捕获且被改写的变量自动装箱为共享单元)、模块注解式可见性 @私有 与 引入 … 为 别名/引入 … 只 名字、模块函数作值;v1.8:位运算(& | ^ ~ << >> 与复合式)、进制字面量(0x/0b/0o)与数字分隔 _、箭头函数语句体、BASE64 与 SHA256/MD5、结构体(声明式)、选择(select);v1.7:对象方法调用(对象.方法())、原型链(继承/读走链/写落自身)、是() 类型判断;v1.6:一等函数与箭头函数(按值捕获)、映射/过滤/归约/排序;v1.5:结构体点语法(字典糖)、服务端套接字、复合赋值;v1.4:实用标准库(文件系统/JSON/HTTP+TCP/进程执行);v1.3:标准库扩充、模块系统、异常处理;v1.2:数组与字典;v1.1:缩进块、指针、内置工具链。v2.39 补丁(2026-11-01,本地时区/诊断/并发打印)——格式化时间 改为本地时区(Linux 解析 TZ 指向的 TZif,Windows 走 SystemTimeToTzSpecificLocalTime,无时区数据回退 UTC);ZL-17 剩余文案:字典键不存在:键、调用了非函数值(收到 类型)、函数「名」需要 N 个参数(实际 M 个);函数值对象在 +24 携带显示名,打印 <函数 名>。ZL-27 支持任意接收者 造().方法(…) 链式调用与 是(对象, 模块.结构体)。ZL-33 并发 打印 行级原子:先转字符串(可分配/GC),再持输出锁整行写出。ZL-24 语义/模块编译错误统一为 文件:行:列: 错误:消息 + 源码行 + ^,跨模块错误指向模块文件。文本拼接新增 字符串构造/字符串追加/字符串取值(200k 次追加约 0.3s,替代 + 的 O(n²));运行期整数溢出提供 加溢出/减溢出/乘溢出 与抛错的 加检/减检/乘检(普通 + - * 仍按规格环绕);堆用量 与 堆统计.字节 排除协程栈虚拟预留,堆统计 新增 栈字节;连接/重复/替换 的运行时实现改为线性;常量赋值错误附带「请声明为变量」提示;新增长度别名 串长/字符串长度;修正并发服务器示例的 Content-Length(UTF-8 字节数);隐式标准库新增数组/字典操作 索引/计数/去重/求和/反序/扁平/最大元素/最小元素/复制/合并/字典筛选/字典映射。

zhlang 是一门中英混合关键字的动态类型语言:手感取 Python 的简易(动态类型、缩进块、模板字符串、真值规则),并发取 Go 的轻量(协程 + 通道),并支持 C 风格指针。源码由纯 zhlang 自举编译器直接生成原生二进制,无需任何外部工具链。


1. 词法

1.1 字符集与标识符

1.2 换行敏感规则(语句分隔)

词法先产生独立的「换行 token」(连续换行与空行折叠为一个;; 亦产生换行 token;EOF 前补一个),随后做过滤遍,删除满足以下任一条件的换行 token:

  1. 前一个 token 是 + - * / % < <= > >= == != && || ! &、=、,、.、(、[、{,或关键字 与/或/非/从/到(即行尾是「期待后续」的位置;|/^/~/<</>> 与 : 不在其列);
  2. 下一个 token 是 )、]、}、否则、否则如果、从、到;
  3. 当前处于括号深度 > 0(小括号、方括号、花括号或模板插值内)——因此数组/字典字面量与索引可跨多行书写。

保留下来的换行即语句分隔符。语义后果(Go 同款取舍):

1.2.1 缩进块(Python 式)

块由行首缩进表示,不使用花括号(花括号用于字典字面量、结构体声明的字段表与命名构造;语句位置出现的 {/} 会得到引导性报错):

1.3 非法字符与友好提示

1.4 运算符与优先级(从低到高)

级运算符备注
1或 / or / ||短路
2与 / and / &&短路
3|位或(双目)
4^位异或(双目)
5&位与(双目;单目位置为取地址)
6== !=
7< <= > >=
8<< >>移位(次数 0..63)
9+ -+ 含字符串/数组拼接
10* / %/ 为真除法
11! / 非 / not、一元 -、~、&(取地址)、*(解引用)右结合
12容器[下标]、容器[起:止]、表达式.字段、表达式->字段、模块.函数(…)、@模块.名字、对象.方法(…)、对象->方法(…)、表达式(…)(间接调用)后缀,可链式混用:m[i][j]、p.a[0].b、f(1)(2)、列表[0](x)、p->a->b;点号带调用括号=限定调用(首名为已引入模块 → 模块调用,否则按 对象.方法(…) ≡ 对象["方法"](…)),不带=字段访问;-> 用于指针/对象引用(见 §2.1)

复合赋值:+= -= *= /= %= &= |= ^= <<= >>=(目标子表达式只求值一次)。 二元运算符一律左结合。=>(箭头函数)不出现在上表:它只能在表达式开头(primary 位置)出现,函数体向右贪婪延伸到最近的括号/逗号边界,或在 => 后以换行+缩进给出语句块体(见 §2.5)。


2. 类型与值(动态)

运行时值共十种:

类型取值字面量示例
整数int6442
浮点IEEE754 双精度3.14
字符串不可变字节序列(UTF-8)"你好" 共${n}个
布尔真 / 假真 false
通道运行时对象通道() 的返回值
指针某个变量槽的地址&x 的返回值
数组可变长有序序列(引用)[1, "二", [3]] []
字典键值表(引用,保插入序){"名": "张三", 1: 真} {}
套接字TCP 连接(运行时对象,按身份比较,GC 自动关闭)连接到(…) 的返回值
函数一等函数值(命名函数或箭头函数,按身份比较)双倍、x => x * 2

2.1 运算语义

2.2 真值规则(Python 式)

类型为真条件
布尔即自身
整数 / 浮点非零(NaN 为假)
字符串非空
数组 / 字典非空(带原型的字典恒为真)
通道 / 指针 / 套接字 / 函数恒真

2.3 转字符串(拼接、打印、转字符串 共用)

2.4 容器:数组与字典

数组 —— 有序、可变长、元素为任意值:

字典 —— 键值表,保持插入顺序:

索引的统一规则:c[k] 对数组(整数下标)、字典(键)、字符串(按码点取单字符子串,只读——赋值 → 运行时错误)有效;其他类型 → 运行时错误「[]索引需要数组、字典或字符串」。

切片 c[起:止]:半开区间(起含、止不含);两端均可省略(s[:3]、s[3:]、s[:] 取全);负数从尾部数(与下标一致);越界钳位不报错,起 ≥ 止 得空;字符串按码点、数组按元素;其他类型 → 运行时错误「「[起:止]」切片需要字符串或数组」;端点非整数 → 运行时错误「切片端点需要整数」;切片不可作赋值目标(编译错)。数组切片产生新数组(元素按值引用语义,与数组赋值别名同款)。

字段访问(结构体糖):表达式.标识符 ≡ 表达式["字段名"](仅字典;字段名须为合法标识符,不能是关键字)——p.名字 读、p.年龄 = 值 写,与索引完全互通(遍历/深相等/键值照用);写字段不存在即新建键(动态语义)。对非字典容器/值 → 按统一索引规则报运行时错误;模块.函数 不带括号 = 函数值(见 §3.3);模块名下没有该函数/结构体 → 编译错。

对象.方法(…):与字段访问同源的语法糖,对象.方法(实参…) ≡ 对象["方法"](实参…)。resolve 期区分:点号前的名字是已引入模块 → 模块调用(模块名优先);否则必须是变量(对象)→ 方法调用。被调值不是函数 → 运行时错误「调用了非函数值…」;参数个数在运行时检查(同其他间接调用)。

原型链(继承):继承(原型[, 添加字段字典]) 生成携带隐藏原型指针的新字典(参数非字典 → 运行时错误):

类型判断:是(对象, 原型) —— 对象自身或原型链上任一节点与原型身份相同(指针相等,不按结构)→ 真;第一参非字典 → 假(类型探测);第二参非字典 → 运行时错误。是(狗, 狗) 为真(自包含)。

结构体(声明式):结构体 名 + 缩进块每行一个字段标识符,或花括号形式 结构体 名 { 字段, … }(两种等价;至少 1 个字段,不得重复;顶层声明,函数/模块文件均可)。花括号内换行被词法层忽略,字段以逗号或空白分隔。

类(脱糖为结构体,继承/封装/多态):类 名[(父类)] + 缩进类体;类体可直接写字段 名字 [类型] [= 默认](前缀可写 变量/常量/属性,三者目前等价:类字段脱糖为原型字典槽,不做只读强制),也可写实例方法 函数 方法(参数)(签名等价于 本 => 方法体,参数自动加上 本)。类体内的 @私有 表示整个类仅当前文件可用(与声明上一行的 @私有 同义)。

遍历:循环 x 在 容器 —— 数组遍历元素、字典遍历键、字符串遍历码点(单字符);循环变量在循环块作用域内;容器只求值一次;循环中修改容器是否影响本轮遍历不做保证(实现按长度快照遍历)。

2.5 一等函数与箭头函数

函数是值:可存入变量/容器、作参数与返回值、经任意表达式调用(f(3)、加(3)(4)、策略["加"](x, y)、列表[0](x))。


3. 语句与程序结构

3.1 BNF 文法

(* v2.23 修订:按自举版实现补齐裸名引入、跨模块类方法挂载、
   选择、切片、位运算与类体;模块文件的顶层限制见 §3.3。 *)

program   ::= { import | decl | stmt }
import    ::= "@" ("引入"|"import") (STRING | IDENT)
              [ "为" IDENT | "只" IDENT { "," IDENT } ]
              (* 必须写在其他顶层内容之前;裸名 ≡ "名字.zh";见 §3.3 *)

decl      ::= [ annotation ] ( funcdef | structdecl | classdecl )
annotation::= "@" ("私有"|"private")
            | "@" ("类方法"|"类函数") "(" [ IDENT "." ] IDENT ")"
              (* 一条声明至多一个注解;类方法/类函数只能修饰紧随其后的
                 顶层 函数,且不得与「函数 类名:方法」旧写法连用;见 §3.2 *)

funcdef   ::= ("函数"|"func") IDENT [ ":" IDENT ] "(" [ params ] ")" [ ":" ] block
              (* 旧写法「函数 类名:方法(…)」;普通函数参数无默认值,最多 8 个 *)
params    ::= IDENT { "," IDENT }

structdecl::= ("结构体"|"struct") IDENT
              ( structfields | "{" IDENT [ "," ] { IDENT [ "," ] } "}" )
structfields ::= 换行 缩进↑ { IDENT } 缩进↓                  (* 至少 1 个字段 *)
              (* 花括号体内字段以逗号或空白分隔,允许尾逗号;见 §2.4 *)

classdecl ::= "类" IDENT [ "(" IDENT ")" ] [ ":" ] classbody     (* 仅中文;class 未支持 *)
classbody ::= 换行 缩进↑ { classmember } 缩进↓               (* 至少 1 个字段 *)
classmember ::= "@" "私有"                                   (* 整个类仅本文件可用;
                                                              规范写法是类声明上一行 @私有;
                                                              类体内不接受英文 private *)
              | [ "属性" | "变量" | "常量" ] IDENT [ IDENT | "函数"|"func" ] [ "=" expr ]  (* 字段;第二个 IDENT 是类型标注 *)
              | ("函数"|"func") "初始化" "(" [ initparams ] ")" [ ":" ] block
              | ("函数"|"func") IDENT "(" [ params ] ")" [ ":" ] block
initparams::= IDENT [ "=" expr ] { "," IDENT [ "=" expr ] }  (* 仅初始化方法支持默认值 *)

stmt      ::= "@" ("断点调试"|"调试断点"|"breakpoint") [ "(" [ args ] ")" ]
              (* 调试指令;仅原生后端 --debug/-d 构建生效,见 §6.10 *)
            | ("变量"|"var"|"常量"|"const") IDENT [ IDENT | "函数"|"func" ] "=" expr
            | assign_target ( "=" | AUGOP ) expr
            | "*" expr "=" expr                               (* 指针写入;仅普通 = *)
            | ("启动"|"go") [ "@" ] [ IDENT "." ] IDENT "(" [ args ] ")"
              (* 带 @ 时必须写 模块.;只能启动自定义函数(含 模块.函数),
                 不能启动内置函数、函数值或类方法;见 §2.5 *)
            | ("如果"|"if") expr block
                { ("否则如果"|"elif") expr block }
                [ ("否则"|"else") block ]
            | ("循环"|"for") IDENT ("从"|"from"|"=") expr ("到"|"to") expr block
            | ("循环"|"for") IDENT ("在"|"in") expr block
            | ("当"|"while") expr block
            | ("尝试"|"try") block [ ("捕捉"|"catch") IDENT block ]
                  [ ("最终"|"finally") block ]                (* 捕捉/最终至少一个 *)
            | ("返回"|"return") [ expr ]
            | ("跳出"|"break") | ("继续"|"continue")
            | selectstmt
            | expr                                            (* 仅函数调用可作表达式语句 *)

selectstmt::= ("选择"|"select") 换行 缩进↑ { selectarm } 缩进↓
selectarm ::= "接收" expr [ "为" IDENT ] block
            | "发送" expr "," expr block
            | ("否则"|"else") block                          (* 必须最后;1..8 个臂 *)

assign_target ::= IDENT { "." IDENT | "[" expr "]" }
                  (* 变量/索引/字段写入,可复合赋值;切片不可作赋值目标;
                     指针写入是单独的「* expr = expr」形式,不支持复合赋值 *)
AUGOP     ::= "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>="

block     ::= 换行 缩进↑ { stmt } 缩进↓
args      ::= expr { "," expr }
namedarg  ::= (IDENT | STRING) ":" expr

expr      ::= or
or        ::= and { ("或"|"or"|"||") and }
and       ::= bitor { ("与"|"and"|"&&") bitor }
bitor     ::= bitxor { "|" bitxor }
bitxor    ::= bitand { "^" bitand }
bitand    ::= eq { "&" eq }
eq        ::= rel { ("=="|"!=") rel }
rel       ::= shift { ("<"|"<="|">"|">=") shift }
shift     ::= add { ("<<"|">>") add }
add       ::= mul { ("+"|"-") mul }
mul       ::= unary { ("*"|"/"|"%") unary }
unary     ::= ("!"|"非"|"not") unary | "-" unary | "~" unary
            | "&" IDENT | "*" unary | postfix   (* & 只能对变量取地址 *)

postfix   ::= primary { "[" expr "]"                        (* 索引 *)
                     | "[" [ expr ] ":" [ expr ] "]"        (* 切片:至少一个冒号 *)
                     | "." IDENT                           (* 字段访问;点号后不接 ( *)
                     | "->" IDENT [ "(" [ args ] ")" ]     (* 指针/对象成员;方法可带调用 *)
                     | "(" [ args ] ")" }                  (* 间接调用 *)
primary   ::= INT | FLOAT | STRING | TEMPLATE
            | ("真"|"true") | ("假"|"false")
            | "[" [ args ] "]"                              (* 数组字面量 *)
            | "{" [ expr ":" expr { "," expr ":" expr } ] "}"  (* 字典字面量 *)
            | "(" expr ")"
            | IDENT
            | IDENT "(" [ args ] ")"                        (* 调用 *)
            | IDENT "{" [ namedarg { "," namedarg } ] "}"   (* 命名构造 *)
            | [ "@" ] IDENT "." IDENT "(" [ args ] ")"      (* 模块.函数 / 对象.方法 / @模块.函数 *)
            | [ "@" ] IDENT "." IDENT "." IDENT "(" [ args ] ")"  (* 模块.类.静态方法 *)
            | [ "@" ] IDENT "." IDENT                       (* @模块.名字 / 模块.类 值 *)
            | IDENT "=>" arrowbody                          (* 单参箭头函数 *)
            | "(" [ IDENT { "," IDENT } ] ")" "=>" arrowbody  (* 零参/多参箭头函数 *)

arrowbody ::= expr | block

3.2 语义细则

3.3 模块系统(@引入)

3.4 异常处理(尝试/捕捉)

3.5 文档注释与 HTML 文档(v2.28 增补)

@私有/@类方法/@类函数 注解。文件首个注释块(其后跟空行或非声明内容) 视为模块文档;紧跟声明的注释块归属该声明。

@说明/@description、@参数/@param 名 说明、@返回/@return、 @示例/@example、@弃用/@deprecated。未加标签的行归入说明; @示例 之后到下一个标签之间的行原样作为代码块。

sh build/原生 tools/文档生成.zh 源1.zh [源2.zh …] -o 文档.html \ [--布局 自动|桌面|移动] [--含私有] [--仅文档]

输出自包含单文件 HTML(内联 CSS/JS、viewport、深色模式、侧栏导航、符号搜索, 锚点 #sN,sN 按文件/声明顺序全局编号)。布局:自动 窄屏折叠为单列, 桌面 固定双栏,移动 强制单列。默认跳过 @私有 声明与私有类的成员; --含私有 包含它们,--仅文档 只输出有文档注释的符号。

需要原生后端运行。生成器按行扫描:签名取声明首行,结构体字段与接口方法暂不 单独列卡片。

3.6 装饰器(原生后端,v2.34 增补)

仅原生后端(x86_64/aarch64/PE)支持;冻结的 C 版引导器与 自举/ 源码不可使用。

zh @装饰器 函数 计时(f) 返回 (x) => 常量 起 = 当前时间() 常量 结果 = f(x) 打印("耗时", 当前时间() - 起) 返回 结果 可与 @私有 叠加;装饰器名不得与内置 @ 指令重名(如 私有/引入/类方法)。

4. 关键字全表(中英 → 同一 token)

中文英文用途中文英文用途
变量var声明(可变)常量const声明(不可变)
函数func定义函数从from循环下界(英文可用 =)
结构体struct结构体声明返回return返回
到to循环上界在in容器遍历
如果if分支当while条件循环
否则如果elif多分支跳出break跳出循环
否则else否则继续continue下一轮
真true布尔真启动go启动协程
假false布尔假与and逻辑与(&&)
引入import引入模块(仅 @引入)尝试try异常处理
选择select多路通道等待捕捉catch捕捉异常
最终finally必然执行的清理块或or逻辑或(||)
循环for计数循环非not逻辑非(!)

以下按上下文识别的软关键字词法上仍是标识符,不占关键字 token:类(类声明)、类方法/类函数(方法挂载注解)、属性(类字段前缀)、初始化(构造方法名)、接收/发送(选择臂)、为(模块别名/选择绑定)、只(选择性引入)——以上均无英文别名。另有 私有 也是软关键字,英文别名 private 只在顶层注解位置可用(类体内只认 @私有)。

@ 不是关键字而是注解/限定前缀:@私有(声明上一行,仅本模块可见)、@引入(唯一导入写法;裸 引入 已退役)、@模块.名字(≡ 模块.名字,含 启动 @模块.函数(…))。


5. 并发模型(协程 + 通道)

5.1 协程

协程可跨 worker 迁移;分点式抢占是 C 运行时的实现,原生运行时在分配点/循环回边 做 GC 安全点,不做节拍式抢占)。每个协程独立 mmap 栈(4MB;并行代码生成的递归帧所需); worker 线程数 = CPU 核数(2..8,ZHC_THREADS 可覆盖)。

分点按节拍(每 256 次)做一次调度节拍——唤醒到期睡眠者,并在有其他协程在等 worker(就绪队列非空)时让出, 由当前 worker 把本协程重新入队(轮转到队尾)。因此忙协程占满全部 worker 时,睡眠协程与长运算协程仍能推进; 「一个协程、就绪队列为空」的常见形态只做一次加锁检查,不产生额外切换。 持运行库锁时绝不让出(打印的输出锁等),避免让出把别的 worker 锁死。留白:不含信号式异步抢占 ——单个分点之间不被打断(例如一次超大分配、一次超长正则回溯)。

→ 越限时报不可捕获的运行时错误「协程栈溢出」;三端同款,不依赖守护页/信号。 (协程栈 4MB;主协程 256MB——只是虚拟预留、 按需提交,普通程序只碰几页;留这么大是为了「用 zhlang 写编译器/解析器」这类深递归程序)。

5.2 通道

5.3 选择(多路通道等待)

选择
    接收 ch1 为 v
        …使用 v…
    接收 ch2
        …丢弃收到的值…
    发送 ch3, 值
        …
    否则
        …都未就绪时的分支(可省)…

5.4 内存管理(自动垃圾回收)

仍登记到独立分配表(地址/大小/类别;表本身一次 192MB 匿名映射),并按阈值 在分配点触发回收:

+ 协程上下文寄存器 + 全局/栈保守扫描(每个 8 字节字凡落在已登记块内 即视为指针);协程栈整块置特殊类别,标记传播跳过、改按活跃区间扫描;

后续分配优先复用(小块复用清零,保持「新分配为零」语义),堆字节随之回落;

max(存活 × 2, 32MB),与本节上文一致);页索引按 4KB 页建立(页内平均块数 约 8;64KB 页实测 ~127,单字查找链过长);页节点/标记栈溢出时本轮不回收 (并翻倍预算退避),表满则永久停用 GC——失败方向都是「不回收」,不会错回收;

{块数, 字节, 容量, 表基, GC次数, GC中止, GC存活, GC块数, GC页节点} (字节为当前存活登记字节;GC* 为诊断计数), 堆整理() 压缩地址为 0 的无效登记项并返回是否已建立分配表; 堆回收() 强制触发一次完整回收(调试/测试入口)。


6. 标准库(内置函数,不可重定义;均提供英文别名)

签名(英文别名)说明
打印(print)逐个按转字符串规则输出,空格分隔,末尾换行;至少 1 个至多 8 个参数;返回 假(因此可作表达式)
打印错误(eprint)同 打印,但写标准错误并立即冲刷(诊断/日志用;可作表达式,返回 假)
长度(len)字符串长度(按码点计);数组元素数;字典键数
子串(substr)按码点取子串;长 可超出末尾(截断);起/长 为负或 起 越界 → 运行时错误「子串越界」
查找(find)首次出现的码点下标,找不到返回 -1
替换(replace)全部替换,返回新串
转整数(toint)字符串(允许首尾空白)/浮点(截断)/整数/布尔 → 整数;无法解析 → 运行时错误「无法将「…」转换为整数」
转浮点(tofloat)同上 → 浮点
转字符串(tostr)→ 字符串(同 §2.3)
类型(type)值的内置类型名:整数/浮点/字符串/布尔/通道/数组/字典/套接字/函数/指针(标准库的 zhlang 实现用它做类型守卫;与 是() 的原型链判定不同,后者测的是「是不是某结构体的实例」)
读入(readln)从标准输入读一行(不含换行);EOF 返回空串
读文件(readfile)整个文件读为字符串;失败 → 运行时错误「无法读取文件「…」:原因」
写文件(writefile)覆盖写;返回写入字节数;失败 → 运行时错误
写字节(writebytes)写字节(路径, 数组) 把元素为 0..255 的整数数组按原始字节写入;返回写入字节数(字符串以 NUL 结尾,ELF 等二进制必须用它)
当前时间(now)Unix 时间戳(浮点秒)
通道(chan)创建通道(见 §5.2)
发送(send) / 接收(recv) / 关闭(close)见 §5.2
休息(sleep)当前协程睡眠指定毫秒数(须非负整数,否则运行时错误);返回 真。原生后端在调度已启动时把协程挂到睡眠队列并让出 worker,不占住线程
协程数(cocount)存活协程数(含主协程)
堆用量(heapuse)GC 堆当前字节数(不含协程栈的虚拟预留;原生后端取最近一次回收后的存活登记字节 − 栈字节)
堆统计(heapstats)原生后端 GC:返回 {块数, 字节, 容量, 表基, 栈字节, GC次数, GC中止, GC存活, GC块数, GC页节点}(字节为当前存活登记字节且不含栈;栈字节为协程栈预留之和);未建立分配表时全为 0
堆整理(heapcompact)原生后端 GC:压缩地址为 0 的无效登记项,返回是否已建立分配表(不回收)
堆回收(heapcollect)原生后端 GC 阶段 2:强制触发一次完整回收(标记-清除 + 空闲链复用),返回 真
追加(push)追加(a, v1, …) 数组末尾追加 1–7 个值,返回数组本身(就地修改)
弹出(pop)移除并返回数组末元素;空数组 → 运行时错误
取(get)取(d, k[, 默认]) 字典安全读取;缺键返回 默认(省略时 假)
删除(del)删除(d, k) 移除键,返回 真/假(是否原本存在);不影响其余键序
包含(contains)数组按值查、字典按键查(沿原型链),返回布尔
继承(inherit)继承(原型[, 添加]) → 带原型指针的新字典(隐藏只读;读走链、写落自身、枚举只看自己,见 §3 原型链)
是(is)是(对象, 原型) → 对象自身或原型链上有该原型(身份比对);第一参非字典为假
键(keys) / 值(values)字典的键数组 / 值数组(按插入序)
映射(map)映射(数组, f) → 新数组(f(元素) 逐个);数组与 f 类型错 → 运行时错误
过滤(filter)过滤(数组, f) → 保留 f(元素) 为真的元素组成新数组
归约(reduce)归约(数组, 初值, f) → 左折叠,f(累积, 元素)
索引(数组, 值) / 计数(数组, 值)首次出现的下标(找不到 -1)/出现次数;按 == 的深度相等(标准库实现)
去重(数组)保持首次出现顺序的新数组;数值按 == 语义(1 与 1.0 同值),数组/字典按深度相等
求和(数组)数值和(整数保整数、混浮点升浮点;空数组为 0;含非数值 → 运行时错误)
反序(数组)倒序的新数组(原数组不变;原地反转用 集合.zh 的 反转)
扁平(数组)只展开一层;非数组元素原样保留
最大元素(数组) / 最小元素(数组)非空数组的极值(元素须可比较)
复制(容器)数组/字典的浅拷贝(内层容器仍共享)
合并(甲, 乙)新字典:甲浅拷贝后乙覆盖;甲已有键保持位置、乙新键按乙序追加
字典筛选(字典, f) / 字典映射(字典, f)f(键, 值) 为真保留 / 返回新值;保持键序
排序(sort)排序(数组) 默认升序(整数/浮点互通、字符串字典序;混类型 → 运行时错误);排序(数组, f) 用 f(a, b) 比较,f 须返回整数(负/零/正,返回其他类型 → 运行时错误);稳定排序,就地修改并返回数组本身
平方根(sqrt) 正弦(sin) 余弦(cos) 正切(tan) 对数(log) 指数(exp)数学函数(浮点入浮点出;负数开方/取对返回 NaN 不报错;参数须为数值)
幂(pow)幂(底, 指数) → 浮点
夹取 符号 最大公约数 最小公倍数数值夹取 / 符号(-1,0,1) / 欧几里得 GCD / LCM(任一为 0 返回 0)
阶乘 幂模 是否质数 质数列表阶乘(溢出抛错)、快速幂取模(模 ≤ 3037000499)、质数判定、≤1e7 的质数表
转进制 解析进制2..36 进制互转;2 的幂精确到 64 位,其余基受 2^53 限制
均值 中位数 方差 标准差 舍入到数值数组统计(空/非数值抛错)与按小数位四舍五入(半数远离零)
绝对值(abs)整数保整数,浮点为浮点
加溢出(a, b) / 减溢出(a, b) / 乘溢出(a, b)64 位整数 + / - / * 是否会溢出 → 布尔(标准库实现;普通 + - * 仍是规格的 64 位环绕)
加检(a, b) / 减检(a, b) / 乘检(a, b)返回运算结果;溢出时抛运行时错误「整数加法/减法/乘法溢出」(标准库实现)
向下取整(floor) 向上取整(ceil) 四舍五入(round)返回整数;四舍五入为「远离零」(2.5→3,-2.5→-3)
随机数(rand)随机数(n) → [0, n) 整数;须正整数;种子自动初始化
分割(split)分割(s, 分隔串) → 字符串数组;分隔串不能为空(空串报运行时错误)
连接(join)连接(数组, 连接串) → 字符串(元素按打印的顶层规则转字符串)
字符串构造() / 字符串追加(缓冲, 片段) / 字符串取值(缓冲)文本拼接缓冲(标准库实现):追加 O(1) 摊还、取值一次性连接;替代 s = s + x 的 O(n²) 累积
字符串长度(s) / 串长(s)长度(s) 的别名(按码点计;标准库实现)
转大写(upper) / 转小写(lower)仅 ASCII 字母;其余字节原样
去空白(trim)去两端空白(ASCII 空白 + 全角空格 U+3000)
开头是(startswith) / 结尾是(endswith)前缀/后缀判断 → 布尔;空前缀/后缀恒真
重复(repeat)重复(s, n);n 为负 → 运行时错误
反转文本 左填充 右填充 去前缀 去后缀 分行 字节长度 出现次数 是空白文本工具:按码点反转;填充到宽度;剥离前后缀;按行切分(去 CR);UTF-8 字节数;不重叠出现次数;纯空白判定
格式化时间(fmttime)格式化时间([时间戳[, 格式]]),strftime 格式,本地时区;默认当前时间与 %Y-%m-%d %H:%M:%S。支持 %Y %y %m %d %e %H %M %S %F %T %R %j %w %u %a %A %b %B %z %s %n %t %%(英文星期/月名,与 C locale 的 strftime 一致)
参数(args)命令行参数数组(不含程序名)。平台注:Windows 下取自 GetCommandLineW(UTF-8)——CRT 的 argv 是按 ANSI 码页解释的,中文参数会变 ??
抛出(raise)主动抛出异常(见 §3.4);非字符串自动转字符串
抛出对象(raiseobj)主动抛出并保留原值;捕捉变量得到对象(见 §3.4/§6.6);未捕获时优先显示值的「消息」字段
文件存在(exists) / 文件大小(filesize)stat;文件大小 对不存在的文件 → 运行时错误
真实路径(realpath)规范绝对路径(POSIX realpath(3):解析符号链接;Windows 绝对化 + 存在性检查);路径不存在/无法解析 → 运行时错误「无法解析路径「…」:原因」。编译器用它做模块去重(§3.3)
确保归档(ensurearchive)把编译器内嵌的某目标 sysroot 归档解压就绪,返回其缓存目录(无内嵌 → 空串)。自举编译器的 sysroot 预检用;普通程序恒得空串
删除文件(remove)unlink,返回是否成功
列目录(listdir)目录项名数组(不含 ./..);非目录 → 运行时错误
建目录(mkdir)递归创建(0755);已存在返回 真
追加文件(appendfile)追加写,返回字节数
解码JSON(jsondec)JSON → 值:null 解码为 假(取舍);\uXXXX(含代理对)转 UTF-8;语法错/尾随内容/嵌套>128 → 运行时错误(可捕获)
编码JSON(jsonenc)值 → JSON 字符串:字典键须为字符串;字符串转义(\" \\ \n、控制字符 \u00XX),UTF-8 透传;整数/浮点(最短往返)/真/假/数组/字典;嵌套>128(含循环引用)→ 运行时错误
编码URL(urlenc) / 解码URL(urldec)百分号编码(保留 -_.~ 与字母数字);解码时 +→空格
编码BASE64(base64enc) / 解码BASE64(base64dec)标准字母表带 = 填充;解码宽松容忍填充与换行,非法字符 → 运行时错误
编码十六进制(hexenc) / 解码十六进制(hexdec)UTF-8 字节 ↔ 小写十六进制;解码要求偶数长度,非法字符 → 运行时错误
哈希SHA256(sha256) / 哈希MD5(md5)输入的十六进制摘要(小写);MD5 仅供校验/去重等非安全用途
哈希HMAC(hmacsha256)HMAC-SHA256(键, 消息),返回小写十六进制;键超过 64 字节先取 SHA256(RFC 2104)
哈希文件(sha256file) / 哈希文件MD5(md5file)文件内容摘要(SHA256 / MD5,二进制安全)
取网页(httpget)HTTP/HTTPS GET → 响应体;DNS/连接/超时/状态≥400 → 运行时错误(含状态码);支持 https(见 §6.2)
网页请求(httpreq)网页请求(url[, 方法[, 头字典[, 体]]]) → {"状态": 整数, "体": 字符串, "头": 字典(键小写)};连接超时 5s,收发超时 10s;支持 chunked 响应;https 同上
连接到(tcpconn)连接到(主机, 端口[, 超时秒=5]) → 套接字(新值类型:按身份相等、恒真、显示为「套接字:N」;GC 自动关闭)
套接字发送(tcpsend)发送字符串(阻塞直到全部发出),返回字节数
套接字接收(tcprecv)套接字接收(s[, 最多字节=4096]) 阻塞收一次 → 字符串;对端关闭且无数据 → 空串
关闭套接字(tcpclose)关闭(幂等);未关闭的套接字由 GC 终结器关闭
监听(listen)监听(端口[, 待发队列=128]) → 服务套接字(SO_REUSEADDR,绑 0.0.0.0);端口占用 → 运行时错误
接受连接(accept)阻塞等待并返回客户端套接字(同 ZH_SOCK);范式:当 真 / 常量 c = 接受连接(srv) / 启动 处理(c)——每连接一协程
域名解析(resolvehost)域名解析(主机) → IPv4 地址数组:IPv4 字面量原样、/etc/hosts、DNS 全部 A 记录(原生运行时);ZHC_DNS 可写 IPv4 或 IPv4:端口;Windows 走 gethostbyname(单个)
UDP绑定(udpbind)UDP绑定(端口[, 绑定地址]) → UDP 套接字(SO_REUSEADDR;地址省略为 0.0.0.0);端口占用/地址非法 → 运行时错误
UDP发送到(udpsendto)UDP发送到(套接字, 主机, 端口, 数据) → 发送字节数;主机按 域名解析 规则解析
UDP接收自(udprecvfrom)UDP接收自(套接字[, 最多字节=65535]) 阻塞收一个数据报 → {"数据": 字符串, "地址": 字符串, "端口": 整数}
套接字设超时(socktimeout)套接字设超时(套接字, 秒) → 真;每次收发/接受独立计时(0 清除);Linux 非阻塞套接字由运行时计时,Windows 同时设 SO_RCVTIMEO/SO_SNDTIMEO;超时抛「套接字接收超时」等可捕获错误
本端地址(sockname) / 对端地址(peername)套接字本地/对端地址 → {"地址": 字符串, "端口": 整数}(getsockname/getpeername)
关闭写端(sockhalfclose)半关闭写方向(SHUT_WR);读方向仍可用
执行(shell)/bin/sh -c 执行,捕获 stdout → {"输出": 字符串, "退出码": 整数};stderr 原样透传
执行捕获(execcap)执行捕获(程序, 参数数组) 不经 shell 直接执行:argv 原样传,stdout+stderr 合流 → {"输出": 字符串, "退出码": 整数};程序起不来 → 运行时错误
是终端(isterm)标准输入是不是终端(isatty(0)) → 真/假
忽略中断(ignoreint)忽略中断(真/假) 置 SIGINT 忽略开关(REPL 在 Ctrl-C 下存活);返回 假
程序路径(exepath)本程序路径(argv[0]):CLI 靠它找自身目录下的 sysroot/后端归档
输入结束(inputeof)标准输入是否已到末尾(读入 在 EOF 与空行都返回 "",靠它区分)
输出(output)输出(s) 逐字写 stdout、不补换行、立即冲刷(提示符/前缀裁剪用);返回 假
终端原始模式(terminalraw)终端原始模式(真/假) 进入/退出行编辑模式(POSIX:清 ICANON/ECHO/ISIG/IEXTEN,保留 ICRNL/OPOST;Windows:清 ENABLE_LINE_INPUT/ENABLE_ECHO_INPUT/ENABLE_PROCESSED_INPUT,并对控制台输出句柄开启 ANSI/VT 处理);成功返回 真;非终端返回 假(调用方回退 读入);Windows 控制台不支持 VT(旧系统)或输出被重定向时也返回 假
读键(readkey)读一个键:返回码点(0..0x10FFFF);特殊键返回 0x110001–0x110007(依次为上/下/左/右/Home/End/Delete);EOF 返回 -1。仅在 终端原始模式(真) 成功后使用(块读)
显示宽度(displaywidth)字符串的终端显示列宽:组合字符 0、CJK/全角/emoji 2,其余 1;非法 UTF-8 字节按 1 计。参数须为字符串,否则运行时错误
字符(chr)字符(码点) → 单字符字符串;码点越界(含代理区)→ 运行时错误
环境变量(getenv)环境变量(名[, 缺省]);未设置返回 缺省(省略时 假)
正则匹配(regexmatch)正则匹配(文本, 模式[, 选项]) → 真/假(是否含匹配,即「查找得到」)
正则全匹配(regexfullmatch)正则全匹配(文本, 模式[, 选项]) → 真/假(整串须恰好匹配)
正则查找(regexfind)首个匹配子串;无匹配 → 假
正则全部(regexfindall)所有(不重叠)匹配子串 → 数组;无匹配 → 空数组
正则分组(regexgroups)首个匹配的分组 → 数组(下标 0 = 整匹配,其后按左括号序);未参与的分组为 假;无匹配 → 假
正则替换(regexreplace)正则替换(文本, 模式, 替换串[, 选项]);全部替换;替换串中 $0–$9 引用分组、$$ 为字面 $
正则分割(regexsplit)按匹配切开 → 数组(不含分组;与 Python 的 split 收组不同)
正则转义(regexescape)转义元字符(与 Python 3.7+ re.escape 同集),用于把字面串安全嵌进模式

别名与中文名指向同一函数,可混用。调用非内置且未定义的函数、参数个数不符 → 编译错(非运行时错)。

实现形态(v2.12):表中 分割/连接/开头是/结尾是/重复/去空白/转大写/转小写/转整数/编码BASE64/解码BASE64/正则转义/编码URL/解码URL/哈希SHA256/哈希MD5/映射/过滤/归约/绝对值 二十条(以及 v2.39 新增的 字符串构造/字符串追加/字符串取值/字符串长度/串长 与 加溢出/减溢出/乘溢出/加检/减检/乘检、索引/计数/去重/求和/反序/扁平/最大元素/最小元素/复制/合并/字典筛选/字典映射)由 zhlang 源码实现(仓库 标准库/{字符串,编码,容器,数学,摘要}.zh,隐式装载,见 §3.3;按需发射——未被引用的模块不进入产物);行为与本规格一致(函数名、参数、错误文案逐字不变,保持原语义)。差别仅在运行时错误的行号指向标准库实现行(见 §7)。另有用户可引入的标准库模块 标准库/集合.zh(反转(原地反转)、最大值(a, b)、最小值(a, b),等值返回 a;裸名 @引入 "集合.zh" 即可,见 §3.3)与 标准库/同步.zh(互斥锁/条件/读写锁/等待组/一次,见 §6.5)、标准库/错误.zh(类型化错误基类/工厂/文本提取,见 §6.6)。

6.1 正则(v2.0)

语法:字面字符、.(任意码点,不含 \n)、^ $(串首/串尾;非多行时 $ 也匹配结尾换行前)、\A \Z(严格串首/串尾)、 字符类 [abc] [^abc] [a-z] [一-龥](] 作首字符为字面)、预定义类 \d \D \w \W \s \S、 词界 \b \B、分组 (...) 与 (?:...)、或 |、 量词 * + ? {n} {n,} {n,m}(均可后缀 ? 表示懒匹配)、转义 \n \t \r \f \v \0 \uXXXX \xNN 与标点字面。

选项(第 3 参,缺省空串;i 忽略大小写(ASCII、全角字母、Latin-1)、s 让 . 匹配换行、m 让 ^ $ 按行): 正则匹配(文本, 模式, "is")。

语义(与 Python re 逐项对齐):最左匹配、备选取先(左优先)、量词贪婪(懒匹配后缀 ?); 空匹配的处理同 Python 扫描器——某位置匹配为空时,同位置再试一次非空匹配,再前进一个码点 (故 正则全部("aaaa", "a*?") 得 9 个结果:"", "a", "", "a", …)。

未支持:反向引用(\1)、命名分组、环视 (?=…)、条件分组、POSIX 字符类 [[:alpha:]]、\p{…} —— 用到即报「不支持/未知转义」错。 Unicode 口径是近似:\w 对非 ASCII 默认算词字符,但 CJK 标点(U+3000–303F)、通用标点(U+2000–2BFF)、全角标点、表情符号除外(与 Python 对 CJK 文本大体一致); \d 认 ASCII 与全角数字;\s 认 ASCII 空白与常见 Unicode 空白(含 U+3000)。

限定与错误:模式是运行时值,模式错误与超限都走运行时错误(带行号): 正则模式错误:…(如「分组未闭合(缺少 ))」「不支持反向引用(\1)」「字符类范围颠倒」); 回溯有步数上限(约 2 亿步)——病态模式(如 (a+)+b 长串)报「回溯步数超限」而不是挂死; 含「或/重复」的分组重复在长输入上有约 100 轮的嵌套上限(报「回溯嵌套过深」), 而确定性体(如 (?:ab)*、(\d\d,)*)与前缀单字符重复(a*、\d+)走迭代实现,不受轮数限制。

6.2 网络与 TLS(v2.1)

取网页 / 网页请求 / 连接到 / 监听 / 接受连接 是内置网络栈;其中 原生后端的 https 走纯 zhlang TLS 1.3(标准库/TLS.zh);冻结的 C 版/自举编译器走自包含 mbedTLS 3.6.2 (代码 vendored 在 third_party/mbedtls/,见其 ZHLANG-VENDOR.md)。

(本地 PEM 文件 + CDP 抓取,吊销命中硬失败)与 OCSP 钉接(只验证服务器在 CertificateEntry 中钉接的响应,不主动请求响应器)。

任一不符即运行时错误(TLS 握手失败(主机名:TLSv1.3):X509 - Certificate verification failed…), 绝不静默放行。

(/etc/ssl/certs/ca-certificates.crt、/etc/pki/tls/certs/ca-bundle.crt、 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem、/etc/ssl/cert.pem 等) → 内嵌的 CA 包(third_party/ca-bundle.crt,三个目标都随产物携带,最小容器/真机开箱可用)。 全部落空 → 报错并提示安装 ca-certificates 或设置 ZHLANG_CA_FILE/SSL_CERT_FILE。 系统包优先于内嵌包,系统更新 CA 后立即生效。

不做 Content-Length 截断检测(需要严格截断防护的场景请自行核对响应长度)。

用到时约 +0.6~0.9MB;不用 HTTP 的程序体积不变。交叉目标从 sysroot/<目标>/lib/libzhtls-<目标>.a 取归档(缺档则 https 回退为「暂不支持」错误,明文 http 不受影响)。

6.4 进程与终端(v2.6)

这六个内置供 CLI/REPL 用语言自身实现而设(自举编译器 编译主.zh 的 --repl 通路)。都可用英文别名调用。

参数按数组原样进 argv(空格、引号、通配符都不会被再解释一遍),退出码 也不会被 shell 覆盖;stdout 与 stderr 合流捕获(顺序即真实写出顺序), 返回 {"输出": 字符串, "退出码": 整数}(被信号结束记 1)。 程序无法启动(不存在/无执行权限)→ 可捕获的运行时错误 「无法执行「…」:<原因>(程序不存在或无法启动)」。 参数数组只能放字符串,否则运行时错误。 平台注:Windows 的 argv 经 ANSI 码页,非 ANSI 字符到不了子进程(与 shell 无关的既有约束)。

管道/重定向里为假,REPL 输出因此可稳定对拍。

忽略中断(真):子进程收到 Ctrl-C 不会把 REPL 一起带走。

进而找 <程序目录>/sysroot/<三元组> 里的交叉归档(自举编译器的关键依赖)。

REPL 靠这个区分 Ctrl-D 与回车续行。

输出 不会 —— REPL 的提示符与「只显示新增输出」的前缀裁剪要的正是前者。

6.5 同步原语(标准库/同步.zh)

标准库/同步.zh 是可引入的标准库模块(不是内置、不隐式注入),纯 zhlang 用通道实现, 为共享内存并发提供 Go sync 风格的锁。裸名引入(见 §3.3):

@引入 "同步.zh" 只 互斥锁, 读写锁, 等待组, 一次
@引入 "同步.zh" 为 同步

尝试加锁() → 真/假、加锁执行(f)(作用域锁:f 正常返回或抛错都会解锁,返回 f 的结果)。

与 Go sync.Cond 同规:等待/通知/通知全部 必须在关联的互斥锁持有期间调用; 等待者进入 FIFO 队列后释放锁,被唤醒后重新加锁。

读锁执行(f)/写锁执行(f)。读者共享、写者独占,写者优先(有写者在等时新读者等待, 避免写者饿死);非重入。读/写解锁在计数不符时抛错。

添加 须在 等待 之前完成(Go sync.WaitGroup 同规),归零后可复用;负计数抛错。

f 的返回值不向外传。f 抛错也算「已执行」——错误只抛给执行者,等待者不重跑;递归调用自身会死锁 (与 Go sync.Once 相同)。

限制与约定:

6.6 类型化错误(标准库/错误.zh)

标准库/错误.zh 是可引入模块(不是内置),提供共享的错误基类、工厂与判别工具, 配合 §3.4 的 抛出对象 使用。

它作为原型链的基类:继承(错误基类, {...}) 或工厂函数都能创建带该原型的错误对象。

返回带 错误基类 原型与 消息/代码/原因 字段的字典;新建错误类型 额外写 类型 标签。

是错误(e)(字典且沿原型链是 错误基类)、错误类型(e)(是错误时返回 类型 字段,缺省空串)。

@引入 "错误.zh" 只 新建错误类型, 是错误, 错误类型
尝试
    抛出对象(新建错误类型("参数错误", "年龄必须为正", 400))
捕捉 e
    如果 是错误(e)
        打印(错误类型(e), e.消息, e.代码)

捕捉变量得到对象(字典),可 是(e, 错误基类)、读字段;普通 抛出 与运行时错误仍是字符串。 未捕获的 抛出对象 优先打印对象的 消息 字段(含原型链),没有该字段时按显示规则打印整个值。

限制:语言目前只支持同一模块内的 类 子类(父类) 继承;跨模块类继承不支持。 因此本模块用原型链 + 类型 字段做错误分类。若需要真正的类继承,可在同一文件内自行 声明错误基类与子类(类语法、是、初始化继承都可用),再用 抛出对象 保留实例。

原生实验后端支持 抛出对象 与字典/原型错误的捕捉;未捕获对象只打印「未捕获的异常」, 不打印消息字段。

6.7 可引入命名空间模块(v2.23 增补)

以下模块都不是内置、不隐式注入,源码在 标准库/,用裸名引入(见 §3.3)后限定调用:

模块主要 API
文件读文件 写文件 追加文件 写字节 读字节 复制文件 写行 追加行 存在 大小 删除 建目录 确保目录 列目录 递归列目录 真实路径 是目录 目录存在 是文件 读行 临时目录 临时文件 临时文件夹
数据库打开 内存;库对象:建表 表 有表 表名 删表 保存 事务 是否已改;表对象:插入 按 找 查询 第一 全部 计数 更新 更新条件 删除 删除条件 建索引 索引列表(见 §6.12)
JSON编码 解码 美化 美化缩进 美化文本 读取 读取或 尝试解码 写入 写入美化 是JSON
正则匹配 全匹配 查找 全部 分组 替换 分割 转义;带选项的 API 以 …选项 结尾(如 匹配选项)
进程执行 执行捕获 运行 程序路径 参数 环境变量 环境变量或 成功 输出 退出码 检查 运行输出 命令输出 运行成功
终端是终端 读入 输出 提示 读键 显示宽度 字符 忽略中断 原始模式 输入结束
时间现在 格式化 默认格式 格式化当前 日期 时间 日期时间 毫秒 现在毫秒 经过 计时 睡眠 分解 是否闰年 月天数 增加秒 增加天 当天零点 星期名 格式化ISO 解析日期
随机随机整数 随机范围 随机浮点 随机布尔 随机选择 洗牌 抽样 随机字节 随机字符串 随机密码 随机正态 按权重选择
路径工具是绝对 拼接 目录名 父目录 文件名 扩展名 去扩展名 去尾分隔 规范化 拆解 家目录
网页转义HTML 文本 原样 片段 元素 空元素 逐块输出 动态 响应 响应码 流式响应 流式响应码 响应体 按需 动态加载 动态加载文本 加载脚本 加载脚本文本;一次性/兼容:渲染 渲染美化 区块 段落 标题 链接 图片 列表 有序列表 列表项 表格 按钮 输入 换行 水平线 表单 样式块 样式链接 脚本 脚本链接 脚本模块 页面 页面带语言 页面带样式 页面带资源 页面完整 写出 写出美化
样式像素 百分比 em rem 秒 毫秒 类选择器 标识选择器 标签选择器 后代 子代 同时 规则 媒体 关键帧 导入 原样 根变量 样式表 写出
JS原样 标识 字面 字符串 数 布尔 数组 对象 二元 三元 一元 调用 成员 索引 箭头 模板 常量声明 变量声明 赋值 表达式语句 返回语句 返回空 如果语句 如果单 当语句 逐语句 循环语句 函数声明 尝试语句 尝试捕捉 尝试最终 跳出语句 继续语句 块 注释 导入 导入默认 导出 导出默认 程序 渲染 渲染紧凑 写出 写出紧凑
服务新应用 配置 取值 设值 使用 分组 异常处理 未找到 日志 彩色 路由 装饰 任何 GET POST PUT DELETE PATCH OPTIONS HEAD 路由列表 打印路由 静态 测试请求 测试请求体 测试请求头 测试请求头体 响应 文本 HTML 动态HTML 动态HTML码 流式HTML 流式HTML码 响应体 JSON 状态 加头 追加头 重定向 重定向到 空 错误 文件响应 取参数 查询 头 Cookie 设Cookie 删Cookie 表单 表单文件 表单值 上传文件 取JSON 事件流 发事件 发事件名 发事件编号 发注释 运行 运行套接字 运行开发 停止 已停止 活动连接 允许跨域
集合(扩展)原有 反转 最大值 最小值;新增 索引 计数 去重 求和 平均 并集 交集 差集 对称差集 分组 计数表 分块 拉链 枚举 展平 任一满足 全部满足
错误(扩展)原有工厂/判定;新增 错误码 错误原因 新建错误带原因 包装错误

网页 是 HTML 构建/渲染库:节点是普通值(文本/原样/片段/元素),文本与属性值 默认转义,只有 原样 输出未转义 HTML。核心输出方式是动态的:逐块输出(节点, 写块) 按文档顺序把片段逐块交给写块,不构造完整字符串;动态(节点) 返回渲染器函数; 响应(节点)/响应码(节点, 状态) 直接得到可交给 服务 的动态响应(HTTP/1.1 分块传输)。 渲染/渲染美化 与 写出/写出美化 保留为一次性收集/静态落盘的兼容接口。

@引入 网页
@引入 服务
常量 页 = 网页.页面("你好", [
    网页.区块({"class": "卡片"}, [
        网页.标题(1, "你好,zhlang"),
        网页.段落("安全转义: <b> & \"引号\""),
        网页.链接("https://example.com/", "去看看"),
    ]),
])
网页.写出("index.html", 页)              // 直接输出 HTML 到文件
服务.GET(应用, "/", (请求) => 网页.响应(页))  // 动态输出

// 动态加载:先出占位节点,浏览器端加载脚本拉取片段并替换
常量 板 = 网页.片段([
    网页.标题(1, "慢慢来"),
    网页.动态加载("/片段/统计"),
    网页.加载脚本(),
])

JS 用 zhlang 构造 JavaScript 源码:节点是普通值;字符串借 JSON 规则转义,标识符 做形状与 JS 保留字校验;二元/一元表达式自动加括号避免优先级陷阱;渲染 产出 2 空格 缩进的多行代码,渲染紧凑 产出一行内联代码,写出/写出紧凑 直接落盘;原样 是 唯一不转义的入口(调用方保证可信)。本库只生成 JS 文本,不把 zhlang 编译成 JS, 可配合 网页.脚本(JS.渲染(脚本)) 内联到页面。

@引入 JS

常量 脚本 = JS.程序([
    JS.常量声明("x", 1),
    JS.函数声明("add", ["a", "b"], [
        JS.返回语句(JS.二元("+", JS.标识("a"), JS.标识("b")))
    ]),
    JS.表达式语句(JS.调用(JS.成员(JS.标识("console"), "log"), [
        JS.调用(JS.标识("add"), [JS.标识("x"), 2])
    ]))
])
JS.写出("app.js", 脚本)

产出:

const x = 1;
function add(a, b) {
  return (a + b);
}
console.log(add(x, 2));

服务 是 Flask/Gin 风格的 HTTP 服务框架:路由支持静态段、:名字(单段捕获)与 *名字(剩余路径);使用 组合中间件((请求, 下一个) => 响应,不调用下一个即短路), 分组 带前缀注册并拥有独立中间件;请求侧提供查询/头/Cookie/表单/JSON/路由参数, 响应侧提供文本/HTML/JSON/状态码/加头/重定向/文件/空;自动处理 404、405(带 Allow)、 OPTIONS 与 HEAD;静态 映射目录(拒绝 .. 穿越),支持二进制、ETag/If-None-Match 与单区间 Range(206/416)。每连接一个协程,支持 HTTP/1.1 保持连接、Content-Length、 分块请求体与 100-continue、请求头/体大小上限;配置 读超时(默认 30s)给每个连接设 收发超时,最大连接(默认 1024,0=不限)超限回 503 并立即关闭(背压),停止(应用) 置停止标记、关闭监听并在 停机超时(默认 10s)内排空在途连接。上传与推送: multipart/form-data(表单文件/表单值/上传文件)、SSE(事件流/发事件/ 发事件名/发事件编号/发注释);处理函数置 请求["接管"]=真 后框架不再写 HTTP 响应(WebSocket 等自写协议)。访问日志写 stdout:服务:GET / [200] 0ms 2B 对端 UA=…,状态码按类别着色(2xx 绿/3xx 青/4xx 黄/5xx 红),耗时 ≥300ms 黄、≥1s 红; 配置 彩色 取 真/假/"自动"(默认;仅交互终端),NO_COLOR=1 关闭、ZHLANG_COLOR=1 强制。装饰器 @服务.装饰(应用, 方法, 路径) 供原生后端使用,C 引导器改走三参调用。 响应体可以是字符串,也可以是 (写块) => … 的动态渲染器:动态HTML/动态HTML码 默认在发送前收集动态体并给 Content-Length(对大页面更稳);流式HTML/流式HTML码 显式选择 Transfer-Encoding: chunked 边渲染边发送(HTTP/1.0/HEAD 下仍自动收集)。 响应体(响应) 用于测试时收集动态体。测试请求/测试请求体 不启动服务器, 直接跑一次分派,便于单元测试。头编码:头值中的非 ASCII/控制字节按 UTF-8 逐字节 %XX 编码(响应与 网页请求 请求头同口径),线上头保持 ASCII,读取端 可用 解码URL 还原;请求头名必须是 ASCII 令牌(§6.2)。

@引入 服务

常量 应用 = 服务.新应用()
服务.GET(应用, "/", (请求) => 服务.文本("你好,zhlang"))
服务.GET(应用, "/api/用户/:id", (请求) =>
    服务.JSON({"id": 服务.取参数(请求, "id")}))
服务.运行(应用, 8080)

限制:无 TLS 服务端与 HTTP/2;WebSocket 需用连接接管(请求["接管"])自写帧。 本模块使用原生后端的套接字超时/地址原语,须用自举原生编译器(build/原生)编译; 已冻结的 C 引导器不能编译本模块。动态响应体仍按字符串采集,静态文件可发二进制。

样式 用 zhlang 写 CSS:声明键可写中文(如 颜色/字号/内边距,映射到 CSS)或标准 CSS 名(--自定义变量 原样通过);数组值按空格连接(内边距: [像素(12), 像素(16)]), 假 值跳过(条件样式)。规则/媒体/关键帧/根变量组合成样式表;与网页库配合用 网页.页面带样式(标题, 体, css) 内联,或 网页.样式链接("样式.css") 外链。

@引入 样式
@引入 网页

常量 css = 样式.样式表([
    样式.规则(样式.类选择器("卡片"), {
        "背景色": "#fff",
        "圆角": 样式.像素(8),
        "内边距": [样式.像素(12), 样式.像素(16)],
    }),
    样式.媒体("(max-width: 600px)", [
        样式.规则(样式.类选择器("卡片"), {"内边距": 样式.像素(8)}),
    ]),
])
网页.写出("样式.css", css)
网页.写出("index.html", 网页.页面带样式("标题", [网页.段落("你好")], css))

原生内容可以自由混入(内容由调用方保证可信):HTML 用 网页.原样(html),CSS 用 网页.样式块(css) / 网页.样式链接(url) 或 样式.原样(css) 混进样式表,JS 用 网页.脚本(js) / 网页.脚本链接(url) / 网页.脚本模块(url);网页.页面带资源(标题, 体, css, js) 一次带上原样 CSS 与 JS。

约定:

或不同的中文内置名调用,避免递归。

进程.环境变量或)。

(含 同步,纯 zhlang 通道实现),包括 文件/JSON/正则(含 i/s/m 选项)/ 进程/终端/时间/随机/路径工具/集合/错误/网页/网络 (TCP/HTTP/HTTPS;Linux DNS 内置/HTTPS 纯 zhlang TLS,PE 用 gethostbyname + WinHTTP); 未覆盖的内置会明确报错。原生 Windows PE 目标自 v2.23 增补起支持文件 IO (宽字符路径;目录存在性用 GetFileAttributesW 判定)与目录列举 (FindFirstFileW/FindNextFileW/FindClose);进程(执行/执行捕获)也已覆盖 (CreatePipe + CreateProcessW,命令行 MSVCRT 引用规则,输出按 ACP 解码)。


6.8 原生编译器:自举与 Go 式多端(v2.23 增补)

纯 zhlang 原生编译器(自举/原生.zh)自本版起可以编出自身:

→ stage2(stage1 再编译自身);cmp stage1 stage2 逐字节相同(make selfhost)。

--target 可用三元组或 Go 风格 系统/架构(linux/amd64、windows/amd64、 linux/arm64);选择优先级:--target > ZHC_TARGET > ZHC_GOOS+ZHC_GOARCH > 宿主。--targets 列出目标、产物后缀与能力表。 目标表的唯一来源是 自举/驱动/目标.zh,驱动不再散落按目标名判断。

已实现(CreateFileW/ReadFile/WriteFile/SetFilePointer/DeleteFileW/ CreateDirectoryW/GetFileAttributesW);路径、命令行、环境变量全部走 UTF-16 W 版 API,通过 MultiByteToWideChar/WideCharToMultiByte 与 UTF-8 互转, 中文源码名/参数/文件名在 Windows 上可用;参数()/程序路径() 从 GetCommandLineW 解析;PE 堆改为 64MB/256MB 的 bump 分配器(旧实现每次分配 调用一次 VirtualAlloc,无法支撑自举)。

在 wine/Windows 上能再编译自身,也能交叉输出 Linux ELF(同一编译器多目标)。

闭包/结构体/类/浮点/模块/文件 IO/JSON/正则/进程/终端),tests/原生 全部语料 在 qemu 下与 x86_64 产物逐字一致。make selfhost-a64 由 x86_64 宿主产出 arm64 编译器,该编译器在 qemu 下继续产出 arm64 程序; ZHC_SELFHOST_A64_FULL=1 时再让它编译自身并做逐字节不动点比对 (v2.23 已在 qemu 下验证 stage1 与 stage2 逐字节相同)。

(连接到/套接字发送/套接字接收/关闭套接字/监听/接受连接 与 取网页/网页请求,HTTP/1.1、chunked、DNS;HTTPS 纯 zhlang TLS(Linux,aarch64 同样)/WinHTTP(PE)) 与 Go 式结构化 接口、参数类型标注(见 §3.2);套接字是文件描述符整数, PE 网络已接入(WinSock + WinHTTP),PE 目录列举与进程 IO(CreateProcess)亦已接入。网络/TLS 的完整版实现仍更全。

原生网络未移植(网络在编译期报错)。

6.9 原生后端:无符号 64 位 / 64×64→128 intrinsic

原生后端(x86_64 与 aarch64)提供一组 intrinsic。值仍是整数(tag=1),64 位载荷按 二补码解释;负数视为 2^64+v,没有独立的“无符号”类型,类型() 仍返回“整数”。

intrinsic语义
原乘128(a, b)无符号 64×64→128,返回 [高64, 低64] 两元素数组
原乘高64(a, b)无符号乘积的高 64 位(umulh / mul 的 rdx)
原乘低64(a, b)乘积的低 64 位
原无符号右移(a, n)逻辑右移(lsr / shr;n 取低 6 位)
原无符号比(a, b)无符号比较:小于 -1、等于 0、大于 1
原无符号除(a, b)无符号商;b=0 抛运行时错误“除以零”
原无符号余(a, b)无符号余数;b=0 抛运行时错误“除以零”

英文别名:umul128 / umulhi / umullo / ushr64 / ucmp64 / udiv64 / umod64。 参数必须是整数(否则运行时类型错误)。普通 + - * & | ^ << 的 64 位环绕语义不变; 普通 >> 仍是算术右移,需要逻辑右移时用 原无符号右移。这些 intrinsic 为原生后端专用。

常量 组 = 原乘128(-1, -1)          // (2^64-1)^2
打印(组[0] == -2)                  // 高 64 位 0xFFFFFFFFFFFFFFFE
打印(组[1] == 1)                   // 低 64 位 1
打印(原无符号右移(-1, 1))          // 9223372036854775807

6.10 调试断点(原生后端,v2.27 增补)

@调试断点、@breakpoint。等价函数调用:调试断点([等级, 内容])、 断点(...)、breakpoint(...)(可作为表达式使用)。省略等级时只写内容; @断点调试() 无内容。

| 名称 | 数值 | 用途 | | --- | --- | --- | | 错误 / error | 0 | 必须立刻看 | | 警告 / warning / warn | 1 | 可疑状态 | | 信息 / info | 2 | 一般流程 | | 调试 / debug | 3 | 细节/高频 |

环境变量 ZHLANG_DEBUG_LEVEL 写数值或名称作为上限,等级大于上限的断点 不输出也不暂停;其它非标准值不参与过滤,始终显示。

不求值。原生 源.zh -d 是调试运行——编译到临时文件后直接执行(用后删除, 不留下二进制;交互终端下 stdout/stderr 直连终端,断点提示实时可见),退出码即 被调试程序退出码;原生 源.zh -o 产物 -d 则产出带断点的调试二进制。 非终端(管道/重定向)下只捕获被调试程序的 stdout 并回放到标准输出, stderr(断点/诊断)继承父进程的 stderr,不再与程序输出合流。 交叉目标不能直接运行,必须写 -o。

顶层语句的函数名为 (顶层)。文件是传给编译器的模块路径。交互终端下等级标签按 等级着色(错误红/警告黄/信息青/调试灰);stderr 不是终端时输出纯文本。

ZHLANG_DEBUG=0 完全关闭;ZHLANG_DEBUG=1 强制进入命令循环(管道调试)。

c/continue/继续 继续运行;q/quit/exit/退出 终止程序; b/bt/栈 再次显示当前位置(调用栈回溯尚未支持);?/h/help/帮助。

函数 处理(n)
    @断点调试(1, "n=" + 转字符串(n))
    返回 n + 1

打印(处理(1))
$ ./原生 例.zh -d
断点 例.zh:2 处理 [1] n=1
调试 例.zh:2> c
2

6.11 网页动态输出与动态加载(v2.28 增补)

网页 的主输出路径不再返回整段 HTML 字符串,而是逐块输出:

API说明
逐块输出(节点, 写块)按文档顺序调用 写块(块),返回块数;不拼接完整字符串
动态(节点)返回 (写块) => 逐块输出(节点, 写块) 渲染器函数
响应(节点) / 响应码(节点, 状态)返回 {状态, 头, 体:渲染器},可直接交给 服务;发送前收集并给 Content-Length
流式响应(节点) / 流式响应码(节点, 状态)同上,但显式要求 HTTP/1.1 分块传输,边渲染边发送
响应体(响应)逐块收集动态响应体并返回字符串(测试/调试)
渲染(节点)一次性收集的兼容接口,等价于把块连接起来
写出(路径, 节点)动态输出到文件(每 64 KiB 追加一批),仍不构造完整字符串

动态加载有两层:

节点;节点数组/子节点里也可以直接放函数值 () => 节点。

占位 div;页面加入 加载脚本()(或单独引用 加载脚本文本())后,脚本在 DOMContentLoaded 时 fetch 地址并用返回的 HTML 片段替换内容。片段端点可直接返回 网页.响应(片段)。加载成功加类 已加载,失败加 加载失败 并显示错误文本。

直接输出 HTML 仍然完整保留:渲染/渲染美化 出字符串,写出/写出美化 落盘; 静态站点生成与动态服务端渲染共用同一棵节点树。

6.12 数据库(标准库/数据库.zh)

标准库/数据库.zh 是可引入模块(不是内置),纯 zhlang 实现的嵌入式表/文档数据库: 记录是字典,表是记录数组,JSON 文件持久化,可选主键、字段索引与事务回滚。

@引入 数据库
常量 库 = 数据库.打开("应用.db")
常量 用户 = 库.建表("用户", "编号")
用户.插入({"编号": 1, "名字": "张三", "年龄": 30})
用户.建索引("名字")
打印(用户.按(1)["名字"])               // 主键查
打印(用户.找("名字", "张三"))           // 字段查(参数化)
打印(用户.查询(r => r["年龄"] > 26))    // 条件函数查
用户.更新(1, {"年龄": 31})
用户.删除(1)
库.保存()
对象API
模块打开(路径)、内存()、远程服务(库, 端口, 令牌)、远程连接(地址, 端口, 令牌)、网关连接(地址, 端口, 令牌)
库建表(名, 主键)(主键传空串表示无主键)、表(名)、有表(名)、表名()、删表(名)、保存()、事务(f)、是否已改()
表插入(记录)、按(键值)、找(字段, 值)、查询(条件)、第一(条件)、全部()、计数()、更新(键值, 变更)、更新条件(条件, 变更)、删除(键值)、删除条件(条件)、建索引(字段)、索引列表()
网关库执行(语句, 参数)、查询(语句, 参数)、调用(命令, 参数)、关闭()

语义与约束:

插入/更新时深拷贝并校验,外部改不动库内状态。

避免绕过主键与索引约束。

保证跨 整数/浮点 的 == 语义正确。

文件内容是 {"格式":1,"表":{…}} 的 JSON。JSON 不区分整数/浮点:整数值浮点(如 4.0) 重新读回会变成整数 4。

事务进行中调用 保存() 会抛错(不自动落盘)。

返回远程库代理(方法:建表 删表 有表 表名 保存 关闭;表代理: 插入 按 找 全部 计数 更新 删除)。协议是换行分隔 JSON、值参数化, 不接收 SQL 文本;条件函数与事务不能跨进程。内置 监听 绑 0.0.0.0,必须使用 非空令牌;协议为明文 TCP(无服务端 TLS),公网须走反向代理/隧道。服务端串行化 请求;服务器程序应使用 ≥2 个 worker(默认按 CPU 数)。

原生驱动。网关连接 把「SQL 语句 + 参数数组」发给网关进程,由网关用目标数据库 的驱动绑定执行;参考网关 tools/数据库网关.py 用 Python 标准库 sqlite3, PostgreSQL/MySQL 按同一协议扩展(关闭多语句、引号外 ? 安全转换)。 值永远走参数绑定,禁止把用户数据拼进 SQL;zhlang 侧不解析 SQL。

// 外部数据库:网关连接(参考网关为 Python sqlite3;PG/MySQL 同协议扩展)
@引入 数据库
常量 网 = 数据库.网关连接("127.0.0.1", 18932, "令牌")
网.执行("CREATE TABLE 用户 (编号 INTEGER PRIMARY KEY, 名字 TEXT)", [])
网.执行("INSERT INTO 用户 (编号, 名字) VALUES (?, ?)", [1, "张三"])
常量 行 = 网.查询("SELECT 编号, 名字 FROM 用户 WHERE 编号 = ?", [1])
打印(行[0]["名字"])            // 张三
常量 载荷 = "Robert'); DROP TABLE 用户;--"
网.执行("INSERT INTO 用户 (编号, 名字) VALUES (?, ?)", [2, 载荷])  // 只是数据
网.关闭()

注入安全:本地/远程表引擎没有 SQL 解析器,传入的字符串永远是数据; 网关客户端只发「语句 + 参数数组」,值由网关侧绑定。因此不存在可被绕过的转义层。 回归用例把 Robert'); DROP TABLE 用户;--、" OR "1"="1 等载荷作为参数存取, 并验证表结构不变。SQL 文本必须由开发者编写占位符,用户数据只进参数数组。

6.13 原生后端内部原语(不保证兼容)

下列名称由原生后端运行时/编译器内部使用,普通用户代码可调用但不承诺兼容性:误用可读写任意进程内存、破坏运行时状态或直接崩溃进程。

原分配 原读8 原读64 原写8 原写64 原拷贝 原系统 原标签 原载荷 原错误 原线程取 原原子交换64 原原子写64 原原子加64(以及 原生_ 前缀的运行时内部函数)。

标准库与规格示例不应使用这些名称;后续版本可能改名、限制参数或禁止用户代码调用。需要文件/网络/进程/内存等能力时使用 §6 的公开内置。

6.14 标准库扩展 API(v2.39)

本节是 §6.7 模块表与隐式标准库新增函数的完整签名/返回值/错误口径。隐式模块 (字符串/编码/容器/数学/摘要)的函数全局可用;可引入模块用 模块.函数(…)。

数学(隐式,标准库/数学.zh)

签名返回值 / 错误
夹取(v, 下, 上)把数值 v 夹到 [下, 上];非数值、下 > 上 → 错误
符号(v)-1 / 0 / 1;NaN 按 0;非数值 → 错误
最大公约数(a, b)非负 GCD(欧几里得,负数取绝对值);非整数 → 错误
最小公倍数(a, b)LCM;任一为 0 → 0;结果按 64 位环绕
阶乘(n)n!;n < 0 → 错误;溢出 → 错误(逐步 乘检)
幂模(底, 指数, 模)底^指数 mod 模;模 ∈ 1..3037000499,指数 ≥ 0,否则错误
是否质数(n)布尔;n < 2 为假;非整数 → 错误
质数列表(n)≤ n 的质数数组(n < 2 空);n > 10000000 → 错误
转进制(n, 基)2..36 进制文本(小写,负数带 -);2 的幂用移位精确到 64 位,其余基限制 `n≤ 2^53-1`
解析进制(s, 基)文本 → 整数;允许首尾空白与 +/-;非法数字/越界溢出 → 错误
均值(数组)算术平均(浮点);非空数值数组,否则错误
中位数(数组)排序后中间值;偶数取两中值平均
方差(数组) / 标准差(数组)总体方差/标准差(除以 n);空/非数值 → 错误
舍入到(v, 位)四舍五入到小数位(负数位 = 十位/百位);半数远离零

字符串(隐式,标准库/字符串.zh)

签名返回值 / 错误
反转文本(s)按码点反转的新串
左填充(s, 宽, 填) / 右填充(s, 宽, 填)补到 宽 个码点;宽 ≤ 长度(s) 原样;填 通常为单字符
去前缀(s, 前缀) / 去后缀(s, 后缀)匹配则去掉,否则原样返回
分行(s)按 \n 切分并去掉行尾 \r;末尾换行不产生空行;空串 → []
字节长度(s)UTF-8 字节数(与按码点的 长度 区分)
出现次数(s, 子串)不重叠出现次数;空子串 → 0
是空白(s)去 ASCII 空白与全角空格后是否为空

集合(可引入,标准库/集合.zh)

签名返回值 / 错误
对称差集(a, b)只出现在一侧的元素(保序去重)
分组(数组, f){键: 元素数组};键 = 类型(f(x)) + ":" + 转字符串(f(x)),保持首次出现顺序
计数表(数组){键: 次数},键规则同 分组
分块(数组, 大小)每 大小 个元素一组(末组可短);大小须正整数
拉链(a, b)[[a0,b0], …],长度取较短数组
枚举(数组)[[下标, 元素], …],下标从 0 起

时间(可引入,标准库/时间.zh)

签名返回值 / 错误
分解(时间戳){年,月,日,时,分,秒,星期,年内第几天,时区};星期 0=周日;按本地时区
是否闰年(年) / 月天数(年, 月)公历闰年判断 / 该月天数(月 1..12)
增加秒(时间戳, 秒) / 增加天(时间戳, 天)绝对时间加减(按 86400 秒/天,不做 DST 钟点对齐)
当天零点(时间戳)本地当天 00:00:00 的时间戳(近似,无 DST 跳变)
星期名(时间戳)"星期" + 日/一/…/六
格式化ISO(时间戳)YYYY-MM-DDTHH:MM:SS±HHMM;无时区数据时省略偏移
解析日期(文本)→ UTC 整数秒。接受 YYYY-MM-DD、YYYY-MM-DD HH:MM[:SS]、YYYY-MM-DDTHH:MM[:SS];尾部 Z 或 ±HH:MM/±HHMM 换算到 UTC;非法日期/时间 → 错误

随机(可引入,标准库/随机.zh)

签名返回值 / 错误
随机浮点()[0.0, 1.0),52 位尾数(两次 26 位抽取)
随机字节(n)长度 n 的 0..255 数组;n < 0 → 错误
随机字符串(n)长度 n 的 A-Za-z0-9 字符串
随机密码(n)字母/数字/符号混合;n ≥ 3 时三类各至少一
随机正态()标准正态 N(0,1)(Box–Muller)
按权重选择(权重)按非负权重返回下标;权重和须为正

随机种子由运行时自动初始化,混入秒级时间、单调毫秒与栈地址低 10 位,同一秒 启动的多个进程不再产生相同序列(仍为确定性伪随机,不用于密码学)。

文件 / 路径工具 / 编码 / 摘要 / JSON / 进程

模块/签名返回值 / 错误
文件.读字节(路径)整个文件 → 0..255 数组(二进制安全,含 NUL)
文件.复制文件(源, 目标)二进制复制,返回写入字节数
文件.写行(路径, 行们) / 文件.追加行(路径, 行们)每行补 \n;返回写入字节数
文件.临时目录()TMPDIR → TEMP → TMP → Windows 当前目录 / POSIX /tmp(去尾分隔)
文件.临时文件(前缀) / 文件.临时文件夹(前缀)创建并返回路径(秒+随机数命名)
文件.递归列目录(路径)相对路径数组;目录项以 / 结尾;深度上限 32(防符号链接环)
路径工具.去尾分隔(路径)去掉多余尾部 /、\(根路径保留)
路径工具.规范化(路径)折叠重复分隔符/./..;保留绝对根与盘符;分隔符风格跟随输入
路径工具.拆解(路径){目录, 文件, 扩展}
路径工具.家目录()HOME → USERPROFILE;都没有 → ""
编码十六进制(s) / 解码十六进制(s)UTF-8 字节 ↔ 小写十六进制;解码须偶数长度,非法字符 → 错误
哈希HMAC(键, 消息)HMAC-SHA256 小写十六进制;键 > 64 字节先取 SHA256(RFC 2104)
哈希文件(路径) / 哈希文件MD5(路径)文件内容摘要(二进制安全)
JSON.尝试解码(文本, 缺省)失败返回缺省,不抛错
JSON.读取或(路径, 缺省)文件不存在/语法错 → 缺省
JSON.写入美化(路径, 值)两空格缩进写入,返回字节数
进程.检查(结果)退出码非 0 抛 命令失败(退出码 N):输出,否则原样返回结果
进程.运行输出(程序, 参数) / 进程.命令输出(命令)返回 stdout,失败抛错
进程.运行成功(程序, 参数)布尔,不抛错

HTTP 头编码:服务端响应头与 网页请求 请求头中的非 ASCII/控制字节按 UTF-8 逐字节 %XX 编码,ASCII(含已有 %)原样;线上头保持 ASCII。请求头名必须是 ASCII 令牌。读取端用 解码URL 还原。

@引入 时间
@引入 摘要
@引入 集合

常量 d = 时间.分解(1700000000)
打印(d["年"], d["月"], d["日"], d["星期"])       // 2023 11 15 3
打印(时间.解析日期("2023-11-15T06:13:20+08:00")) // 1700000000
打印(摘要.哈希HMAC("key", "x"))                  // 4fc3b7ea…
打印(集合.分块([1,2,3,4,5], 2))                  // [[1, 2], [3, 4], [5]]

6.15 包管理与测试(v2.40)

原生 get <包名>[@版本] [--仓库 网址] [--目录 项目根](自举发行版编译器;stage-0 zhc 不带): 从静态仓库下载并安装包;默认仓库 ZHLANG_仓库 环境变量或 https://pingsec.top/repo。仓库协议:

<仓库>/索引.json
<仓库>/包/<名>/<版本>/<文件>.tar.b64

索引.json(UTF-8):{"格式":1,"包":{"名":{"最新":"1.0.0","说明":"…", "版本":{"1.0.0":{"文件":"包/名/1.0.0/名-1.0.0.tar.b64","sha256":"…","编码":"base64", "依赖":{"其他包":"1.0.0"},"需要编译器":"2.40"}}}}}。sha256 是 base64 文本的 摘要(stage-0 C 运行时的哈希/字节数组按 C 串处理,二进制串会在 NUL 截断;base64 传输让 C 引导编译器与原生编译器行为一致)。依赖 递归安装;""/最新/^/~ 按仓库最新版本处理(v2.40 不做 semver 区间求值)。安装布局: <项目根>/包/<名>/…(ustar 内相对路径原样展开;拒绝绝对路径、..、反斜杠、 盘符与符号链接以外的条目),并合并写 zh包.json(依赖)与 zh包锁.json (精确版本 + sha256)。

测试框架:标准库/测试.zh(可引入)提供 测试套件(名)、测试(套, 名, 体)、 运行(套, 用例表)、断言/断言真/断言假/断言相等/断言不等/断言近似、 报告(套)(返回失败数)。体 可为无参函数或函数数组;断言失败抛可捕获错误, 由 测试 收集并继续。注意:块体箭头函数不能直接写在调用括号里(括号内换行被 忽略),多断言用 [() => 断言…, () => 断言…] 或先赋给变量。

环绕运算:数学.环绕加/环绕减/环绕乘 提供显式模 2^64 运算,不触发「整数溢出」。

@引入 "测试.zh" 只 测试套件, 测试, 断言相等, 报告
变量 t = 测试套件("示例")
测试(t, "相等", [() => 断言相等(1 + 1, 2, "1+1")])
如果 报告(t) != 0
    抛出("测试失败")

7. 错误信息约定

编译错(退出码 1):文件:行:列: 错误:消息 + 源码行 + ^ 指示(列按码点,指示按显示宽度对齐)。示例:

例子.zh:3:9: 错误:未定义的变量「名字」
    打印("你好," + 名字)
            ^

原生后端对语义/模块错误也按同一格式渲染(列优先取消息中第一个「…」名字在该行的位置,否则取首个非空白;跨模块错误指向定义该函数的模块文件,并回显该文件对应的源码行)。

运行时错误(生成的程序,stderr,退出码 1):运行时错误(第 N 行):消息。示例:

运行时错误(第 12 行):「+」不支持 布尔 与 字符串

未被 捕捉 接住的抛出与此格式完全一致(见 §3.4);普通抛出/运行时错误的捕捉消息是纯文本(无该前缀);抛出对象 的捕捉值是原对象,未捕获时优先打印其「消息」字段。

调用栈(v2.23 增补):未捕获错误在错误行之后打印由内向外的 zhlang 调用栈,每帧是 函数名(定义于第 N 行)(生成函数在入口压帧、返回前弹帧;行号是函数声明行)。每协程独立, 最多保留最内层 64 帧(更深时注明省略层数);被 捕捉 的错误不打印,捕捉后栈深度回到 尝试 入口,后续错误不会带出已展开的旧帧。示例:

运行时错误(第 3 行):下标越界:5(长度 1)
调用栈(由内向外):
  内(定义于第 1 行)
  外(定义于第 4 行)

行号与实现位置(v2.11):实现于标准库(§6 zhlang 实现)的内置,错误消息正文保持不变,行号指向标准库实现行(标准库/*.zh 内的行),不是调用处。其余情况行号一律指向调用处。

gcc 错误:透传 gcc 退出码与 stderr,并保留生成的 .c 文件供排查。


8. 实现形态


8.1 交互式会话(编译 --repl)

语言本身无解释器(编译单元 = 整个程序),会话因此实现为整段重跑:每段可编译输入都触发一次 「累计源码 → 临时程序 → 运行」,只显示比上一轮新增的 stdout(前缀裁剪);编译错不计入会话, 运行失败(运行时错误/崩溃/非零退出)同样不计入,以保证后续轮次的输出基线能推进。 副作用(写文件、网络请求、打印)会随每轮重放——这是该设计的固有代价,已在会话内 :帮助 说明。 会话命令 :帮助、:会话、:清(:重置/:reset/:clear 同义)、:退出 在续行中同样生效; 续行中 :清 会放弃未完成输入。全角冒号 : 与英文别名同义。会话临时目录按微秒时间戳 + 随机数命名, 同一秒启动的多个会话互不共用 会话.zh/会话.bin。

9. 完整示例

9.1 你好世界

打印("你好,zhlang!")

9.2 阶乘(递归)

函数 阶乘(n)
    如果 n <= 1
        返回 1
    返回 n * 阶乘(n - 1)

常量 汇报 = `5 的阶乘是 ${阶乘(5)}`
打印(汇报)

9.3 九九乘法表

循环 i 从 1 到 9
    循环 j 从 1 到 i
        打印(i, "*", j, "=", i * j)

9.4 模板字符串与字符串库

常量 名字 = "zhlang"
常量 版本 = 1.0
打印(`语言:${名字},版本:${版本},明年:${版本 + 1}`)
打印(长度("你好世界"))          // 4
打印(子串("你好世界", 1, 2))     // 好世
打印(查找("banana", "na"))       // 2
打印(替换("a-b-c", "-", "+"))    // a+b+c

9.5 猜数字(交互)

常量 答案 = 42
当 真
    打印("猜一个数:")
    常量 猜 = 转整数(读入())
    如果 猜 == 答案
        打印("猜对了!")
        跳出
    如果 猜 < 答案
        打印("小了")
    否则
        打印("大了")

9.6 生产者-消费者(并发)

函数 生产者(ch)
    循环 i 从 1 到 5
        发送(ch, i)
    关闭(ch)

函数 消费者(ch)
    当 真
        常量 v = 接收(ch)
        如果 v == 假
            跳出
        打印("收到", v)

常量 ch = 通道()
启动 生产者(ch)
消费者(ch)
打印("完成")

(多生产者共用一个通道时不要各自 关闭——向已关闭通道发送是运行时错误;确需结束信号时由唯一的关闭方发哨兵值。)

9.7 一万协程压测(并发 + GC)

函数 工人(id, ch)
    发送(ch, id * 2)

常量 ch = 通道(10000)
常量 开始 = 当前时间()
循环 i 从 1 到 10000
    启动 工人(i, ch)
变量 收 = 0
循环 i 从 1 到 10000
    常量 v = 接收(ch)
    收 = 收 + v
打印("合计", 收, "耗时", 转字符串(当前时间() - 开始) + " 秒")
打印("堆用量", 堆用量())

9.8 数组与字典(v1.2)

// 数组:排序与遍历
常量 分数 = [88, 92, 79, 95]
分数[2] = 80                    // 索引赋值
追加(分数, 90)
打印(分数, 分数[-1])            // [88, 92, 80, 95, 90] 90

// 字典:统计字频(保插入序)
常量 文本 = "床前明月光疑是地上霜举头望明月低头思故乡"
常量 频次 = {}
循环 字 在 文本                 // 字符串遍历得码点
    如果 包含(频次, 字)
        频次[字] = 频次[字] + 1
    否则
        频次[字] = 1
打印(频次)                     // {"床": 1, "前": 1, …, "明": 2, …}

// 遍历字典得键;键() 亦然
循环 k 在 频次
    如果 频次[k] > 1
        打印(`「${k}」出现 ${频次[k]} 次`)

9.9 一等函数与箭头函数(v1.6)

函数 双倍(x)
    返回 x * 2

常量 nums = [1, 2, 3, 4, 5]

打印(映射(nums, n => n * n))            // [1, 4, 9, 16, 25]
打印(过滤(nums, n => n % 2 == 1))        // [1, 3, 5]
打印(归约(nums, 0, (a, x) => a + x))     // 15

常量 f = 双倍                              // 函数名作值
打印(f(21))                              // 42

常量 加 = a => b => a + b                  // 柯里化:箭头函数返回箭头函数
打印(加(3)(4))                           // 7

常量 基数 = 100
常量 含基数 = x => 基数 + x                // 捕获(此后不赋值):按值绑定
打印(含基数(1))                          // 101

变量 计数 = 0
常量 加一 = () =>                          // 捕获且被改写 → 共享单元
    计数 = 计数 + 1
    返回 计数
常量 读计数 = () => 计数                   // 同一变量,同一单元
打印(加一(), 加一(), 读计数())           // 1 2 2
计数 = 100
打印(读计数())                           // 100(外层改写闭包可见)

9.10 正则(v2.0)

// 原始字符串:模式里的反斜杠原样书写
常量 行 = "2026-10-15 09:13:02 ERROR 登录失败 user=alice ip=10.0.0.7"

打印(正则分组(行, r"^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) (\w+)"))
// ["2026-10-15 09:13:02 ERROR", "2026", "10", "15", "09", "13", "02", "ERROR"]

打印(正则查找(行, r"user=(\w+)"), 正则分组(行, r"user=(\w+)")[1])   // user=alice alice
打印(正则全部(行, r"\d+"))                                        // ["2026", "10", "15", "09", "13", "02", "10", "0", "0", "7"]
打印(正则替换(行, r"(\d+)\.(\d+)\.(\d+)\.(\d+)", "$1.$2.*.*"))    // …ip=10.0.*.*
打印(正则分割("a, b;c", r"[,;]\s*"))                              // ["a", "b", "c"]

打印(正则匹配("HELLO", "hello", "i"))                             // 真(选项 i 忽略大小写)
打印(正则匹配("中文测试", r"[一-龥]+"))                            // 真
打印(正则匹配("abc", "(未闭合"))                                   // 运行时错误:正则模式错误:分组未闭合(缺少 ))

9.11 HTTPS(v2.1)

// 内置 TLS 客户端:证书链 + 主机名校验;CA 用系统包(或 ZHLANG_CA_FILE 指定)
常量 r = 网页请求("https://example.com/", "GET")
打印(r["状态"])                          // 200
打印(长度(r["体"]) > 0)                  // 真
打印(正则匹配(r["头"]["content-type"], "text/html"))   // 真

// 校验失败一定报错,不会静默放行:
// 取网页("https://127.0.0.1:8443/")     // CA 不匹配 → 运行时错误:TLS 握手失败(…):X509 - Certificate verification failed…
原生后端(M4):x86_64/aarch64 Linux 上同一 API 由 标准库/TLS.zh 的纯 zhlang TLS 1.3 实现(TLS_CHACHA20_POLY1305_SHA256 + x25519,证书链/主机名/SAN/有效期校验), 信任库见 §6.2 的原生后端说明;编译期按程序是否用到 取网页/网页请求 决定是否 发射 TLS 代码与内嵌 CA(不用 HTTP 的程序体积不变,PE 目标也不发射)。 ZHLANG_HTTPS=curl 显式回退系统 curl;证书校验失败不会自动回退。