- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
std.ast是仓颉标准库中负责语法解析与语法树建模的包,它把仓颉源码的词法单元(Tokens)解析为抽象语法树(AST)节点对象。本指南围绕官方示例《操作 AST 对象示例》展开,讲解如何获取ClassDecl节点后对其执行增(新增成员函数)、删、改、查等结构化操作,并结合仓库源码说明quote、parseDecl、类型转换与Body.decls容器的工作机制。读完本文,你将能够在仓颉宏或源码分析工具中自由读写 AST 节点。
AST 节点模型:从 Decl 到 ClassDecl
在std.ast中,所有语法树节点都继承自Node(见 ast 包类总览)。声明类节点统一继承自public open class Decl <: Node(见 Decl 定义),类定义、接口定义、函数定义、变量定义、枚举定义、宏定义等都属于Decl节点。本文操作的ClassDecl(类定义节点)正是Decl的一个子类:
ClassDecl通过public mut prop body: Body暴露类体,通过public mut prop superTypes: ArrayList<TypeNode>、upperBound等属性描述继承关系(见 ClassDecl 定义);Body表示类、结构体、接口等由{}和内部声明节点组成的结构,其核心成员是public mut prop decls: ArrayList<Decl>,即类体内的声明节点集合(见 Body 定义)。
从源码看,ClassDecl.body实际返回其私有字段classbody_,类型为Body;而Body.decls返回ArrayList<Decl>(对应源码 decls.cj)。这正是本文示例能够对类体做"增"操作的根本原因:decls本身是可变的ArrayList,可以直接add/remove。
操作示例:向类节点新增成员函数
官方示例《操作 AST 对象示例》的完整代码如下(注意:代码是仓库中经过<!-- verify -->标记验证过的可运行示例):
import std.ast.* main() { let input: Tokens = quote( class Data { var a: Int32 init(a_: Int32) { a = a_ } } ) let decl = parseDecl(input) // 获取一个 Decl 声明节点 var classDecl = match (decl as ClassDecl) { // decl 的具体类型为 ClassDecl, 将其进行类型转化。 case Some(v) => v case None => throw Exception() } var identifier = classDecl.identifier // 为该节点增加一个成员函数用于获取a的值 var funcdecl = FuncDecl(quote(func getValue() {a})) classDecl.body.decls.add(funcdecl) println("Identifier value is ${identifier.value}") println("ClassDecl body size is ${classDecl.body.decls.size}") 0 }运行结果:
Identifier value is Data ClassDecl body size is 3第一步:用 quote 构造 Tokens
quote(...)是仓颉编译器提供的内建语法,用于把一段仓颉源码文本直接转换为Tokens类型(词法单元序列)。它支持插值操作:插值对象只要实现了ToTokens接口(func toTokens(): Tokens)即可被嵌入,Token、Tokens、各整数类型、Float64、Bool、Rune、String等类型均已实现该接口(见 totokens.cj)。因此:
let input: Tokens = quote( class Data { var a: Int32 init(a_: Int32) { a = a_ } } )等价于先对这段源码做词法分析得到Tokens。除了quote,也可以使用cangjieLex(code: String)函数把字符串转为Tokens,该函数底层通过 FFI 调用编译器词法器CJ_AST_Lex,并在失败时抛出IllegalArgumentException(见 ffi.cj)。
第二步:parseDecl 解析为 Decl 节点
parseDecl(input)将Tokens解析为一个Decl声明节点(见 parseDecl 定义)。其源码签名还带有一个命名参数astKind!: String = "",用于指定特殊声明类型(如"PrimaryCtorDecl"、"PropMemberDecl"),默认解析为普通Decl(见 parse_decl.cj)。
从实现上看,parseDecl通过 FFI 调用编译器侧的CJ_AST_ParseDecl,将结果以 flatbuffers 格式回传,再由generateDeclAST构造成仓颉侧 AST 对象;任何解析失败都会抛出ParseASTException("Failed to parse decl.")(见 parse_decl.cj)。示例中input顶层是一个类定义,因此解析结果是ClassDecl类型。
第三步:as 类型转换与安全取值
parseDecl的返回类型是父类型Decl,而示例需要访问ClassDecl特有的body、identifier等成员,因此先做类型转换:
var classDecl = match (decl as ClassDecl) { case Some(v) => v case None => throw Exception() }decl as ClassDecl是仓颉的类型检查表达式,转换成功返回Some(v),失败返回None。示例通过match分支处理None情形,保证在decl不是ClassDecl时不会盲目访问成员。随后:
var identifier = classDecl.identifieridentifier是声明节点的标识符词法单元(Token),其value字段保存标识符文本。Decl基类内部以protected var identifier_: Token保存该值(见 Decl 定义)。
第四步:新增成员函数——改 AST 的关键一步
"增"操作的核心是两步:先用quote构造出一个新的FuncDecl节点,再把它加入body.decls容器:
var funcdecl = FuncDecl(quote(func getValue() {a})) classDecl.body.decls.add(funcdecl)FuncDecl(inputs: Tokens)是FuncDecl的构造方式之一:传入一组Tokens,内部解析并构造函数定义节点。FuncDecl拥有block(函数体)、funcParams(参数列表)、declType(返回类型)等可变属性(见 FuncDecl 定义),因此不仅可以整体添加,也可以先FuncDecl()构造空节点再逐项设置属性。
classDecl.body.decls是ArrayList<Decl>,add后类体声明数从 2 变为 3。这也说明对 AST 的"增"本质上是操作普通的集合容器——这正是std.ast将 AST 节点设计为可读写(mut prop)对象带来的灵活性。
结果验证
Identifier value is Data ClassDecl body size is 3body.size为 3 对应类体中三个声明:var a: Int32(VarDecl)、init(...)(FuncDecl)、新加入的getValue()(FuncDecl)。若在示例末尾调用classDecl.dump(),还可以看到包含getValue节点的完整树形结构(dump用法见 语法树节点打印示例)。
删:移除 AST 子节点
由于Body.decls是ArrayList<Decl>,"删"同样基于集合操作。例如移除示例中新增的getValue:
classDecl.body.decls.removeAt(classDecl.body.decls.size - 1) // 或按需删除:classDecl.body.decls.remove(funcdecl)对于其它容器型属性(如ClassDecl.superTypes、FuncDecl.funcParams),同样可以add/remove来增删父类声明与函数参数。需要注意:直接操作节点属性时,std.ast会对部分词法单元属性做类型校验,例如给Body.lBrace设置非{的Token会抛出ASTException(见 Body 定义),源码中通过checkTokenType(v, LCURL)实现(见 decls.cj)。
改:修改节点属性
所有关键属性均为mut prop,可直接赋值完成"改"。例如:
// 修改类名标识符 classDecl.identifier = Token(TokenKind.IDENTIFIER, "NewData") // 修改函数体:为 getValue 换一个表达式块 funcdecl.block = Block(quote(return a))Block(inputs: Tokens)、Token(kind, value)等构造方式与FuncDecl(inputs)一致,均可从Tokens重建子节点。修改完成后同样可以用dump()或toTokens()把节点重新序列化为词法单元,验证改动是否符合预期。
查:dump 与遍历
除了直接读取identifier.value这类属性,"查"还有两个常用手段:
- dump():每个 AST 节点都实现了
dump,可打印节点整体树形结构,便于实时查看节点层级与属性(见 语法树节点打印示例); - traverse(visitor):通过继承
Visitor并重写visit函数,可自定义对某类节点的访问行为,例如收集所有未初始化变量(见 自定义访问函数遍历 AST 示例)。
从源码看,ClassDecl.traverse会先访问自身,再依次遍历通用子节点、父类型、泛型约束与类体(见 decls.cj),因此"查"既可以精确到单个属性,也可以全量遍历。
进一步阅读
- 解析示例:parseDecl / parseProgram:获取
Decl、Program节点的完整流程; - ast 包函数总览:
parseDecl、parseDeclFragment、parseExpr、parseProgram、cangjieLex、diagReport等解析与报错接口; - ast 包类总览:
Node、Decl、ClassDecl、FuncDecl、Body、Tokens的完整属性与方法; - 自定义报错接口示例:在解析
Tokens时输出与编译器一致的WARNING/ERROR提示; - Macro With Context 示例:在嵌套宏场景下用
assertParentContext、setItem、getChildMessages传递上下文信息。
结合本文的增删改查操作,配合上述解析、遍历与报错能力,即可在仓颉宏展开阶段对源码 AST 进行完整的结构化改写。
- 编程语言
- 语言运行时
- 标准库
- 垃圾回收
- 并发编程
【免费下载链接】cangjie_runtime
仓颉编程语言运行时与标准库。
相关推荐
UnityNativeCamera权限管理详解:解决Android与iOS权限申请难题
UnityNativeCamera权限管理详解:解决Android与iOS权限申请难题 UnityNativeCamera是一款强大的原生插件,专为Unity开
仓颉 std.ast 包 Constructor 节点详解:enum 构造器的 AST 表示与操作实践
仓颉 std.ast 包 Constructor 节点详解:enum 构造器的 AST 表示与操作实践 在仓颉语言的标准库 std.ast 包中,抽象语法树(A
人工智能AI 技能文档开发工具GoGoCode 节点修改操作全指南:从 attr 到 generate 的 AST 增删改查实战
GoGoCode 节点修改操作全指南:从 attr 到 generate 的 AST 增删改查实战 导读 本指南以 GoGoCode 官方规格文档 "Modif
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考