编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载仓颉Cangjie的宏系统支持在编译期对源码进行语法树级变换而std.ast包提供的 Macro With Context带上下文的宏能力让嵌套宏之间不仅能感知彼此的调用关系还能在展开阶段直接交换数据。本文以 stdlib/doc/libs/std/ast/ast_samples/context.md 为骨架结合std.ast的 API 文档与 ffi.cj 源码实现完整讲解assertParentContext的嵌套校验、setItem与getChildMessages的消息传递机制并给出可直接运行的编译示例与排错要点。一、背景什么是带上下文的宏Macro With Context在嵌套宏场景下一个宏调用内层宏可能出现在另一个宏调用外层宏的调用参数内部。std.ast为这类场景提供了专门的一组上下文函数用于在宏展开时获取调用上下文信息主要包括assertParentContext(String)断言当前宏调用嵌套在指定外层宏调用内否则报编译错误insideParentContext(String)以布尔值形式返回嵌套关系检查结果setItem(key, value)重载Bool/Int64/String/Tokens内层宏向外层宏发送键值对消息getChildMessages(String)外层宏获取指定内层宏发送的全部消息。这些函数有一个共同的使用限制见 ast_package_overview.md只能作为函数被直接调用不能赋值给变量不能作为实参或返回值使用。从实现上看它们依赖宏展开器在展开过程中维护的当前宏对象MACRO_OBJECT线程局部对象来工作因此脱离宏展开过程调用是没有意义的。二、嵌套宏上下文校验assertParentContext2.1 完整示例与编译命令宏定义文件macro_definition.cj// macro_definition.cj macro package macro_definition import std.ast.* public macro Outter(input: Tokens): Tokens { return input } public macro Inner(input: Tokens): Tokens { assertParentContext(Outter) return input }宏调用文件macro_call.cj// macro_call.cj package macro_calling import macro_definition.* main() { Outter var a 0 Inner var b 0 // error: The macro call Inner should with the surround code contains a call Outter. }编译命令# 先编译宏定义文件 cjc macro_definition.cj --compile-macro # 编译使用宏的文件 cjc macro_call.cj -o demoInner宏在定义时通过assertParentContext(Outter)声明调用我时我必须以Outter宏调用为外围上下文。由于示例调用中Outter与Inner是平级的两条语句、不存在嵌套关系编译期会直接报出错误error: The macro call Inner should with the surround code contains a call Outter.2.2 源码实现视角assertParentContext的实现位于 stdlib/libs/std/ast/ffi.cj第 282 行附近public func assertParentContext(parentMacroName: String): Unit { unsafe { if (let Some(macroObjPtr) - MACRO_OBJECT.get()) { var pStr LibC.mallocCString(parentMacroName) ... var flag CJ_CheckParentContext(macroObjPtr, pStr, true) ... if (!flag) { throw MacroContextException(Parent macro context check failed for macro ${parentMacroName}.) } } } }可以看到校验最终由运行时接口CJ_CheckParentContext完成它会遍历宏调用上下文链确认名为parentMacroName的外层宏调用确实包裹着当前宏。校验失败时抛出的异常类型是 MacroContextException最终由编译器以标准错误形式呈现给用户。2.3 需要条件判断时使用 insideParentContext如果不想直接报错而是想根据嵌套关系做分支处理可以使用insideParentContext(String)它返回Booltrue表示当前宏嵌套在给定的外层宏调用内实现同样位于 ffi.cj第 304 行附近其内部调用CJ_CheckParentContext(macroObjPtr, pStr, false)区别仅在于不抛出异常而是返回结果。典型用法public macro inner(input: Tokens): Tokens { if (insideParentContext(outer)) { // 处于 outer 宏内时的逻辑 } else { // 未处于 outer 宏内时的逻辑 } return input }三、内层宏向外层宏发送消息setItemassertParentContext解决的是嵌套关系校验而setItem解决的是嵌套宏之间的数据通信。3.1 完整示例宏定义文件macro_definition.cj// macro_definition.cj macro package macro_definition import std.ast.* public macro Outter(input: Tokens): Tokens { let messages getChildMessages(Inner) for (m in messages) { let value1 m.getString(key1) // get value: value1 let value2 m.getString(key2) // get value: value2 println(value1: ${value1} value2: ${value2}) } return input } public macro Inner(input: Tokens): Tokens { assertParentContext(Outter) setItem(key1, value1) setItem(key2, value2) return input }宏调用文件macro_call.cj// macro_call.cj import macro_definition.* main() { Outter( Inner var cnt 0 ) println(cnt) }编译命令# 先编译宏定义文件 cjc macro_definition.cj --compile-macro # 编译使用宏的文件 cjc macro_call.cj -o demo编译阶段输出value1: value1 value2: value2在这个例子中内层宏Inner在展开时通过setItem(key1, value1)与setItem(key2, value2)把两个键值对暂存到外层宏的上下文中随后外层宏Outter展开时调用getChildMessages(Inner)拿到Inner发送的一组信息对象每个调用Inner的位置对应一条消息再通过信息对象的getString按 key 取出对应值。3.2 setItem 的四种重载setItem支持四种值类型对应四个重载见 ast_package_funcs.md重载发送类型检索接口setItem(String, String)字符串getString(key)setItem(String, Int64)整数getInt64(key)setItem(String, Bool)布尔getBool(key)setItem(String, Tokens)词法单元序列getTokens(key)从源码看ffi.cj 第 331396 行每种重载都会把值序列化后连同类型标签交给运行时接口CJ_SetItemInfo类型标签用常量区分let ITEM_TYPE_STRING: UInt8 1 let ITEM_TYPE_INT64: UInt8 2 let ITEM_TYPE_BOOL: UInt8 3 let ITEM_TYPE_TOKENS: UInt8 4例如发送Tokens时实现会先调用value.toBytes()将词法单元序列化再以字节指针形式传递外层宏读取时再反序列化为Tokens。因此setItem不仅能传简单值还能把一段代码如quote(1 1)完整地传出去。四、外层宏接收消息getChildMessages 与 MacroMessage4.1 接收语义getChildMessages(children: String): ArrayListMacroMessage用于获取名为children的内层宏发送的全部消息。关键语义一个内层宏调用 一条MacroMessage如果Outter内调用了多次InnergetChildMessages会返回与调用次数等长的消息列表每条MacroMessage内部是一个键值对映射用getString/getInt64/getBool/getTokens按 key 取值没有消息不是错误从实现看ffi.cj 第 559 行附近当没有对应消息时返回空列表ArrayListMacroMessage()外层宏可直接遍历若按 key 取不到对应类型的信息getString等接口会抛出Exception如Macro item ${key} does not exist.或类型不匹配时提示has a different type: expected String。4.2 MacroMessage 的完整接口MacroMessage类见 ast_package_classes.md除上述四个 getter 外还提供hasItem(key: String): Bool判断该条消息中是否存在指定 key可在取值前做防御性检查。一个使用hasItem的典型防御写法参考 ast_package_exceptions.md 中的示例public macro outer(input: Tokens) { let messages getChildMessages(inner) if (messages.size 0 !messages[0].hasItem(key1)) { throw MacroContextException(No such item) } return input }4.3 多值多类型综合示例下面是在官方文档示例基础上整理的一个同时传递Int64与Bool的完整场景宏定义与宏调用分别编译// 宏定义 macro package M import std.ast.* import std.collection.ArrayList public macro inner(input: Tokens) { setItem(Int64FromInner, 100) // 发送 Int64 setItem(TrueFromInner, true) // 发送 Bool return input } public macro outer(input: Tokens) { let messages getChildMessages(inner) let msg messages[0] println(Message from inner-macro: ${msg.getInt64(Int64FromInner)}) println(Bool message: ${msg.getBool(TrueFromInner)}) return input }// 宏调用 import M.* outer(inner(var a 1)) main() { }编译运行后输出Message from inner-macro: 100 Bool message: true五、编译与调试流程上下文宏涉及两类编译过程编译宏定义使用--compile-macro选项编译宏定义文件例如cjc macro_definition.cj --compile-macro编译宏调用方直接编译使用宏的文件并输出可执行程序例如cjc macro_call.cj -o demo。如需观察宏展开过程与中间结果官方 API 文档中的示例还使用了--debug-macro选项如cjc macro_call.cj --debug-macro配合std.ast的语法树节点dump函数示例见 ast_samples/dump.md可以输出 AST 节点的完整结构便于定位上下文宏代码中Tokens变换是否正确。需要注意宏展开后的代码不允许出现包声明和包导入语句详见 parseProgram 的说明因此消息传递、上下文校验等逻辑都应封装在宏函数内部完成最终return input返回干净的可编译Tokens。六、常见错误与排查要点现象原因与处理编译报错The macro call Inner should with the surround code contains a call Outter.Inner的assertParentContext(Outter)校验失败说明调用处不存在Outter包裹检查调用代码的嵌套结构参考 context.md 第一组示例。抛出Macro item key does not exist.getString等取值接口按 key 找不到对应项先调用hasItem(key)判断或检查setItem的 key 是否拼写一致。抛出Macro item key has a different type: expected String.setItem发送的类型与取值接口不匹配setItem(String, Int64)发的值必须用getInt64取。getChildMessages返回空列表内层宏未发送消息或内层宏名与参数不一致文档明确没有消息不是错误可按空列表处理。上下文函数无法在宏外使用assertParentContext等函数依赖宏展开上下文MACRO_OBJECT只能在宏展开过程中直接调用。总结通过assertParentContext/insideParentContext与setItem/getChildMessages的组合仓颉宏作者可以在编译期实现嵌套关系约束 父子宏数据交换两类能力前者在宏定义处声明依赖的外层宏让错误在编译期提前暴露后者以内层宏调用为粒度把字符串、整数、布尔甚至整段Tokens代码安全地传递给外层宏处理。配合--compile-macro、--debug-macro与dump调试手段即可构建结构清晰、约束严谨的嵌套宏体系。相关 API 的完整签名与更多示例可继续参阅 ast_package_overview.md、ast_package_funcs.md 与源码实现 stdlib/libs/std/ast/ffi.cj。赞分享编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载相关推荐仓颉编程语言嵌套宏上下文通信实战Macro With Context 的断言约束与消息传递机制仓颉编程语言嵌套宏上下文通信实战Macro With Context 的断言约束与消息传递机制 在仓颉Cangjie语言的宏系统中嵌套宏调用一个宏调用文档知识库教程OpenHarmony仓颉 std.ast 包函数 API 全解析从词法解析到宏上下文通信仓颉 std.ast 包函数 API 全解析从词法解析到宏上下文通信 导读 std.ast 是仓颉标准库中负责 源码解析与抽象语法树AST构建 的核心包编程语言语言运行时标准库垃圾回收并发编程仓颉 std.ast 包函数 API 全解析从词法解析到宏上下文通信仓颉 std.ast 包函数 API 全解析从词法解析到宏上下文通信 std.ast 是仓颉标准库中面向编译期宏开发的语法解析包本文以 ast_packag文档知识库教程OpenHarmony上一篇Sinon.JS 完整指南JavaScript 测试的 Spies、Stubs 与 Mocks 实战与源码解析下一篇Nx 23.1.0 Gradle 迁移指南将 dev.nx.gradle.project-graph 升级到 0.1.23创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考