仓颉语言宏(Cangjie Macros)详细介绍及强大使用
目录
- 一、仓颉宏概述
- 1.1 宏的主要用途
- 1.2 使用宏的优势
- 二、宏的基本特性
- 2.1 用法示例
- 2.2 编译流程
- 三、宏的强大应用示例:JSON序列化
- 四、宏的工作原理
- 五、宏的高级应用场景
- 六、Json宏的深入解析
- 七、Json宏的使用示例
- 八、宏开发的最佳实践
- 九、宏的限制与注意事项
- 十、总结
- 参考链接
哈喽,大家好,我是csdn的猫哥。今天给大家分享下仓颉语言宏的介绍及宏的强大之处。宏在仓颉语言中是一种强大的元编程工具,它允许你在编译时操作和生成代码。与普通函数不同,宏不是在运行时处理值,而是在编译时处理代码本身。宏是仓颉语言中实现元编程和代码生成的重要机制,为开发者提供了在编译时操作代码的强大能力。
一、仓颉宏概述
仓颉宏是一种强大的编译时代码转换机制,它允许开发者在代码编译前对程序进行元编程操作。与传统函数不同,宏的输入输出都是代码本身,这使得仓颉宏能够实现普通函数无法完成的语法扩展和代码生成功能。
宏在编译时对输入的程序片段进行变换,生成新的程序片段,然后继续参与编译和执行。宏的主要作用是在编译阶段对代码进行元编程(metaprogramming),例如生成重复代码、实现语法糖或自定义DSL(领域特定语言)。
1.1 宏的主要用途
1.代码生成:宏可以根据模板或规则自动生成重复性代码,减少手动编写的工作量。
2.语法扩展:宏可以创建新的语法结构,使代码更简洁或更符合特定领域的表达方式。
3.编译时计算:宏可以在编译时执行计算,将结果直接嵌入到生成的代码中。
4.代码转换:宏可以分析输入代码并转换为其他形式的代码。
1.2 使用宏的优势
- 性能优化:宏展开在编译时完成,不会带来运行时开销。
- 灵活性:可以创建特定领域的语言扩展。
- 减少样板代码:自动生成重复性代码结构。
- 代码简洁:创建更表达性的API。
二、宏的基本特性
- 编译时执行:宏在编译阶段而非运行时执行。
- 代码作为数据:宏接收和返回的都是代码片段(Tokens)。
- 语法扩展:可以创建新的语法结构。
- 代码生成:自动生成重复或模板化的代码。
2.1 用法示例
以下是一个简单的宏示例,用于在调试时打印表达式及其值:
宏定义(dprint.cj):
macro package defineimport std.ast.*public macro dprint(input: Tokens): Tokens {let inputStr = input.toString()let result = quote(print($(inputStr) + " = ")println($(input)))return result
}
宏调用(main.cj):
import define.*main() {let x = 3let y = 2@dprint(x) // 打印 "x = 3"@dprint(x + y) // 打印 "x + y = 5"
}
输出结果:
x = 3
x + y = 5
代码解释:
宏定义:
macro package define:声明一个宏包。
public macro dprint(input: Tokens): Tokens:定义一个名为 dprint 的宏,接受 Tokens 类型的输入(即代码片段),并返回新的 Tokens。
quote(…):用于构造新的代码片段,$(inputStr) 和 $(input) 是插值语法,分别插入表达式字符串和原始表达式。
宏调用:
@dprint(x):调用宏,宏展开后会生成 print(“x” + " = "); println(x) 的代码。在仓颉中,宏调用使用@符号前缀。例如:
@my_macro(arg1, arg2)
这种语法明确区分了宏调用和普通函数调用,使代码更清晰易读。
2.2 编译流程
由于宏是在编译期展开的,因此需要分两步编译:
# 第一步:编译宏包
cjc macro_pkg/*.cj --compile-macro# 第二步:编译主程序
cjc main.cj -o program
三、宏的强大应用示例:JSON序列化
让我们通过一个实际的Json宏示例来展示仓颉宏的强大功能:
在仓颉中,可以通过serialization 模块 和 encoding.json 模块实现JSON数据的序列化与返序列化。对@JSON宏的实现思路实际是将重复性的代码通过宏的特性进行一层封装,然后再编译期对代码进行展开。
首先看下不使用宏情况下实现的代码:
class DataItem <: Serializable < ToDoItem > {public var title: String = "标题"public var isChecked: Bool = falsepublic func serialize(): DataModel {return DataModelStruct().add(field < String >("title", title)).add(field < Bool >("isChecked", isChecked)) }public static func deserialize(dm: DataModel): ToDoItem {let dms = match(dm) {case data: DataModelStruct => datacase _ => throw Exception("this data is not DataModelStruct")}let result = ToDoItem()result.title = String.deserialize(dms.get("title"));result.isChecked = Bool.deserialize(dms.get("isChecked"));return result}public func toJSON(): JsonValue {return serialize().toJson()}
}
通过以上代码可以看到,需要把接口继承部分以及serialize和deserialize的这部分公共代码提取到宏里面进行动态拼接,同时需要动态获取变量和变量的对应类型,写起来代码有点儿多,比较繁琐,且几乎都是重复的样板代码。
接下来猫哥将带领大家通过代码实操来讲解下如何实现一个简易的@JSON宏:
macro package ohos_app_cangjie_entry.macrosimport std.ast.*
public import encoding.json.*
public import serialization.serialization.*
public import std.collection.*public macro Json(attrs: Tokens, input: Tokens): Tokens {if (attrs.toString() == "query") {return tokenCommon(true, input)}return tokenCommon(false, input)
}public macro Json(input: Tokens): Tokens {return tokenCommon(false, input)
}
这个Json宏可以自动为类生成序列化和反序列化方法,大大简化了JSON处理的代码量。
四、宏的工作原理
- 输入处理:宏接收Tokens类型的输入,这是代码的抽象表示
- 代码分析:宏可以解析输入的代码结构(如类声明、方法等)
- 代码生成:基于输入代码生成新的代码片段
- 输出替换:生成的代码替换原始代码参与后续编译
五、宏的高级应用场景
- 领域特定语言(DSL):创建针对特定领域的专用语法
- 代码优化:在编译时进行特定优化
- 模板代码生成:自动生成重复的模式化代码
- 语法糖:创建更简洁的表达方式
- 类型安全检查:在编译时进行额外的类型检查
六、Json宏的深入解析
让我们看看Json宏的核心实现:
func tokenCommon(isQuery: Bool, input: Tokens): Tokens {var decl = ClassDecl(input) // 解析输入的类声明// 生成序列化代码var serialize = quote(DataModelStruct())for (item in decl.body.decls) {if (item is VarDecl) {// 为每个公共字段生成序列化逻辑serialize += quote(.add(field<$itemType>($(item.identifier.value), $(item.identifier))))}}// 生成反序列化代码var deserialize = quote(let dms = match (dm) {case data: DataModelStruct => datacase _ => throw Exception("this data is not DataModelStruct")}let result = $(decl.identifier)())// 生成完整的类定义return quote($modifier class $(decl.identifier) <: $extendTypes Serializable<$(decl.identifier)> {$(decl.body.decls)$(serializeToken)$(deserializeToken)public func toJson(): JsonValue {return serialize().toJson()}})
}
以上代码中使用ClassDecl进行Tokens的解析,然后通过identifier获取class的标识符,即上述所属的类名ToDoItem,接下来通过获取decl的具体的作用域内的代码内容来获取我们所需要转换的变量名以及变量类型并实现序列化与返序列化的字段映射模板拼接。
上述代码不难理解,其中主要就是有个新的东东,ClassDecl。
其中的ClassDecl是什么?
ClassDecl 是仓颉编程语言中用于表示类声明(Class Declaration)的结构或类。它通常用于解析和处理类的定义,提供对类结构的访问和操作。
ClassDecl 主要用途:
- 解析类声明:可以从源代码或令牌流中解析出类的定义
- 访问类信息:可以获取类名、成员变量、方法等类结构信息
- 代码生成:可用于生成或修改类定义的代码
在上述的代码中,ClassDecl 被用来:
- 解析输入的类声明:
var decl = ClassDecl(input) - 访问类名:
$(decl.identifier) - 访问类体中的声明:
decl.body.decls - 生成新的类定义:在最后的
quote块中使用类信息构建新的类
var decl = ClassDecl(input) // 解析输入的类声明
这行代码创建了一个 ClassDecl 对象,将输入的 Tokens 解析为类声明结构。
for (item in decl.body.decls) {if (item is VarDecl) {// 处理成员变量}
}
这里遍历类体中的所有声明,筛选出变量声明(VarDecl)进行处理。
quote($modifier class $(decl.identifier) <: $extendTypes Serializable<$(decl.identifier)> {// 使用解析出的类信息构建新类}
)
最后使用解析出的类信息(类名、成员等)生成一个新的类定义,并添加序列化功能。
ClassDecl 典型应用场景
- 代码分析工具
- 元编程和代码生成
- 编译器插件
- 序列化/反序列化框架
最后,这个宏会自动为类添加以下功能:
serialize()方法:将对象转换为DataModeldeserialize()方法:从DataModel重建对象toJson()方法:将对象转换为JsonValue- 可选地生成
toParams()方法(当使用@Json("query")时)
七、Json宏的使用示例
使用这个Json宏非常简单:
@Json
class User {public var name: Stringpublic var age: Intpublic var email: Option<String>
}// 编译后自动生成的方法可以这样使用:
let user = User(name: "Alice", age: 30, email: some("alice@example.com"))
let json = user.toJson() // 自动生成的toJson方法
let user2 = User.deserialize(json) // 自动生成的deserialize方法
八、宏开发的最佳实践
- 保持宏的简洁性:宏逻辑应该尽可能简单明确
- 良好的错误处理:对输入代码进行充分验证
- 文档完善:宏的行为应该清晰文档化
- 类型安全:尽可能在编译时捕获错误
- 性能考虑:宏不应过度增加编译时间
九、宏的限制与注意事项
- 调试困难:宏在编译时运行,调试可能比较复杂
- 编译时间:复杂的宏可能增加编译时间
- 可读性:过度使用宏可能降低代码可读性
- 学习曲线:需要理解元编程概念
十、总结
仓颉语言的宏系统提供了强大的元编程能力,能够显著减少样板代码,提高开发效率。通过Json宏的示例,我们看到了如何利用宏自动生成重复的模式化代码。
宏是仓颉语言中一项高级特性,适合解决特定类型的问题。掌握仓颉宏需要深入理解AST结构和编译器工作原理,但这种投入将换来开发效率的质的飞跃。随着仓颉生态的发展,宏将成为构建高质量基础设施的核心工具。
参考链接
官网文档 宏的介绍: https://cangjie-lang.cn/docs
