仓颉 std.ast 带上下文宏(Macro With Context)实战指南:assertParentContext 嵌套校验与 setItem 消息传递

📅 发布时间:2026/10/15 3:08:08
仓颉 std.ast 带上下文宏(Macro With Context)实战指南:assertParentContext 嵌套校验与 setItem 消息传递
编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载本文以仓颉标准库std.ast包中的Macro With Context带上下文宏为主题围绕官方示例 context.md 展开讲解嵌套宏场景下如何校验内层宏的父上下文以及内层宏如何通过键值对消息与外层宏通信。读完本文你将掌握assertParentContext、insideParentContext、setItem、getChildMessages与MacroMessage的完整用法并能直接编译运行文中的两套可执行示例。一、什么是带上下文宏在仓颉语言的宏机制中宏可以在编译期接收并改写Tokens代码片段。当宏调用发生嵌套一个宏调用的展开结果中包含另一个宏调用或代码中直接书写Outter(Inner ...)形式的嵌套调用时就产生了宏上下文Macro Context问题内层宏需要确认自己确实位于某个特定的外层宏调用之内从而决定是否执行、如何执行内层宏可能需要向外层宏传递编译期信息字符串、整数、布尔值甚至代码片段由外层宏在展开阶段统一收集处理。std.ast包为此提供了专门的上下文相关 API。在标准库源码中这些接口集中定义于 ffi.cj 的PART TWO: Macro with context related API区域底层通过ThreadLocalCPointerUnit类型的MACRO_OBJECT见 ffi.cj持有当前正在展开的宏对象指针并调用编译器前端的CJ_CheckParentContext、CJ_SetItemInfo、CJ_GetChildMessages等 foreign 函数见 ffi.cj完成上下文查询与消息存取。注意按官方文档 ast_package_overview.md 的说明Macro With Context 相关函数只能作为函数被直接调用不能赋值给变量也不能作为实参或返回值使用。二、场景一用 assertParentContext 校验父宏上下文2.1 宏定义第一个示例展示了如何在内层宏定义中声明调用约束。宏定义文件内容如下// 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 }这里的核心是Inner宏体内的assertParentContext(Outter)它要求Inner的每次调用都必须位于一个名为Outter的宏调用内部否则编译器在宏展开阶段会直接报错。2.2 宏调用触发错误的写法// 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. }2.3 编译与报错# 先编译宏定义文件 cjc macro_definition.cj --compile-macro # 编译使用宏的文件 cjc macro_call.cj -o demo由于Outter var a 0与Inner var b 0是两条相互独立的语句Inner与Outter之间不存在嵌套关系因此编译器报告错误提示信息为error: The macro call Inner should with the surround code contains a call Outter.2.4 源码实现原理从标准库源码 ffi.cj 可以看到assertParentContext的实现逻辑public func assertParentContext(parentMacroName: String): Unit { unsafe { if (let Some(macroObjPtr) - MACRO_OBJECT.get()) { var pStr LibC.mallocCString(parentMacroName) if (pStr.isNull()) { throw IllegalMemoryException(Failed to allocate memory in assertParentContext.) } var flag CJ_CheckParentContext(macroObjPtr, pStr, true) LibC.free(pStr) if (!flag) { throw MacroContextException(Parent macro context check failed for macro ${parentMacroName}.) } } } }要点如下通过MACRO_OBJECT.get()取出当前正在展开的宏对象若不在任何宏展开上下文中返回None则检查被跳过调用编译器前端的CJ_CheckParentContext第三个参数report为true表示需要上报编译错误检查当前调用是否位于名为parentMacroName的宏调用内检查失败时抛出MacroContextException定义于 ffi.cj其类名报告为MacroAssertParentException即上面看到的编译错误。2.5 姊妹函数 insideParentContext如果不想让检查失败直接报错而希望以布尔值的方式感知上下文可以使用insideParentContext(parentMacroName: String): Bool见 ffi.cj。它同样调用CJ_CheckParentContext但report参数传false仅返回判断结果而不抛出异常。官方示例 ast_package_funcs.md 演示了在inner宏内打印insideParentContext(outer)嵌套调用outer(inner(var a 1))时输出insideParentContext(outer): true两者对比函数行为适用场景assertParentContext(name)不满足条件时抛出编译错误强制约束内层宏必须嵌套在指定外层宏内insideParentContext(name)返回Bool不报错条件判断根据是否位于特定外层宏内走不同分支三、场景二setItem 发送 getChildMessages 接收键值对消息3.1 宏定义第二个示例演示内层宏向外层宏主动发送编译期消息// 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 }这里Inner宏做了三件事用assertParentContext(Outter)保证自身必须嵌套在Outter内用setItem(key1, value1)、setItem(key2, value2)向外层宏发送两个String类型的键值对原样返回input令牌序列。而外层宏Outter则通过getChildMessages(Inner)收集所有名为Inner的内层宏调用发送的消息对象遍历后用MacroMessage.getString(key)按 key 取值并打印。3.2 宏调用// macro_call.cj import macro_definition.* main() { Outter( Inner var cnt 0 ) println(cnt) }注意这里Inner var cnt 0被显式包裹在Outter(...)的参数列表中构成了真正的嵌套调用关系因此assertParentContext(Outter)校验通过。3.3 编译与运行# 先编译宏定义文件 cjc macro_definition.cj --compile-macro # 编译使用宏的文件 cjc macro_call.cj -o demo编译阶段宏展开时输出value1: value1 value2: value2也就是说Outter宏在展开阶段成功读取到了Inner宏通过setItem写入的key1、key2两个消息值。四、消息传递 API 全貌4.1 setItem内层宏的发送接口setItem支持四种重载全部定义于 ffi.cj并分别对应四种消息类型常量ITEM_TYPE_STRING / ITEM_TYPE_INT64 / ITEM_TYPE_BOOL / ITEM_TYPE_TOKENS见 ffi.cj函数签名发送的消息类型官方示例setItem(key: String, value: String)字符串见 ast_package_funcs.mdsetItem(key: String, value: Int64)64 位整数同上setItem(key: String, value: Bool)布尔值同上setItem(key: String, value: Tokens)代码片段词法令牌序列同上以setItem(key: String, value: Tokens)为例Tokens值会先通过toBytes()序列化再调用CJ_SetItemInfo写入宏对象见 ffi.cj这使得内层宏可以把一段代码作为消息传给外层宏实现编译期代码的传递与复用。4.2 getChildMessages外层宏的接收接口public func getChildMessages(children: String): ArrayListMacroMessage参数children待接收消息的内层宏名称返回值ArrayListMacroMessage每个元素对应一次内层宏调用发送的一组键值对。如果外层宏体内多次调用Inner比如宏展开后生成了多处Inner调用外层宏将收到多条MacroMessage这正是注释中Outter可以调用多次Inner的含义若没有任何内层宏发送消息返回空列表而非报错见 ffi.cj 的实现其中resVector为 null 时直接返回空ArrayList。4.3 MacroMessage单次调用携带的消息MacroMessage类定义于 ffi.cjAPI 文档见 ast_package_classes.md封装了一次内层宏调用发送的全部键值对提供以下读取方法方法读取类型说明getString(key: String): String字符串对应setItem(key, stringValue)getInt64(key: String): Int6464 位整数对应setItem(key, intValue)getBool(key: String): Bool布尔值对应setItem(key, boolValue)getTokens(key: String): Tokens代码片段对应setItem(key, tokensValue)hasItem(key: String): Bool是否存在该 key可用于发送前/接收后做存在性判断从源码实现看getString等读取方法在类型不匹配时会抛出Exception(Macro item ... has a different type: expected ...)在key 不存在时会抛出Exception(Macro item ... does not exist.)见 ffi.cj因此外层宏读取前最好先用hasItem确认或保证内层宏一定发送了对应 key。五、编译流程与注意事项5.1 两步编译带上下文宏遵循仓颉宏的标准两步编译流程# 第一步编译宏定义文件产出可供调用的宏 cjc macro_definition.cj --compile-macro # 第二步编译调用宏的业务文件指定输出可执行文件名 cjc macro_call.cj -o demo其中第一步的--compile-macro是关键选项第二步编译时编译器会加载已编译的宏定义并在宏展开阶段执行宏体内的assertParentContext、setItem等上下文操作。5.2 使用约束结合官方文档与源码使用带上下文宏时需要牢记只能直接调用assertParentContext、insideParentContext、setItem、getChildMessages都只能作为函数直接调用不能赋值给变量、作为实参或返回值传递嵌套校验是硬约束内层宏一旦调用assertParentContext其每次调用都必须处于指定外层宏调用之内否则编译失败而insideParentContext适合做软性判断消息按 key 索引内层宏用setItem写入的 key 在外层宏读取时大小写敏感且必须精确一致类型必须匹配写入的值的类型必须与读取方法对应混用会抛出异常无消息不等于错误getChildMessages在没有收到消息时返回空列表外层宏可以通过messages.size 0判断是否收到了内层消息上下文异常类型与上下文宏相关的运行期异常统一为MacroContextException见 ast_package_exceptions.md例如assertParentContext校验失败即抛出该异常。六、总结仓颉std.ast的 Macro With Context 机制为嵌套宏提供了一套上下文感知 消息传递的完整能力assertParentContext/insideParentContext解决我是否在某个外层宏里的查询问题setItemgetChildMessagesMacroMessage解决内层宏如何把编译期数据交给外层宏的通信问题。配合--compile-macro两步编译流程开发者可以在宏展开阶段实现带约束、可交互的代码生成逻辑。更完整的函数签名与逐接口示例可继续查阅 ast_package_funcs.md 与 ast_package_classes.md。赞分享编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载相关推荐仓颉 std.ast 宏上下文编程指南assertParentContext 嵌套校验与 setItem/getChildMessages 消息通信仓颉 std.ast 宏上下文编程指南assertParentContext 嵌套校验与 setItem/getChildMessages 消息通信 仓颉C编程语言语言运行时标准库垃圾回收并发编程仓颉 std.ast 宏上下文Macro With Context实战assertParentContext 约束检查与嵌套宏通信仓颉 std.ast 宏上下文Macro With Context实战assertParentContext 约束检查与嵌套宏通信 本文基于仓颉标准库 s人工智能AI 技能文档开发工具仓颉编程语言嵌套宏上下文通信实战Macro With Context 的断言约束与消息传递机制仓颉编程语言嵌套宏上下文通信实战Macro With Context 的断言约束与消息传递机制 在仓颉Cangjie语言的宏系统中嵌套宏调用一个宏调用文档知识库教程OpenHarmony上一篇WorkshopDL终极指南免费下载Steam创意工坊模组的完整解决方案下一篇3分钟搞定股票数据获取MOOTDX量化分析终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考