
编程语言语言运行时编译器【免费下载链接】wrenThe Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.项目地址https://gitcode.com/gh_mirrors/wr/wren点击查看免费下载Wren 是一门小巧、快速、基于类并发的脚本语言。本指南聚焦其官方可选模块meta完整讲解Meta类的四个静态方法——getModuleVariables、eval、compile、compileExpression——的用法、返回值语义、错误行为并结合仓库源码与测试用例深入解析其底层实现原理。读完本文你将掌握如何用meta在运行时读取模块变量表、动态编译并执行 Wren 代码以及如何用Meta.compile实现枚举等常见元编程模式。一、模块总览meta 是什么如何启用meta模块为 Wren 提供了一类特殊的元编程能力它能在运行时读取某个模块的顶层变量列表并把字符串形式的 Wren 源码或表达式现场编译成闭包函数供后续调用。这意味着程序可以自己写代码、自己编译、自己执行是构建 DSL、解释器、代码生成工具的基石。从仓库结构看meta是一个可选模块optional module其实现位于 src/optional/wren_opt_meta.c配套的 Wren 源码位于 src/optional/wren_opt_meta.wren模块内嵌的字节码源文件为 src/optional/wren_opt_meta.wren.inc。模块本身只包含一个类Meta因此官方文档总结为它使得 Wren 能够进行某些类型的元编程见 doc/site/modules/meta/index.markdown。1.1 启用与关闭方式meta默认是启用的。在 src/vm/wren_common.h 中所有可选模块WREN_OPT_META、WREN_OPT_RANDOM默认定义为1// The VM includes a number of optional modules. You can choose to include // these or not. By default, they are all available. To disable one, set the // corresponding WREN_OPT_name define to 0. #ifndef WREN_OPT_META #define WREN_OPT_META 1 #endif如果你希望从应用程序中移除meta模块只需在编译时把预处理器常量WREN_OPT_META设为0。整个模块的 C 实现src/optional/wren_opt_meta.c与头文件src/optional/wren_opt_meta.h都被#if WREN_OPT_META包裹关闭后相关代码不会被编译进 VM。此外在 VM 的模块加载路径中meta属于内置可选模块当宿主应用没有为某个模块名提供自定义加载器时src/vm/wren_vm.c 会检查模块名是否为meta若是则直接使用wrenMetaSource()提供的内嵌源码因此import meta无需任何外部文件即可工作#if WREN_OPT_META if (strcmp(nameString-value, meta) 0) result.source wrenMetaSource(); #endif1.2 导入方式与其他模块一样使用meta前需要显式导入官方文档给出的导入语句为见 doc/site/modules/meta/meta.markdownimport meta for Meta导入后即可通过Meta类调用其静态方法。二、getModuleVariables读取模块的顶层变量清单2.1 方法签名与语义Meta.getModuleVariables(module)getModuleVariables返回一个列表List包含指定module中定义或可见的所有模块级变量module level variables。官方文档明确指出见 [doc/site/modules/meta/meta.markdown#L13-L15]这包括从其他模块显式导入的变量以及从内置模块隐式导入的变量。也就是说返回列表 该模块自身的顶层变量 显式import进来的名字 所有内置核心类Object、Bool、List等。2.2 完整示例输出模块变量表官方文档给出了一对模块的完整演示。先创建被导入的模块module.wren/* module.wren */ var M 1再创建主模块get_mod_vars.wren/* get_mod_vars.wren */ import meta for Meta import ./module for M var v 42 var f Fn.new { var g 2 } class C {} System.print(Meta.getModuleVariables(./get_mod_vars)) var w 43 // still returned even though defined later运行后输出[Object, Class, Object metaclass, Bool, Fiber, Fn, Null, Num, Sequence, MapSequence, SkipSequence, TakeSequence, WhereSequence, List, String, StringByteSequence, StringCodePointSequence, Map, MapKeySequence, MapValueSequence, MapEntry, Range, System, Meta, M, v, f, C, w]这个输出清单包含三层信息内置核心类型Object、Class、Bool、Fiber、Fn、Null、Num、Sequence及其各种子序列、List、String、Map、Range、System等它们是隐式导入的内置模块变量显式导入的变量M来自import ./module for M和Meta来自import meta for Meta本模块的顶层变量v、f、C以及在调用之后才声明的w——这证明该列表是基于整个模块的变量表快照与声明顺序无关。注意f内部声明的局部变量g不会出现在列表中因为它不是模块级变量。这一点在官方文档中特别强调见 [doc/site/modules/meta/meta.markdown#L50]。2.3 错误行为若module不是字符串抛出运行时错误Module name must be a string.若找不到名为module的模块抛出运行时错误Could not find a module named module.。这些错误行为在 src/optional/wren_opt_meta.wren 的 Wren 层包装代码中实现static getModuleVariables(module) { if (!(module is String)) Fiber.abort(Module name must be a string.) var result getModuleVariables_(module) if (result ! null) return result Fiber.abort(Could not find a module named %(module).) }2.4 底层实现C 侧是如何工作的getModuleVariables的核心逻辑在 C 函数metaGetModuleVariablessrc/optional/wren_opt_meta.c中。它接收模块名字符串在 VM 的模块表中查找对应模块调用wrenMapGet(vm-modules, ...)查询模块若找不到返回null由 Wren 层包装代码转换为Fiber.abort错误若找到取ObjModule的variableNames变量名数组按个数创建一个等长的ObjList为避免分配字符串期间触发 GC 导致悬垂引用先以NULL_VAL填满列表元素再逐个填入module-variableNames.data[i]。这也是为什么输出顺序与模块内变量声明顺序一致列表直接来自模块编译期构建的变量名数组。2.5 测试用例佐证仓库中 test/meta/ 目录下的测试完整覆盖了该方法的三种场景test/meta/get_module_variables.wren 验证返回列表包含隐式导入的内置核心类Object、Bool、包含顶层变量包括声明于调用之后的later、不包含未知名字unknowntest/meta/get_module_variables_not_string.wren 验证传非字符串时抛错Module name must be a string.test/meta/get_module_variables_unknown_module.wren 验证未知模块名抛错Could not find a module named unknown.。三、eval编译并自动执行一段源码3.1 方法签名与语义Meta.eval(source)eval把字符串source编译成闭包并立即自动执行见 [doc/site/modules/meta/meta.markdown#L54-L57]。它等价于compile加.call()的组合但不会把闭包返回给你。3.2 示例在运行时执行多行代码官方文档的示例展示了eval的经典用法——编译一段引用外部变量的多行代码import meta for Meta var a 2 var b 3 var source var c a * b System.print(c) Meta.eval(source) // 6关键点eval编译出的代码与调用它的模块处于同一模块上下文因此可以直接访问模块中的顶层变量a、b。闭包执行后输出6。3.3 错误行为若source不是字符串抛出运行时错误Source code must be a string.若源码无法编译抛出运行时错误Could not compile source code.——但编译错误详情本身不会被打印见 [doc/site/modules/meta/meta.markdown#L60]。Wren 层的实现src/optional/wren_opt_meta.wren如下static eval(source) { if (!(source is String)) Fiber.abort(Source code must be a string.) var closure compile_(source, false, false) // TODO: Include compile errors. if (closure null) Fiber.abort(Could not compile source code.) closure.call() }注意这里调用底层compile_(source, false, false)第二个参数isExpression为false按完整源码编译而非表达式第三个参数printErrors为false不打印编译错误这与文档编译错误不打印的行为完全一致。四、compileExpression编译表达式返回闭包4.1 方法签名与语义Meta.compileExpression(expression)compileExpression把字符串expression编译为闭包并返回但不执行见 [doc/site/modules/meta/meta.markdown#L76-L80]。编译出的闭包在调用时返回该表达式的值。4.2 示例把算术表达式变成可复用闭包import meta for Meta var d 4 var e 5 var expression d * e var closure Meta.compileExpression(expression) System.print(closure.call()) // 20与eval一样表达式中可以引用调用模块的顶层变量d、e闭包调用时计算d * e并返回20。4.3 错误行为若expression不是字符串抛出运行时错误Source code must be a string.与eval不同compileExpression会打印编译错误此时返回的闭包为null但不会抛错。从 Wren 层实现src/optional/wren_opt_meta.wren可见它调用compile_(source, true, true)isExpression为true按表达式编译、printErrors为true打印编译错误static compileExpression(source) { if (!(source is String)) Fiber.abort(Source code must be a string.) return compile_(source, true, true) }五、compile编译源码返回闭包5.1 方法签名与语义Meta.compile(source)compile与compileExpression类似把字符串source编译为闭包并返回、不执行区别在于它按**完整源码语句序列**编译而非单个表达式见 [doc/site/modules/meta/meta.markdown#L98-L104]。5.2 错误行为若source不是字符串抛出运行时错误Source code must be a string.会打印编译错误此时返回的闭包为null但不会抛错。Wren 层实现src/optional/wren_opt_meta.wren调用compile_(source, false, true)static compile(source) { if (!(source is String)) Fiber.abort(Source code must be a string.) return compile_(source, false, true) }5.3 经典实战用 compile 实现枚举Enumcompile最常见的应用场景是代码生成把数据变成 Wren 源码字符串编译成真正的类。官方文档用一个完整的Enum类演示了这一模式见 [doc/site/modules/meta/meta.markdown#L106-L145]import meta for Meta /* Enum creates an enum with any number of read-only static members. Members are assigned in order an initial integer value (often 0), incremented by 1 each time. The enum has: 1. static property getters for each member, 2. a static startsFrom property, and 3. a static members property which returns a list of its members as strings. */ class Enum { // Creates a class for the Enum (with an underscore after the name to avoid duplicate definition) // and returns a reference to it. static create(name, members, startsFrom) { if (name.type ! String || name ) Fiber.abort(Name must be a non-empty string.) if (members.isEmpty) Fiber.abort(An enum must have at least one member.) if (startsFrom.type ! Num || !startsFrom.isInteger) { Fiber.abort(Must start from an integer.) } name name _ var s class %(name) {\n for (i in 0...members.count) { var m members[i] s s static %(m) { %(i startsFrom) }\n } var mems members.map { |m| \%(m)\ }.join(, ) s s static startsFrom { %(startsFrom) }\n s s static members { [%(mems)] }\n}\n s s return %(name) return Meta.compile(s).call() } } var Fruits Enum.create(Fruits, [orange, apple, banana, lemon], 0) System.print(Fruits.banana) // 2 System.print(Fruits.startsFrom) // 0 System.print(Fruits.members) // [orange, apple, banana, lemon]运行结果2 0 [orange, apple, banana, lemon]这个例子的执行流程非常清晰是理解compile价值的绝佳范本校验输入name必须是非空字符串、members非空、startsFrom必须是整数否则用Fiber.abort中止拼接源码用字符串插值动态生成一个名为Fruits_加下划线避免与类名冲突的类的完整源码为每个成员生成static orange { 0 }之类的只读 getter并附带startsFrom与members两个静态属性编译并执行Meta.compile(s)把生成的源码字符串编译成闭包.call()执行后返回类对象本身源码末尾的return Fruits_语句保证了这一点使用枚举Fruits.banana返回2index 2 startsFrom 0Fruits.startsFrom返回0Fruits.members返回成员名字符串列表。六、底层原理meta 的 C 侧实现与调用链meta模块的四个公开方法最终都调用两个foreign static方法src/optional/wren_opt_meta.wrenforeign static compile_(source, isExpression, printErrors) foreign static getModuleVariables_(module)这两个 foreign 方法由 C 侧绑定src/optional/wren_opt_meta.c解析wrenMetaBindForeignMethod检查签名compile_(_,_,_)对应metaCompilegetModuleVariables_(_)对应metaGetModuleVariables。6.1 metaCompile把字符串交给编译器metaCompilesrc/optional/wren_opt_meta.c是eval/compile/compileExpression的共同底层void metaCompile(WrenVM* vm) { const char* source wrenGetSlotString(vm, 1); bool isExpression wrenGetSlotBool(vm, 2); bool printErrors wrenGetSlotBool(vm, 3); // Look up the module surrounding the callsite. This is brittle. The -2 walks // up the callstack assuming that the meta module has one level of // indirection before hitting the users code. Any change to meta may require // this constant to be tweaked. ObjFiber* currentFiber vm-fiber; ObjFn* fn currentFiber-frames[currentFiber-numFrames - 2].closure-fn; ObjString* module fn-module-name; ObjClosure* closure wrenCompileSource(vm, module-value, source, isExpression, printErrors); ... }这段实现揭示了三个重要细节编译上下文编译时以调用meta的模块而非meta模块自身为编译单元。实现通过向上回溯两层调用栈numFrames - 2找到调用者模块。源码注释明确说明这是一种脆弱brittle的实现依赖meta模块只有一层间接调用这一假设改动meta模块可能需要同步调整该常量参数传递isExpression与printErrors两个布尔标志原样传入wrenCompileSource这正是前文所述evalfalse, false与compile/compileExpressionfalse/true, true行为差异的根源返回方式编译成功时把ObjClosure*放到 API 栈顶vm-apiStack[0]返回给 Wren 层失败时closure NULL返回null。6.2 模块加载与编译入口metaCompile调用的wrenCompileSource是 VM 内部的统一编译入口它把字符串源码与模块名交给编译器产出ObjClosure。这也解释了为什么eval的代码能访问调用模块的顶层变量——因为编译时绑定的就是调用者的模块上下文。七、四方法对比与选择建议方法输入语义是否执行返回编译错误处理Meta.eval(source)完整源码立即自动执行无返回null不打印抛Could not compile source code.Meta.compileExpression(expression)单个表达式不执行闭包调用时返回表达式值打印错误闭包为null不抛错Meta.compile(source)完整源码不执行闭包打印错误闭包为null不抛错Meta.getModuleVariables(module)模块名字符串—顶层变量名列表非字符串或模块不存在时抛错选择建议只想跑一段动态代码、不关心闭包本身 → 用eval想把表达式编译成可复用闭包如计算器、公式求值 → 用compileExpression想编译一段含类定义、语句序列的源码并拿到类或函数引用如枚举生成 → 用compile想在运行时内省模块结构、做反射式工具 → 用getModuleVariables。八、限制与注意事项可选模块开关meta默认启用但可被WREN_OPT_META 0关闭若宿主关闭了该模块import meta将失败模块名语义getModuleVariables的模块名遵循模块导入系统的命名规则例如官方示例中./get_mod_vars即当前模块的路径式名称编译错误可见性差异eval隐藏编译错误详情compile与compileExpression则打印它们——设计上eval偏向执行脚本后两者偏向开发者拿到闭包后自行处理错误与局部变量的隔离meta只能看到模块级变量函数内的局部变量不可见、也不可被动态编译代码直接引用编译上下文依赖调用栈从源码注释看当前实现通过调用栈深度-2定位调用者模块属于实现细节可能在未来的版本中调整。参考资料doc/site/modules/meta/index.markdown ——meta模块总览与启用/关闭说明doc/site/modules/meta/meta.markdown ——Meta类完整 API 文档与全部示例src/optional/wren_opt_meta.wren ——Meta类的 Wren 层实现src/optional/wren_opt_meta.c —— foreign 方法 C 层实现src/optional/wren_opt_meta.h —— 模块头文件与开关宏src/vm/wren_common.h —— 可选模块默认开启配置src/vm/wren_vm.c ——meta内置模块加载路径test/meta/ —— 模块行为测试用例赞分享编程语言语言运行时编译器【免费下载链接】wrenThe Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.项目地址https://gitcode.com/gh_mirrors/wr/wren点击查看免费下载相关推荐Candle让CNC机床控制变得直观简单的GRBL控制器Candle让CNC机床控制变得直观简单的GRBL控制器 Candle是一款基于Qt框架开发的GRBL控制器应用专为CNC机床用户设计。它将复杂的G代码控制物联网桌面应用Wren 模块体系详解核心模块与可选模块meta / random的启用机制与实战用法Wren 模块体系详解核心模块与可选模块meta / random的启用机制与实战用法 Wren 是一门轻量级、基于类并发的脚本语言它的功能被组织为 模编程语言语言运行时编译器Puerts 模块化编程指南从 Eval 到 ESM 模块与 TypeScriptPuerts 模块化编程指南从 Eval 到 ESM 模块与 TypeScript 本指南聚焦 Puerts 在 Unity 环境下的 JavaScript/游戏开发跨平台上一篇彻底解决EssentialsX的IncompatibleClassChangeError从原理到实战修复指南下一篇Deep-Live-Cam 云端免费部署没有显卡也能用一张照片实时换脸创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考