news 2026/9/26 2:44:57

Wren 语言核心指南:小巧快速、类机制与 Fiber 并发一体的嵌入式脚本语言

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Wren 语言核心指南:小巧快速、类机制与 Fiber 并发一体的嵌入式脚本语言
  • 编程语言
  • 语言运行时
  • 编译器

【免费下载链接】wren

The Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.

项目地址:https://gitcode.com/gh_mirrors/wr/wren
点击查看免费下载

Wren 是一款面向嵌入式场景设计的解释型脚本语言,官方将其概括为 "a small, fast, class-based concurrent scripting language"。本文以仓库根目录的 README.md 为主线,逐条拆解 Wren 的五大设计支柱(小、快、基于类、并发、可嵌入),并结合本仓库中的 VM 源码、C API 头文件、官方文档与测试用例给出源码级证据,最后给出完整的构建与嵌入 C 程序的可运行方案。读完本文,你将掌握 Wren 的语法风格、执行模型,以及如何把 Wren VM 作为库嵌入到自己的 C/C++ 程序中。

一句话认识 Wren:Smalltalk 的思想、Lua 的体积、Erlang 的并发

README 开篇用一句话给出了 Wren 的定位:

Think Smalltalk in a Lua-sized package with a dash of Erlang and wrapped up in a familiar, modern syntax.

也就是说,Wren 借鉴了 Smalltalk 的纯面向对象思想、Lua 级别的极简体积,并从 Erlang 的并发理念中抽取了轻量协程模型,最后用一套现代而熟悉的 C 系语法包装起来。README 给出的示例代码同时展示了类、Fiber 协程和字符串插值三种核心能力:

System.print("Hello, world!") class Wren { flyTo(city) { System.print("Flying to %(city)") } } var adjectives = Fiber.new { ["small", "clean", "fast"].each {|word| Fiber.yield(word) } } while (!adjectives.isDone) System.print(adjectives.call())

这段代码可以拆解为三个语言特性:System.print与%(city)字符串插值属于标准输出与语法层面;class Wren声明类并定义方法体现其基于类的对象模型;Fiber.new { ... }创建协程、Fiber.yield(word)挂起当前协程、.call()唤醒协程,则是 Wren 并发模型的核心用法。这一小段示例几乎浓缩了整门语言的精华。

五大设计支柱:从 README 到源码的逐条验证

小:整个 VM 实现不足 4000 个分号

README 宣称 "The VM implementation is under 4,000 semicolons",并强调它 "small, but not dense"——即代码量小但可读性高、注释详尽。这一点可以直接在仓库中验证:VM 的全部实现集中在 src/vm 目录下,包含编译器 wren_compiler.c(约 4140 行)、虚拟机主循环 wren_vm.c(约 1994 行)、值表示 wren_value.c 等文件。对src/vm下所有.c/.h文件的粗略统计,分号总数约 3600 余个,与 README "under 4,000 semicolons" 的说法一致。

体积小的另一面是依赖极少:src/README.md 说明源码仅由include(公共头文件目录)、vm(VM 本体)、optional(可选模块)三部分组成,官方文档也明确 Wren "has no dependencies beyond the C standard library"。代码同时兼容 C99 与 C++98 及之后的任何标准,这为嵌入到各种宿主项目提供了极大的便利。

快:单遍编译器与紧凑对象表示

Wren 的性能路线是"单遍编译到紧凑字节码 + 紧凑对象表示":

  • 单遍编译器:wren_compiler.c 在 wren_opcodes.h 定义的指令集之上实现了一次扫描即产出字节码的编译器,跳过了 AST 中间环节,从源码解析到字节码一气呵成。
  • 紧凑对象表示(NaN tagging):wren_value.h 中用大段注释解释了 NaN tagging 的原理:IEEE 754 双精度浮点数的 NaN 位模式有大量冗余,Wren 把这些"无意义"的位用来编码布尔值、null、堆对象指针等,而数值本身不做任何掩码处理,因此数值运算路径零开销。该注释也说明这种表示把 double、指针与若干单例值全部塞进同一个 64 位序列,从而压缩了对象体积、提升了缓存友好度。
  • NaN tagging 并非硬编码,而是由编译配置开关控制:projects/make等工程中提供了release_64bit-no-nan-tagging这类配置,可在不需要该优化时关闭。

关于性能的详细对比数据,官方另有 性能文档 阐述设计取舍;仓库的 test/benchmark 目录则收录了 fib、binary_trees、delta_blue 等经典基准的 Wren 实现(如 fib.wren、binary_trees.wren),并附有 Lua、Python、Ruby、Dart 等同题实现供横向参考。

基于类:类与继承位于语言中心

与许多"类机制不完整甚至缺席"的脚本语言不同,Wren 把类放在了前台。README 明确 "Wren places classes front and center"。仓库证据包括:

  • 语法层面:类、继承、构造器都是一等语法,详见 类文档 与 方法调用文档。
  • 实现层面:核心对象系统在 wren_value.c 中实现,ObjClass等结构负责类的元模型。
  • 测试层面:test/language/class、test/language/inheritance等目录(各含 18 个用例)系统覆盖了类声明、继承、多态等场景;test/core/class下也有 equality、supertype 等行为测试。

并发:轻量 Fiber 协程作为执行模型核心

README 将 Wren 描述为 "concurrent",其实现载体是轻量协程 Fiber:所有 Wren 代码都运行在某个 fiber 内,脚本启动时自动创建 main fiber;新协程由Fiber.new {}创建,call()唤醒、Fiber.yield()挂起、transfer()切换。官方 并发文档 进一步说明:

  • Fiber 是协作式调度,不会在随机时刻被抢占,开发者完全掌控切换时机,避免了线程式并发的上下文切换开销与竞态麻烦;
  • Fiber 不占用操作系统线程资源,每个 fiber 只需一块栈内存,且和普通对象一样可被垃圾回收,因此可以大量创建——文档举例说游戏里可以为每个实体分配一个 fiber,交互式模式下每一行输入代码都会新建一个 fiber。

README 示例中的while (!adjectives.isDone) System.print(adjectives.call())正是 yield/call 协作的典型用法:主协程反复唤醒adjectives协程,后者每次 yield 出一个形容词,直到耗尽。仓库 test/core/fiber 下上百个.wren测试用例(call、transfer、yield 及其各种参数、错误与重入场景)构成了对这一模型最详尽的规范说明。

脚本语言:为嵌入而生,无全局状态

Wren 的最终定位是"嵌入到应用程序中运行的脚本语言",为此它:

  • 只提供小型标准库和一套易用的 C API(详见下文);
  • 关键设计是无全局状态:所有解释器状态都封装在WrenVM结构体内(wren.h 注释明确 "Wren has no global state"),因此宿主可以同时创建多个互不干扰的 VM,甚至在不同线程上并发运行。

如何运行 Wren:三种上手路径

官方文档(快速上手)给出了三条路径:

  1. 浏览器在线体验:仓库 doc/site/try 目录是官方 Web 版的源码,包含 wren_try.wasm 与配套的 wren_try.js 前端脚本,无需安装即可运行 Wren 代码。
  2. Wren CLI:用于直接执行脚本的命令行程序,支持文件 IO 等能力。需要说明的是,CLI 属于独立的配套项目,本仓库只包含语言核心(VM),不含 CLI 可执行程序。
  3. 嵌入到自己的代码:把 Wren VM 编译成库或直接合入源码,再通过 C API 调用,这也是本仓库的核心用法,下文重点展开。

构建 Wren:工程与产物

仓库 projects 目录下按平台提供了现成的构建工程:

  • Windows:打开 projects/vs2019/wren.sln(或vs2017版本)直接编译;
  • macOS:打开 projects/xcode/wren.xcworkspace;
  • Linux/BSD:进入 projects/make(BSD 对应 projects/make.bsd、macOS 对应 projects/make.mac)执行make。

以 projects/make/Makefile 为例,默认配置为release_64bit,可选的config值包括:release_64bit、release_32bit、release_64bit-no-nan-tagging、debug_64bit、debug_32bit、debug_64bit-no-nan-tagging,对应产物分别为静态库wren、动态库wren_shared以及用于运行测试的wren_test。

构建完成后,库文件统一输出到仓库根目录的lib/文件夹:

链接方式WindowsLinuxmacOS
静态链接wren.liblibwren.alibwren.a
动态链接wren.dlllibwren.solibwren.dylib

此外,官方还提供了"聚合构建"(amalgamated/unity build):运行python3 util/generate_amalgamation.py > build/wren.c(脚本见 util/generate_amalgamation.py)即可把全部 VM 源码合并进单个wren.c,配合 src/include/wren.h 一起加入工程即可,这是最简单的接入方式。

把 Wren 嵌入你的 C 程序:最小可运行示例

包含头文件

无论静态/动态链接还是直接编入源码,都需要把 src/include 加入头文件搜索路径,然后:

#include "wren.h"

如果宿主程序是 C++ 而链接的是以 C 编译的 Wren 库,则改用 wren.hpp(它负责处理 C/C++ 调用约定差异)。Wren 只依赖 C 标准库,无需额外链接;不过 README 与文档提醒,在部分平台(如 BSD 与 Linux)上math.h的部分函数位于独立的libm库中,需要显式链接。

创建 VM:WrenConfiguration 与 wrenNewVM

创建 VM 的第一步是初始化配置结构体:

WrenConfiguration config; wrenInitConfiguration(&config);

wrenInitConfiguration()会填入全部默认值(原型见 wren.h)。随后可按需覆写字段,最常用的是回调与内存参数,核心字段如下:

字段类型作用默认行为
writeFnWrenWriteFn接收System.print/System.write输出的文本(不含换行)为 NULL 时丢弃输出
errorFnWrenErrorFn报告编译错误、运行时错误与栈帧为 NULL 时不报告错误
loadModuleFnWrenLoadModuleFn按模块名加载并返回源码(宿主负责文件 IO)无
resolveModuleFnWrenResolveModuleFn把 import 字符串规范化为模块名,可用于实现相对导入为 NULL 时原样使用 import 字符串
bindForeignMethodFnWrenBindForeignMethodFn把foreign方法签名绑定到 C 函数指针无
bindForeignClassFnWrenBindForeignClassFn绑定 foreign 类的 allocate/finalize 回调无
reallocateFnWrenReallocateFn自定义内存分配/释放内置realloc/free实现
initialHeapSizesize_t首次触发 GC 的堆阈值0 时默认 10MB
minHeapSizesize_tGC 后堆的最小保留量,防止堆收缩过小导致频繁 GC0 时默认 1MB
heapGrowthPercentintGC 后按当前存活字节的此百分比设定下一次收集阈值(例:50 表示存活 400 字节时,累计分配 600 字节触发下次 GC)0 时默认 50
userDatavoid*与 VM 关联的宿主自定义数据无

配置就绪后创建 VM:

WrenVM* vm = wrenNewVM(&config);

wrenNewVM()会拷贝一份配置(wren.h),调用后即可丢弃传入的配置结构体。由于 VM 无全局状态,多个 VM 可以完全独立运行。

执行代码:wrenInterpret 与结果码

向 VM 提交一段 Wren 源码:

WrenInterpretResult result = wrenInterpret(vm, "my_module", "System.print(\"I am running in a VM!\")");

执行流程是:先把源码单遍编译为字节码,编译失败立即返回WREN_RESULT_COMPILE_ERROR;随后为这段代码新建 fiber 并运行,期间代码可自行派生更多 fiber;若运行时错误未被其他 fiber 捕获,则一路回退到主 fiber 并返回WREN_RESULT_RUNTIME_ERROR;全部 fiber 正常结束则返回WREN_RESULT_SUCCESS。所有传给wrenInterpret()的代码都运行在特殊的 "main" 模块中,因此多次调用之间可以共享顶层变量,行为类似 REPL。

释放 VM:wrenFreeVM

wrenFreeVM(vm);

调用后该WrenVM*不可再使用。特别地,如果仍持有存活的WrenHandle(见 槽与句柄文档),Wren 会主动报错,防止句柄泄漏或悬空。

完整可运行示例

仓库 example/embedding/main.c 提供了上述全流程的完整实现,摘录核心部分:

#include <stdio.h> #include "wren.h" static void writeFn(WrenVM* vm, const char* text) { printf("%s", text); } void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) { switch (errorType) { case WREN_ERROR_COMPILE: printf("[%s line %d] [Error] %s\n", module, line, msg); break; case WREN_ERROR_STACK_TRACE: printf("[%s line %d] in %s\n", module, line, msg); break; case WREN_ERROR_RUNTIME: printf("[Runtime Error] %s\n", msg); break; } } int main() { WrenConfiguration config; wrenInitConfiguration(&config); config.writeFn = &writeFn; config.errorFn = &errorFn; WrenVM* vm = wrenNewVM(&config); const char* module = "main"; const char* script = "System.print(\"I am running in a VM!\")"; WrenInterpretResult result = wrenInterpret(vm, module, script); switch (result) { case WREN_RESULT_COMPILE_ERROR: printf("Compile Error!\n"); break; case WREN_RESULT_RUNTIME_ERROR: printf("Runtime Error!\n"); break; case WREN_RESULT_SUCCESS: printf("Success!\n"); break; } wrenFreeVM(vm); }

errorFn展示了三种WrenErrorType的典型处理:编译错误携带模块名与行号、运行时错误只有消息、而栈跟踪信息则以WREN_ERROR_STACK_TRACE逐帧回调。这既是错误处理的最小实现,也对应 错误处理文档 描述的 Fiber 与错误传播机制。

更进一步的嵌入能力

VM 与宿主之间传递数据统一通过"槽"(slot)完成,宿主可用WrenHandle长期持有对 Wren 对象的引用以防被 GC 回收,详见 槽与句柄文档;foreign 类与 foreign 方法让 C 数据结构可以无缝暴露给 Wren 代码,参考 存储 C 数据 与 从 C 调用 Wren。仓库 test/api 下的 api_tests.c、slots.c、handle.c、foreign_class.c 等测试文件则是这些 API 用法的权威参考。

可选模块:Meta 与 Random

仓库 src/optional 提供了两个可选模块,它们默认编译进 VM,也可通过编译宏裁掉:

  • meta(wren_opt_meta.wren):提供Meta.eval、Meta.getModuleVariables等元编程能力,对应测试见 test/meta;
  • random(wren_opt_random.wren):提供随机数生成与洗牌等能力,对应测试见 test/random。

这些模块同样采用"核心用 Wren 写、性能敏感处用 C 实现"的分层策略,src/optional中每个模块都同时存在.wren与.c源码。

学习路线与测试体系

若要进一步学习,仓库 doc/site 下有一整套官方文档,建议按此顺序阅读:

  1. 语法文档:注释(支持嵌套块注释)、保留字(as break class construct continue else false for foreign if import in is null return static super this true var while)、标识符等语言基础;
  2. 变量与值、类、方法调用;
  3. 并发 与 错误处理:理解 Fiber 与错误传播;
  4. 嵌入指南 系列:配置 VM、槽与句柄、foreign 机制。

测试方面,test/core(覆盖 bool/class/fiber/function/list/map/number/string 等核心类)、test/language(覆盖 30+ 语法主题,从赋值、闭包到继承、非局部跳转)、test/api(C API 行为)、test/benchmark(性能基准)以及 test/regression(历史缺陷回归)共同构成了语言规范的"可执行文档",是深入理解 Wren 行为边界的首选素材。

结语

从 README 出发可以看到,Wren 的设计哲学高度自洽:体积小换来易读与易嵌入,单遍编译与紧凑对象表示换来性能,类机制保证了对象模型的纯粹,Fiber 协程提供了协作式并发的轻量抽象,而零依赖的 C API 让它可以安静地"住进"任何宿主程序。当前仓库对应版本 0.4.0(见 wren.h 中的版本宏)。如果这些特性符合你的需求,不妨从 示例脚本 开始体验语言本身,再按上文步骤把 VM 嵌入到自己的项目里。

  • 编程语言
  • 语言运行时
  • 编译器

【免费下载链接】wren

The Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.

项目地址:https://gitcode.com/gh_mirrors/wr/wren
点击查看免费下载

相关推荐

上一篇:如何在Mac上部署Unlimited-OCR-bf16?5分钟快速上手AI文档解析工具
下一篇:5步精通XHS-Downloader:小红书内容备份与数据导出完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:42:28

cx23885视频采集卡驱动实战:从源码解包到V4L2调试

简介&#xff1a;Conexant CX23885 是一款面向 PCIe 接口的视频桥接芯片&#xff0c;本压缩包提供其 Linux 驱动源码&#xff0c;供内核驱动开发者、嵌入式视频采集模块调试者参考。该驱动用于解决 CX23885 在 PCIe 总线下的设备注册、视频流捕获与硬件控制等核心问题&#xff…

作者头像 李华
网站建设 2026/9/26 2:41:43

扭矩矢量控制:分布式电驱时代的底盘控制革命

1. 这不是“高级差速锁”&#xff0c;而是电驱时代的底盘控制范式革命你可能在某款新发布的纯电SUV宣传页上见过这个词——“扭矩矢量控制”&#xff0c;旁边配着车辆过弯时内侧轮减速、外侧轮加速的动态示意图&#xff0c;文案写着“精准过弯”“弯道如直线”。但如果你真去查…

作者头像 李华
网站建设 2026/9/26 2:41:35

零基础跑通3D高斯泼溅:Spirula Studio 命令行训练完整指南

零基础跑通3D高斯泼溅&#xff1a;Spirula Studio 命令行训练完整指南 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Spi…

作者头像 李华
网站建设 2026/9/26 2:40:56

判据、全绿、缺口、漂移与自查 —— 线上补课(下)

系列&#xff1a;gdev-master&#xff08;NVIDIA/nouveau 用户态 GPGPU 运行时&#xff09;从 C/C 到 Rust 的移植工程 第三部换了一个问题&#xff1a;前两部回答「怎么搬」&#xff0c;这一部回答「凭什么说对了」。 一句话核心最值钱的一格每写一条检查&#xff0c;同时写…

作者头像 李华