hazeljs

中文文档
0.0.3发布于 2026/11/04MITTypeScriptESM + CJS零依赖

用自定义行为扩展普通JavaScript程序的Hazel编辑框。实验。

中文使用文档
文档目录17 节
  1. 为什么
  2. 安装
  3. 用法
  4. 分析并运行
  5. 从JavaScript调用Hazel函数
  6. API
  7. 值
  8. 模块程序
  9. 错误、漏洞和未完成的代码
  10. 向他们展示
  11. 无论如何运行
  12. 原型用例
  13. 尺寸和速度
  14. 真的是黑兹尔吗?
  15. 限制
  16. 关于淡褐色
  17. 许可证

Hazel's hazelnut logo

hazeljs

用自定义行为扩展普通JavaScript程序的Hazel编辑框。

你可以带一个框,一个文本区域或者你自己的编辑器;Hazeljs对输入的内容进行类型检查,指出哪里出错了,在哪里出错了,然后运行它,即使它还没有完成。

淡褐色的(带有漏洞的类型化函数式编程语言)作为JavaScript库。从Node或web页面分析和运行Hazel程序,并调用它们在JavaScript中定义的函数。

const Hazel = require("hazeljs");

const double = Hazel.run("fun (x : Int) -> x * 2").value;
double(21); // 42

实验。 这是一个原型(0.0)。x): API可能在任何版本中更改。它是从草稿库中构建的 hazelgrove /榛# 2711 和 # 2719,在提交时 0d66c36618 (Hazel.build 的名字)。

为什么

Hazel类型检查并运行未完成的程序:有漏洞或类型错误的程序仍然运行,绕过它们。这使得它成为一种很好的代码语言,一个页面允许人们在运行时编写和修改:规则、脚本、练习。

我们调用一个JavaScript程序,其事件处理程序是Hazel函数 Hazlet:页面对每个事件调用Hazel函数,并对其返回的内容进行操作。第一个是游戏,下面下面 原型用例。

安装

npm install hazeljs

或者在web页面中,从提供npm包的CDN:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/hazel-lib.js"></script>
<script>
  console.log(Hazel.run("1 + 2").value); // 3
</script>

该脚本定义了一个全局变量 Hazel (window.Hazel). 相同的文件在 https://unpkg.com/[email protected]/hazel-lib.js。

用法

CommonJS:

const Hazel = require("hazeljs");

ES模块,整个对象或其部分:

import Hazel from "hazeljs";
import { analyze, run, analyzeProgram, runProgram } from "hazeljs";

TypeScript类型包括(index.d.ts).

分析并运行

Hazel.analyze("1 + true");
// { errors: ["Error in term:\n  true\nNature of error: Expecting type Int but got inconsistent type Bool"] }

Hazel.run("let x = 2 in x * 21");
// { errors: [], value: 42 }

程序的错误不会使其停止运行:Hazel会对其漏洞和错误进行评估,因此 value 是否存在这两种情况,然后代码决定接受哪些错误。

从JavaScript调用Hazel函数

分析一个程序一次,然后随心所欲地调用它的函数:

const rule = Hazel.run(`
  type Cmd = + Done + Flip(Int, Cmd) in
  type Event = + Pull(Int) in
  fun e : Event -> case e | Pull(l) => Flip(l, Done) end`).value;

rule({ tag: "Pull", value: 2 }); // { tag: "Flip", value: [2, { tag: "Done" }] }

API

呼叫返回
Hazel.analyze(text){ errors }:程序的错误消息,以字符串的形式。
Hazel.run(text){ errors, value }:与程序的值相同。
Hazel.analyzeProgram(spec){ ok, errors, holes, programHoles, incomplete, programIncomplete, ms },用于命名模块程序(如下)。
Hazel.runProgram(spec)一样的,而且 value。
Hazel.version图书馆自己的标签,如 "0.1 (draft, js-library)"。
Hazel.build这个构建来自hazelgrove/hazel提交。

值

值可以以两种方式转换:Hazel的结果以这样的形式返回,而传递给Hazel函数的JavaScript值以这样的形式返回。

淡褐色的JavaScript
Int, Float 还有其他数字数量
Bool布尔
String字符串
一个列表一个数组
()null
标记元组 (x = 1, y = 2)一个对象 {x: 1, y: 2}
另一个元组 (1, true)一个数组 [1, true]
一个构造函数 A、应用 B(3){tag: "A"}, {tag: "B", value: 3}
一个函数一个JavaScript函数(一个参数,或多个元组参数)

其他任何东西(类型、洞)都会返回为 {hazel: "<its printing>"}。

JavaScript中的构造函数没有自己的类型,所以Hazel在参数被注释的地方检查它(fun e : Event -> ...). 对应用的构造函数进行整体、标签和有效负载检查;不适合该类型的变量将停留在参数上,调用返回 {hazel: ...}。

模块程序

对于每个模块都有一个编辑器的页面,将文本交给Hazel并让它从中构建一个程序:

const result = Hazel.runProgram({
  preamble: "type Cmd = + Done + Say(String) in",  // definitions every module sees, ending in `in`
  modules: [
    { name: "Common", text: "let greet = fun (s : String) -> Say(s)" },
    { name: "Room", text: "let on_event = fun (n : Int) -> Common.greet(\"hi\")" },
  ],
  tail: "Room.on_event",  // what the program gives; () if left out
});

result.ok;       // true: no errors, no holes, nothing left unfinished
result.value(1); // { tag: "Say", value: "hi" }

每个模块看到序言和它之前的模块,如 Common.greet尾巴能看见它们。模块的文本是它的主体:它的成员,没有 in。

  • errors: [{ module, message, id }],在那里 module 是错误所在模块的名称,还是 null 开头部分和结尾部分。
  • holes:每个模块的孔,按名称({ Common: 0, Room: 1 }); programHoles开头部分和结尾部分
  • incomplete:每个模块的括号和关键字都是打开的(a ( 没有 ),一个 case 没有 end)或关闭时没有开瓶器; programIncomplete开头部分和结尾部分
  • ok:没有错误,没有漏洞,没有不完整。仍然在编写的函数可能有漏洞,但没有错误,所以要采取行动 value 只有当 ok。
  • ms表示通话所用时间,单位为毫秒。在0.0.1到0.0.3中,它读取0(一个已知的错误);自己计算打电话的时间。

Hazel记得它读过的最后一个程序的文本,一部分一部分地,所以在一个模块改变后再次调用只读取该模块。

错误、漏洞和未完成的代码

Hazel在运行程序之前读取整个程序,并将错误作为数据报告,而不是作为异常报告。一个来自《乱糟糟的装扮》陷阱房间的例子:杠杆是一个 Side, L 或 R房间规则传递的是一个数字。

const result = Hazel.analyzeProgram({
  preamble: "type Side = + L + R in",
  modules: [
    { name: "Common", text: "let flip = fun (s : Side) -> s" },
    { name: "TrapRoom", text: "let answer = Common.flip(3)" },
  ],
  tail: "TrapRoom.answer",
});
{
  ok: false,
  errors: [{
    module: "TrapRoom",
    message: "Error in term:\n  3\nNature of error: Expecting type Side but got inconsistent type Int",
    id: "3d0f0000-0000-4000-b53b-3f45d4c660e0",
  }],
  holes: { Common: 0, TrapRoom: 0 }, programHoles: 0,
  incomplete: { Common: 0, TrapRoom: 0 }, programIncomplete: 0,
  // and ms
}

程序未准备就绪有三种方式,每种方式都在其所在位置计算:

什么例子在哪里显示
一个错误Common.flip(3)一个数,其中 Side 是想要的errors,每个都有它的模块
一个洞:还没有写的部分Common.flip(?)holes: { TrapRoom: 1 },没有错误
一些未完成的东西:一个括号或关键字没有它的伙伴Common.flip(Lincomplete: { TrapRoom: 1 }

一个只有文字的程序,通过阅读 Hazel.analyze 或 Hazel.run,将其错误报告为字符串,没有模块:

Hazel.analyze("let x : Int = true in x").errors;
// ["Error in term:\n  true\nNature of error: Expecting type Int but got inconsistent type Bool"]

向他们展示

Hazel的消息是为Hazel的开发人员编写的。Dress Up表示一个人读到的类型错误,使用这个函数(its) scriptErrorText,略为缩短)。其他信息则按原样传递:

function errorText(message) {
  const m = message.match(/^Error in term:\s*\n\s*([\s\S]*?)\nNature of error: Expecting type ([\s\S]*?) but got inconsistent type ([\s\S]*)$/);
  if (!m) return message;
  const a = t => (/^[AEIOU]/.test(t.trim()) ? "an " : "a ") + t.trim();
  return `${m[1].trim()}: ${a(m[2])} is wanted here, but this is ${a(m[3])}`;
}

for (const e of result.errors) console.log(`${e.module ?? "the program"}: ${errorText(e.message)}`);
// TrapRoom: 3: a Side is wanted here, but this is an Int

当你展示给他们看时,要知道两件事:

  • 输出一个没有名称的sum类型。 有 Side 在模块中定义,而不是在序言中定义,消息会说 L + R. 游戏将这些类型映射回自己的名称。
  • 一个不存在的名字是一个缺失的标签。 Common.flop(L) 给了 Nature of error: Label not found,因为模块的成员可以像带标签的元组一样被访问。

游戏在三个地方显示一个房间的错误:在房间的盒子下面,按名字,在它的轮廓上有⚠️和一个计数,在它的事件日志中作为一个检查条目,在任何东西运行之前。

The trap room's box in Messed Up Dress Up, with its type errors listed under its name.

无论如何运行

runProgram 和 run 评估错误和漏洞,就像Hazel经常做的那样 value 有没有可能 ok 是假的。但是,如果错误或漏洞停止了计算,则返回值为 { hazel: "…" },它停在一个术语的打印输出,一个页面不能对它进行操作。mess Up Dress Up总是用它自己的JavaScript来回答问题,而房间的规则则不是 ok,并将黑兹尔的答案记录在旁边,这样游戏就可以在规则固定的情况下继续运行。欲了解更多,请参阅游戏 游戏脚本指南,第8节,关于破碎的脚本。

原型用例

乱穿打扮 是一款纯JavaScript的装扮冒险游戏,由两名年轻玩家制作。这是第一个黑兹莱特:它的五个房间把他们的事件交给一个规则,一个功能 on_event : (Globals, Event) -> Cmd,玩家可以在游戏的📜脚本面板中用Hazel(或JavaScript或Fumola)编写。游戏从面板的模块中创建一个程序 Hazel.runProgram,然后用每个事件调用房间规则,并运行它返回的命令。

黑兹尔的陷阱室规则,游戏旁边:

The game's Scripting panel at full size: the game's view at the top left, and the trap room's rule in Hazel, a case on the event: a red lever pulled flips the pits wired to it, a blue lever pulled moves the boulders.

游戏和阅读同时进行:房间里的每个事件都交给黑兹尔,黑兹尔的回答也记录在小组的事件日志中:

The event log: calls of TrapRoom.on_event with Pull(L) and Pull(R) events, each with the event and the rule's answer as JSON, what the call changed (a lever from false to true), and how long the rule and the call took.

游戏的脚本指南,更多的这些图片: 游戏脚本指南。

尺寸和速度

hazel-lib.js 约为2.8 MB,压缩后为640 KB。在笔记本电脑上的Node 22中测量: require("hazeljs") 大约需要170毫秒,运行一个小程序大约需要30毫秒。一个包含100个短构件的模块的分析时间约为110 ms,一个包含400个短构件的模块的分析时间约为2.3 s;编辑后,只读取文本更改的模块。

真的是黑兹尔吗?

hazel-lib.js 是2.8 MB的编译JavaScript,所以阅读它并不能告诉你什么。您可以检查的是,在您可以读取的提交中,它正是Hazel的源代码编译成的。

它命名它的提交。 Hazel.build 返回构建它的hazelgrove/hazel提交,以及 hazel 领域的 package.json 命名同一个提交,连同它的分支和拉请求。对于这个版本来说是这样的 0d66c36618ee709a60f91cf01fbf0b46121f2499。

重新构建并比较。 和一个克隆的 hazelgrove /榛 并设置其工具链(参见其README),运行

scripts/verify-build.sh path/to/hazel

它检出提交、构建 hazel-lib.js Hazel自己的部署工作流(发布概要文件)的方式 GITHUB_SHA 设置为提交),并比较两个文件。每个字节必须匹配,除了一个注释行, //# buildInfo:,其中js_of_ocaml为自己的版本标签命名(5.9.1 从一次安装开始, 5.9.1+5.9.1 从Hazel的锁文件)。没有这一行,这个版本的文件是SHA-256 4695853925917b8dc8514a78c3adbd3140a2277d79ab2c27612ce1595e08e856。

或者让GitHub重新构建它。 这个库的 验证hazel-lib.js workflow在GitHub的机器上运行相同的脚本,从Hazel的一个新克隆。任何人都可以从Actions选项卡中运行它,并且它的运行是公开的。

Hazel自己的CI也构建了它。 Hazel的Deploy工作流构建推送到hazelgrove/ Hazel的每个提交。它是这个提交(运行39680368678)给出了相同的散列,但没有buildInfo行。该构建是服务于 https://build.hazel.org/js-library-embed/hazel-lib.js 直到分支得到一个更新的提交:

curl -sL https://build.hazel.org/js-library-embed/hazel-lib.js | grep -v '^//# buildInfo:' | sha256sum
grep -v '^//# buildInfo:' node_modules/hazeljs/hazel-lib.js | sha256sum

这显示了文件的来源。它是Hazel的提交,由OCaml工具链在Hazel的锁文件中编译,仅此而已。它并不表明提交的代码是你想要的;为此,请阅读它,或者相信它的作者。

限制

  • 实验。 还没有稳定的承诺;固定一个精确的版本。
  • 同步的。 每个调用都在调用线程上运行,因此长时间运行的程序会阻塞它。
  • 一个全球性的。 图书馆放了一个 Hazel 全局对象上的。
  • 用于慢读的控制台行。 只有Hazel的较慢的恢复解析器才能读取的文本(未闭括号,例如 let x = ()打印a SLOW PARSE (...) 在读取时将行发送到控制台。
  • 不能撤消或重做。 图书馆没有编辑器:它读取你给它的文本和答案。撤销和重做是Hazel编辑器的一部分,而不是这个库的一部分,因此保存文本的页面保留其历史:保留它拥有的文本(或每个模块的文本),并再次传递较旧的文本以返回。返回的代价是编辑所做的:重新读取自上次调用以来文本更改的模块,而其他模块则不读取。
  • 错误命名一个术语,而不是一个地方。 一个错误的 id 是Hazel语法树中对应项的id;图书馆还没有为它提供行和列。
  • 卡住的值是调试输出。 如果出现错误或漏洞导致计算停止, { hazel } 保存术语的OCaml打印输出,而不是Hazel语法。
  • 深度程序需要更大的堆栈。 Hazel的分析深入递归:在Node中,一个大约有150个成员的模块溢出了默认堆栈(Stack overflow). 提高: node --stack-size=32000 app.js,将壳自身的极限提高到与(ulimit -s 65536),否则Node会崩溃。

关于淡褐色

Hazel是由Hazel团队开发的:参见 hazel.org 对于这个项目,它的论文和制作它的人,还有 GitHub的贡献者 为代码工作的人。它的来源是 hazelgrove /榛,问题和bug就归它了 问题跟踪器。

许可证

MIT,就像Hazel一样。看到 许可证。

版本与发布记录

hazeljs 共发布 4 个版本,首次发布于 2026/11/03;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.0.3latest2026/11/04今天
0.0.22026/11/04今天
0.0.12026/11/031 天前
0.0.0-stage2026/11/031 天前

关于 hazeljs 的常见问题

hazeljs 是什么?

用自定义行为扩展普通JavaScript程序的Hazel编辑框。实验。

如何安装 hazeljs?

在项目目录运行 npm install hazeljs,或使用 pnpm add / yarn add / bun add。

hazeljs 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

hazeljs 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

hazeljs 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

hazeljs 可以商用吗?

hazeljs 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

hazeljs 还在维护吗?

最新版本 0.0.3 发布于 2026/11/04(今天);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/11/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。