☰
仓颉 std.ast MacroExpandExpr 详解:宏调用表达式 AST 节点的构造与属性操作
2026/10/14 2:51:52 网站建设 项目流程
  • 人工智能
  • AI 技能
  • 文档
  • 开发工具

【免费下载链接】DocAgent

项目地址:https://gitcode.com/Cangjie-SIG/DocAgent
点击查看免费下载

MacroExpandExpr是仓颉语言标准库std.ast包中用于表示宏调用表达式节点的类。凡是出现在表达式位置的宏调用(如@testDef(1)、@M (a is Int64)),在语法树中都以MacroExpandExpr节点承载。本文基于 MacroExpandExpr_class.md 展开,系统梳理该节点的类定位、两种构造函数、8 个可变属性及各自的操作约束,并结合仓库中ast包的类定义、解析示例与宏上下文示例,说明如何解析、读写和验证宏调用表达式节点,帮助你在编写仓颉宏或做 AST 变换时准确操作这一节点。

一、类定位:宏调用在语法树中的表达形式

在std.ast的类体系中,MacroExpandExpr 的完整类声明如下(见 ast_package_classes.md):

public class MacroExpandExpr <: Expr { public init() public init(inputs: Tokens) }
  • 它继承自Expr(所有表达式节点的父类,继承自Node,见 Expr_class.md),因此属于表达式节点,可以出现在let a = @M(...)、@M(...) + 1等表达式位置。
  • 一个典型的MacroExpandExpr节点示例为@M (a is Int64),即由@操作符、宏标识符M以及括号内的输入词法单元构成。

与同类宏调用节点的区别

std.ast中与宏调用相关的节点一共有三个,它们分别对应宏调用出现的不同语法位置:

节点父类型表示的语法位置对应文档
MacroExpandExprExpr表达式位置的宏调用(如@testDef(1)、@M (a is Int64))MacroExpandExpr_class.md
MacroExpandDeclDecl声明位置的宏调用(如@Outter var a = 0、@test var x: Int64)MacroExpandDecl_class.md
MacroExpandParamFuncParam函数参数位置的宏调用(宏展开产出参数声明)MacroExpandParam_class.md

三者结构相似(都包含lParen/rParen、lSquare/rSquare、macroAttrs、macroInputs等属性),区别在于继承链与附加字段:

  • MacroExpandExpr使用identifier保存宏标识符;
  • MacroExpandDecl/MacroExpandParam使用fullIdentifier保存完整标识符,并额外提供macroInputDecl: Decl承载被宏包裹的声明节点;
  • 此外,MacroExpandExpr仅有的at属性(@操作符)是另外两个节点没有的。

两种宏调用形态

从属性设计可以看出,MacroExpandExpr需要同时表达两种宏调用形态:

  1. 普通宏调用:@M(inputs),其中@、标识符M、左括号lParen、输入macroInputs、右括号rParen分别对应一个 Token / Tokens;
  2. 属性宏调用:@Mattrs,即在普通宏调用的标识符之后增加[(lSquare)、属性宏输入(macroAttrs)、](rSquare),随后才是括号输入。

正因如此,节点中同时存在lParen与lSquare两组括号 Token、macroInputs与macroAttrs两组输入 Tokens。

二、构造函数

MacroExpandExpr提供两个重载的init构造函数(原型与说明见 ast_package_classes.md):

原型说明
()构造一个默认的MacroExpandExpr对象。
(inputs: Tokens)根据传入的词法单元集合,构造一个MacroExpandExpr对象。

使用要点:

  • 默认构造:得到一个所有字段为空的节点对象,通常用于先建节点、再逐个通过属性赋值填充内容的场景;
  • 基于 Tokens 构造:inputs是待构造节点的词法单元集合(Tokens 类型),一般来自quote(...)表达式或宏展开时收到的输入参数。

异常约束:当传入的Tokens无法构造为MacroExpandExpr节点时(例如内容不是合法的宏调用表达式语法),构造函数会抛出 ASTException。

三、成员属性详解

MacroExpandExpr的 8 个成员均为public mut prop,即可读可写。属性与说明汇总如下(完整定义见 ast_package_classes.md):

属性原型类型说明
atpublic mut prop at: TokenToken获取或设置节点中的@操作符。
identifierpublic mut prop identifier: TokenToken获取或设置宏调用节点的标识符。
lParenpublic mut prop lParen: TokenToken获取或设置宏调用的"("。
lSquarepublic mut prop lSquare: TokenToken获取或设置属性宏调用的"["。
macroAttrspublic mut prop macroAttrs: TokensTokens获取或设置属性宏调用的输入。
macroInputspublic mut prop macroInputs: TokensTokens获取或设置宏调用的输入。
rParenpublic mut prop rParen: TokenToken获取或设置宏调用的")"。
rSquarepublic mut prop rSquare: TokenToken获取或设置属性宏调用的"]"。

3.1 语法骨架属性:at、identifier

  • at保存宏调用起始处的@操作符。它是本节点区分于普通函数调用CallExpr的关键标记。设置该属性时,传入的 Token 必须是@操作符,否则抛出ASTException(见 ast_package_classes.md)。
  • identifier保存宏名称标识符,例如@testDef(1)中的testDef。注意与MacroExpandDecl/MacroExpandParam的fullIdentifier不同,表达式版本记录的是标识符 Token,对应 Token 类型。

3.2 普通宏调用的括号与输入:lParen、macroInputs、rParen

  • lParen/rParen分别对应宏调用输入的两侧圆括号(/)。设置时必须传入对应的括号 Token,否则抛出ASTException。
  • macroInputs是Tokens类型,保存括号内的完整输入词法单元序列。例如@M (a is Int64)中,macroInputs就是a is Int64对应的全部 Token。它是宏体处理的核心输入——宏定义形如macro M(input: Tokens): Tokens,input正是这里的macroInputs。

3.3 属性宏的方括号与输入:lSquare、macroAttrs、rSquare

  • lSquare/rSquare对应属性宏调用的[/]。与lParen/rParen相同,设置错误类型 Token 时会抛出ASTException。
  • macroAttrs保存方括号内的属性宏输入(Tokens类型)。属性宏调用@Mattrs的attrs部分即保存在此;普通宏调用不涉及方括号时,lSquare、macroAttrs、rSquare可以为空或 ILLEGAL 状态的占位 Token。

3.4 Token 与 Tokens 的类型说明

属性涉及的两个基础类型:

  • Token:词法单元类型,是构成仓颉源码的最小单元,包含kind: TokenKind(词法单元类型)、value: String(字面量值)、pos: Position(位置信息)等成员。@、(、)、[、]、宏标识符都各自是一个 Token。
  • Tokens:对Token序列进行封装的类型,macroInputs与macroAttrs都是该类型,支持按索引访问(如input[i],参见 report.md 中的遍历示例)。

四、实战:解析宏调用表达式并读写节点属性

结合 parse.md 与 dump.md 中演示的「quote→ 解析函数 →dump」范式,我们可以用parseExpr将一段含宏调用的代码解析为MacroExpandExpr节点,再通过属性读取宏名与输入。

import std.ast.* main() { // 表达式位置的宏调用:对应 MacroExpandExpr 节点 let input: Tokens = quote( @testDef(1 + a) ) // 解析为表达式节点,具体类型为 MacroExpandExpr let expr = parseExpr(input) var macroExpr = match (expr as MacroExpandExpr) { case Some(v) => v case None => throw Exception() } // 读取语法骨架:@ 操作符与宏标识符 println("at value is ${macroExpr.at.value}") println("identifier value is ${macroExpr.identifier.value}") // 读取宏调用的输入词法单元(括号内的内容) println("macroInputs size is ${macroExpr.macroInputs.size}") // 打印节点整体结构,便于核对各属性 macroExpr.dump() }

要点说明:

  • quote(...)把仓颉代码片段转换为 Tokens;parseExpr(input)返回一个Expr,具体类型可用as MacroExpandExpr做类型转换(此模式与 parse.md 中decl as ClassDecl的用法一致);
  • at.value、identifier.value取 Token 的字面量值(Token 的value成员见 Token_struct.md);
  • macroInputs是Tokens,其size表示输入词法单元个数;
  • 每个 AST 节点都实现了dump函数,可以实时打印节点整体结构(见 dump.md),当节点属性较多、容易混淆时,这是核对MacroExpandExpr内部字段最直接的手段。

此外,属性都是mut可写的,因此可以在宏展开或 AST 变换中对节点做改写,例如替换identifier指向另一个宏、替换macroInputs为新的词法单元序列。改写时需要注意 3.1~3.3 节提到的 Token 类型校验,写错会触发ASTException。

五、在宏展开上下文中的使用

MacroExpandExpr是宏展开机制在语法树层面的载体。std.ast提供了「Macro With Context」系列函数,让嵌套宏之间可以传递信息(见 ast_package_overview.md 与 context.md):

  • assertParentContext(String):检查当前宏调用是否嵌套在指定外层宏调用中,不符合时编译器报错;
  • insideParentContext(String):同上,但返回布尔值;
  • setItem(String, ...):内层宏向外层宏发送Bool/Int64/String/Tokens类型信息;
  • getChildMessages(String):外层宏获取内层宏发送的信息对象列表(MacroMessage),再通过getString/getInt64/getBool/getTokens/hasItem取值。

以 context.md 的第二个示例为参考:外层宏Outter与内层宏Inner在调用端以@Outter(...)包裹@Inner var cnt = 0的形式嵌套出现。其中处于表达式位置的@Outter(...)调用即对应MacroExpandExpr节点;编译时Inner通过assertParentContext("Outter")校验自己位于Outter宏调用内部,通过setItem("key1", "value1")发送数据,Outter通过getChildMessages("Inner")接收并打印:

value1: value1 value2: value2

这一机制说明:理解MacroExpandExpr各属性(特别是identifier与macroInputs),是识别「当前展开的是哪个宏、输入是什么」的基础;而assertParentContext这类上下文函数则进一步把节点间嵌套关系转化为可编程的校验与通信能力。

六、注意事项

  1. 属性赋值强校验:at必须是@,lParen/rParen必须是(/),lSquare/rSquare必须是[/],否则抛出ASTException(详见 ast_package_classes.md 中各属性的异常说明);
  2. 区分表达式 / 声明 / 参数三种宏调用节点:处理声明(如@Outter var a = 0)时使用 MacroExpandDecl,处理表达式(如@testDef(1))时才使用MacroExpandExpr,二者不可混用;
  3. macroAttrs与macroInputs的语义不同:macroAttrs对应属性宏的[...]输入,macroInputs对应普通宏调用的(...)输入,改写时不要弄反;
  4. 解析失败会抛异常:用init(Tokens)构造或调用parseExpr解析非法内容时,会抛出ASTException/ParseASTException(见 ASTException_class.md 与 ParseASTException_class.md);
  5. 结合 dump 验证:节点字段多、命名相似,实际开发时优先用dump输出节点结构(dump.md),避免凭直觉操作出错。

延伸阅读

  • 类完整定义与属性异常说明:ast_package_classes.md
  • 包级功能总览:ast_package_overview.md、readme.md
  • 宏上下文(嵌套宏通信)示例:context.md
  • AST 解析与 dump 示例:parse.md、dump.md
  • 相关节点:MacroExpandDecl_class.md、MacroExpandParam_class.md、Expr_class.md
  • 人工智能
  • AI 技能
  • 文档
  • 开发工具

【免费下载链接】DocAgent

项目地址:https://gitcode.com/Cangjie-SIG/DocAgent
点击查看免费下载

相关推荐

上一篇:WandEnhancer技术深度解析:开源增强方案如何安全解锁WeMod Pro功能
下一篇:WarcraftHelper:让经典魔兽争霸3在现代系统上重获新生的技术解决方案

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询