Skip to content

【Zig 日报】ezi_gex 一个支持 Unicode 的 Zig 正则表达式引擎 #340

@jiacai2050

Description

@jiacai2050

ezi_gex 可在运行时和编译时运行,并具有可插拔的后端架构。

  • 线性时间。基于 Thompson-NFA——绝无灾难性回溯。(a*)*b 在长输入上表现良好。
  • Unicode 优先。\w, \b, \p{L}, \p{Script=Greek},大小写折叠以及字符类均符合 Unicode 标准,在编译阶段解析为码点范围(code-point ranges),因此在匹配时无需进行任何 Unicode 表查找。Unicode 支持来自 ezi_code;ezi_gex 从不直接调用 std.unicode。
  • 支持编译时(Comptime)。编译正则表达式并在编译时执行匹配——程序数据存储在 ro_data 中,匹配器在 comptime 运行。(这是 Zig 版的 C++ ctre 技巧,且具备完整的 Unicode 支持。)
  • 可插拔。匹配逻辑位于一个小巧、无需虚函数表(vtable-free)的后端契约之后。该库附带四个后端和一个调度器;你可以编写自己的后端并将其接入相同的入口。详见 docs/architecture.md。
  • 平台无关。对调用者提供的内存进行纯计算——无系统调用,无全局分配器,库代码中无任何平台假设。它在任何支持 Zig(以及 ezi_code)的平台上均可导入并编译:native、wasm32-freestanding / wasm32-wasi 以及裸机 *-freestanding(已验证在上述四种环境下均可编译)。

状态

版本 0.1.0——第一个带标签的发布版本。0.2.0-dev 正在开发中。处于 1.0 前阶段,API 可能仍会更改,但所有公共接口均已标注 @stable-since: v0.1.0,并从该标签起遵循 SemVer 规范。跟踪较新的 Zig 开发构建版本(0.17.0-dev);它无法在 0.16 稳定版上编译。
已实现的功能均经过测试(约 210 个测试用例:模块行为、跨后端一致性、运行时与编译时对等性)。第一个预过滤层已完成:字面量后端使用 std.mem.indexOf(SIMD memchr + Boyer–Moore–Horspool)进行扫描,并自动读取 HIR 分析以跳过 NFA 模式的工作——包括前导字面量 memchr 预过滤、^/\A 起始短路以及最小长度门控。目前尚无惰性 DFA 后端,因此在通用(不可前缀化)模式下,吞吐量低于 RE2/Rust;该架构无需更改 API 即可吸收 DFA——详见“性能”部分。

安装

通过 git 引用(在抓取时解析标签):
zig fetch --save git+https://github.com/shaik-abdul-thouhid/ezi-gex.git#v0.1.0
或通过普通 HTTP tarball(在 build.zig.zon 中固定内容哈希):
zig fetch --save https://github.com/shaik-abdul-thouhid/ezi-gex/archive/refs/tags/v0.1.0.tar.gz
跟踪 main 分支(未发布的 0.2.0-dev)——如果你想要最新的开发中接口,请抓取分支而不是标签。这会解析 main 当前的提交并锁定其哈希值;重新运行可更新到最新:
zig fetch --save git+https://github.com/shaik-abdul-thouhid/ezi-gex.git#main

main 是开发分支:它能编译且经过测试,但 API 尚未包含在标签中,在 0.2.0 之前可能会有变动。为了构建的可重复性,请优先使用已发布的标签;仅在需要未发布的功能时使用 main 分支。
然后在 build.zig 中(ezi_code 依赖项会被传递解析——你只需添加 ezi_gex):

const ezi_gex = b.dependency("ezi_gex", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("ezi_gex", ezi_gex.module("ezi_gex"));

快速概览

获取完整操作指南(包括词法分析、编译时路径、编写自己的后端等),请参阅 docs/usage-guide.md。

const gex = @import("ezi_gex");

// ── 运行时:编译模式(可能是用户提供的);永不崩溃 ──────
var diag: gex.Diagnostic = .{};
var re = gex.compileRuntime(gpa, "(?<user>\\w+)@(?<host>\\w+)", &diag, .{}) catch {
    // diag.message() + diag.faultySlice(pattern) 可告诉你错误内容及位置
    return;
};
defer re.deinit();

// Scratch 是每次搜索的工作状态 —— 由你拥有;每个线程一个。
// 直接从后端的 `Scratch` 类型构建(此处为堆分配);入口不会为你构造它。在多次搜索中复用;不要在线程间共享 Scratch。
var sc = try @TypeOf(re).Scratch.init(gpa, &re.program);
defer sc.deinit(gpa);

if (re.find(&sc, "ping bob@example")) |m| {
    _ = m.slice("ping bob@example"); // "bob@example"
}

// 捕获:提供一个 re.slotCount() == 2*(groups+1) 的槽位缓冲区
const slots = try gpa.alloc(?usize, re.slotCount());
defer gpa.free(slots);
if (re.captures(&sc, slots, "bob@example")) |c| {
    _ = c.namedSlice("user"); // "bob"
    _ = c.namedSlice("host"); // "example"
}

// ── 编译时:程序嵌入二进制文件;匹配在编译时运行 ─────
const Re = comptime gex.compileComptime("(\\d{4})-(\\d{2})", .{});
const yes = comptime Re.isMatchComptime("y2026-06"); // true,在构建时计算
const cap = comptime Re.capturesComptime("y2026-06").?; // 组在构建时解析(从 v0.2.0-dev 可用)
const year = comptime cap.groupSlice(1).?;              // "2026",一个 ro_data 切片(从 v0.2.0-dev 可用)

⚠️ 编译时有限制 —— 你需要自行权衡

compileComptime 在 Zig 编译器的常量求值器中运行从解析 → HIR → 程序降级的完整过程,并将结果嵌入到 ro_data 中。这会带来两个后果,你需要根据自己的选择承担——库不会为你做决定:

仅在编译器内存空间充足时有效。常量求值受 eval-branch 配额和编译器内存限制;大型、深度嵌套或病态的模式可能会超出配额,或导致构建缓慢且内存消耗巨大。对于大型或用户提供的模式,请优先使用 compileRuntime —— 运行时编译没有这种上限。
每个编译时程序都会将其范围(ranges)添加到 ro_data。一个 compileComptime 正则表达式会将字符类范围固化其中:每个不同的 \w 约 6.3 KB(约 800 个范围),每个 \p{L} 约 5.3 KB,ASCII 类 ≤0.5 KB。模式中相同的类会被“合并(interned)”为单个范围块,因此像 \w{3,32} 这样的重复计数只消耗一个 \w,而不是每个副本消耗一个 —— 重复不再成倍增加表大小。每个编译时模式中每个不同的类对应一个块;compileRuntime 不会增加二进制大小。
Unicode 表本身的成本是固定的,不是每个模式都有 —— 参见 § 二进制大小。在多个编译时程序中堆叠许多不同的 Unicode 类是增加 ro_data 大小的唯一方式。

文档目录

  • docs/usage-guide.md —— 实操指南。
  • docs/architecture.md —— 架构、数据流、如何编写自己的后端、注意事项及隐含假设。
  • src/core/README.md —— 前端(扫描器 → AST → HIR)。
  • src/engine/README.md —— 契约、NFA、后端、入口。
  • src/engine/backends/README.md —— 四个后端及 auto 的选择逻辑。

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:

  1. 供稿,分享自己使用 Zig 的心得
  2. 改进 ZigCC 组织下的开源项目
  3. 加入微信群Telegram 群组

Metadata

Metadata

Assignees

No one assigned

    Labels

    日报daily report

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions