API

语言 API 与命令行

内置函数中文名与英文别名一一对应。完整语义以《zhlang 语言规格》为准;本页 §6 内容由 docs/spec.md 自动生成。

能力边界: 本页包含完整语言规格中的内置条目;自举版 GC 为阶段 2(保守标记-清除,分配优先复用空闲链)。

语言 API

主题内容
词法与字面量标识符、数字(0x/0b/0o/_)、字符串/原始串/模板串、注释 · 查看
类型与值整数/浮点/字符串/布尔/数组/字典/函数/指针;真值规则、深相等 · 查看
语句与程序结构变量/常量、控制流、函数/闭包、结构体/类/初始化、模块、异常 · 查看
并发协程(启动)、通道(无缓冲/缓冲)、选择、死锁 · 查看
错误约定运行时错误文案、尝试/捕捉/最终、调用栈 · 查看
完整规格词法/语法/标准库/实现形态/示例

标准库与内置

以下内容从规格 §6 自动生成;内置函数也可用英文别名。

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)。

  • 协议:TLS 1.3 与 1.2(客户端;C 版/自举的 mbedTLS 路径)。原生后端的纯 zhlang TLS 仅实现 TLS 1.3(TLS_CHACHA20_POLY1305_SHA256 + x25519);只支持 TLS 1.2、或不提供 ChaCha20 密码套件的服务器(部分 badssl 用例、百度等)无法连接,可用 ZHLANG_HTTPS=curl 回退系统 curl。SNI 送出;只做客户端,不提供 DTLS/服务端/客户端证书/会话票据持久化。
  • 撤销检查:mbedTLS 路径(C 版/自举)不做 OCSP/CRL;原生后端的纯 zhlang TLS 支持 CRL

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

  • 校验:VERIFY_REQUIRED——证书链必须链到受信任 CA,且主机名必须与证书 SAN(无 SAN 时回退 CN)匹配;

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

  • CA 来源(按序):环境变量 ZHLANG_CA_FILE / SSL_CERT_FILE → 系统包

(/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 后立即生效。

  • 超时:连接 5s;收发沿用套接字读超时 10s(TLS 层把读超时映射为错误而不是空转)。
  • 收尾:对端直接关连接而未发 close_notify(精简实现常见)按正常结束处理;正文以连接关闭定界,

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

  • 排查开关:ZHLANG_TLS_DEBUG=2(1~4 递增)打印 mbedTLS 报文级日志到 stderr。
  • 原生后端(纯 zhlang TLS,M4):x86_64/aarch64 Linux 的 https 由 标准库/TLS.zh 完成 TLS 1.3 握手、证书链/主机名校验与传输;不依赖 curl、不链接 C 库。信任库顺序:ZHLANG_CA_FILE → SSL_CERT_FILE → 系统 CA 文件 → 编译期内嵌 third_party/ca-bundle.crt(ZHLANG_CA_EMBEDDED=1 可强制内嵌)。校验失败必定报错,不静默回退;套接字在原生后端就是 fd/SOCKET 整数,类型(监听(...)) 返回 整数,打印按整数输出;套接字 类型标注按整数承载检查(见 §2.3);ZHLANG_HTTPS=curl 可显式改用系统 curl -sS -i。Windows PE 仍用 WinHTTP(系统 TLS/CA)。TLS 代码与内嵌 CA 只在程序用到 取网页/网页请求 时发射。
  • 体积账:TLS 代码只在程序用到 取网页/网页请求 时才链进产物(编译期门控 ZHLANG_HTTP_TLS),

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

6.4 进程与终端(v2.6)

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

  • 执行捕获(程序, 参数数组) —— 与 执行 的关键差别是不经 shell:

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

  • 是终端() —— isatty(0)。REPL 据此决定要不要印提示符(>>> / ... );

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

  • 忽略中断(真/假) —— 置 SIGINT 为 SIG_IGN/SIG_DFL。REPL 开头调

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

  • 程序路径() —— argv[0]。CLI 靠它定位自身可执行文件所在目录,

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

  • 输入结束() —— feof(stdin)。读入 在 EOF 与「用户敲了空行」都返回 "",

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

  • 输出(s) —— 逐字写 stdout、不补换行、立即冲刷。打印 会补换行并加空格分隔,

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

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

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

@引入 "同步.zh" 只 互斥锁, 读写锁, 等待组, 一次
@引入 "同步.zh" 为 同步
  • 互斥锁:初始化()、加锁()(阻塞)、解锁()(未持有时抛可捕获错误;不校验解锁者身份)、

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

  • 条件:初始化(互斥锁)、等待()、通知() → 真/假、通知全部() → 被唤醒数。

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

  • 读写锁:读锁()/读解锁()、写锁()/写解锁()、尝试读锁()/尝试写锁()、

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

  • 等待组:添加(n)、完成()(=添加(-1))、等待()。计数减到 0 时广播唤醒全部等待者;

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

  • 一次:执行一次(f)。首个调用者执行 f 并返回 真,其余调用者等待 f 结束(或抛错)后返回 假;

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

限制与约定:

  • 全部阻塞都是通道阻塞,由调度器挂起协程,不占死 worker;实现只依赖 通道/选择,不新增运行时。
  • 互斥锁/读写锁不是重入锁,也不校验持有者身份(与 Go sync 一致)。
  • zhlang 没有 finally;临界区可能抛错时用 加锁执行/读锁执行/写锁执行,手写加解锁需覆盖所有退出路径。
  • 通道只保护队列本身,不保护负载;数组/字典内部仍无锁,并发读写共享容器必须用这里的锁或通道所有权。
  • 同步模块基于协程/通道,自举原生版已支持(x86_64/aarch64 实测,Windows PE 经 wine 验证)。

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

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

  • 错误基类:字段 消息 字符串、代码 整数、原因(默认 假);方法 文本() 返回消息。

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

  • 工厂:新建错误(消息)、新建错误码(消息, 代码)、新建错误类型(类型, 消息, 代码)。

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

  • 工具:错误文本(e)(字符串原样;字典优先取 消息;其余转字符串)、

是错误(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。

约定:

  • 模块内不 @引入 其他模块,只依赖内置;同名包装(如 文件.写文件)内部用英文内置别名

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

  • 普通函数没有默认参数,因此可选参数一律拆成独立变体(如 正则.匹配选项,

进程.环境变量或)。

  • 官网自举原生版以原生后端为准:原生 x86_64/aarch64 Linux 可用全部可引入模块

(含 同步,纯 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)自本版起可以编出自身:

  • 自举不动点:stage0(仓库 stage-0 引导器)→ stage1(stage0 编译编译器源码)

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

  • Go 式多目标:一个原生编译器二进制内建 x86_64 ELF/PE 与 aarch64 后端。

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

  • Windows PE 目标:读文件/写文件/追加文件/文件存在/文件大小/删除文件/建目录

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

  • Windows 交叉自举:Linux 上的 stage0 可直接产出 PE 编译器;该 PE 编译器

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

  • aarch64 交叉自举:a64 后端已与 x86_64 后端同覆盖面(容器/字典/异常/

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

  • 原生网络与接口(v2.25):x86_64-linux 原生后端新增纯 syscall 网络栈

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

  • 仍未打通:PE 目标上的 REPL 行编辑/终端路径未在真机充分验证;

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

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 写数值或名称作为上限,等级大于上限的断点 不输出也不暂停;其它非标准值不参与过滤,始终显示。

  • 开关:只有 -d/--debug 才生成断点代码;不加时整条指令剥掉,参数表达式

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

  • 输出:运行时向 stderr 打印一行 断点 <文件>:<行> <函数> [<等级>] <内容>;

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

  • 暂停策略:stdin 不是终端时打印后继续(便于 CI/重定向);

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 追加一批),仍不构造完整字符串

动态加载有两层:

  • 服务端按需:按需(加载函数) 节点的加载函数在输出到该位置时才调用,返回要渲染的

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

  • 浏览器端动态:动态加载(地址)/动态加载文本(地址, 占位) 输出带 data-src 的

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

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

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

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

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

语义与约束:

  • 记录:只允许 JSON 数据(字典/数组/整数/浮点/字符串/布尔,字典键必须是字符串)。

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

  • 主键:只接受 整数/字符串,插入与更新时保证唯一;按 用主键命中原行或返回 假。
  • 读取返回副本:按/找/查询/第一/全部 都返回深拷贝,修改必须走 更新/删除,

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

  • 索引:建索引(字段) 只加速 字符串/布尔 的等值查询;数值与容器值走线性扫描,

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

  • 持久化:保存() 先写 路径.bak 再写主文件;打开 时主文件损坏则回退 .bak。

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

  • 事务:事务(f) 在任何抛出时把全部表恢复到进入前状态并重抛;不支持嵌套;

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

  • 并发:本地库对象不是协程安全的;跨协程共享请用 同步.互斥锁 保护。
  • 远程:远程服务(库, 端口, 令牌) 启动令牌认证服务,远程连接(地址, 端口, 令牌)

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

  • 外部数据库网关:纯 zhlang 且原生后端无 C 混写,不能直接加载 JDBC/ODBC/

原生驱动。网关连接 把「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
    抛出("测试失败")

命令行(自举编译器)

选项说明
-o <文件>指定输出二进制名
--target <目标>交叉输出:x86_64-linux-gnu / aarch64-linux-gnu / x86_64-windows-gnu
--targets列出支持的目标与能力
get <包名>[@版本]从包仓库下载安装(--仓库 覆盖,默认 pingsec.top/repo)
--repl, -i交互式会话(整段重跑)
-d, --debug调试运行(临时编译执行,不留产物;加 -o 产出调试二进制)
--version, -v版本信息
-h, --help帮助