概述
宏是仓颉语言中一种强大的元编程工具,它允许开发者在编译时对代码进行变换和生成。与普通函数不同,宏的输入和输出都是程序本身,这使得宏能够实现传统函数无法完成的功能。
宏的基本概念
什么是宏
宏可以理解为一种特殊的函数:
- 普通函数在输入的值上进行计算,然后输出一个新的值
- 宏的输入和输出都是程序本身,在输入一段程序后,输出一段新的程序
为了区分宏调用和函数调用,宏调用使用 @
加上宏的名称。
宏的基本示例
让我们从一个简单的调试打印宏开始:
cangjie
// 宏定义文件:define/dprint.cj
macro package cangjie_blog.define
import std.ast.*
public macro dprint(input: Tokens): Tokens {
let inputStr = input.toString()
let result = quote(
print($(inputStr) + " = ")
println($(input)))
return result
}
cangjie
package cangjie_blog
// 主文件:main.cj
import cangjie_blog.define.dprint
main() {
let x = 3
let y = 2
@dprint(x) // 打印 "x = 3"
@dprint(x + y) // 打印 "x + y = 5"
}
编译和运行:
bash
# 编译宏定义
cjc src/define/*.cj --compile-macro
# 编译主程序
cjc src/main.cj -o main
# 运行
./main
输出结果:
ini
x = 3
x + y = 5
Tokens 类型和 quote 表达式
Token 类型
Token
是宏操作的基本单元,代表一个词法单元。每个 Token
包含类型、内容和位置信息。
cangjie
import std.ast.*
// 构造不同类型的 Token
let tk1 = Token(TokenKind.ADD) // '+' 运算符
let tk2 = Token(TokenKind.FUNC) // func 关键字
let tk3 = Token(TokenKind.IDENTIFIER, "x") // x 标识符
let tk4 = Token(TokenKind.INTEGER_LITERAL, "3") // 整数字面量
let tk5 = Token(TokenKind.STRING_LITERAL, "xyz") // 字符串字面量
Tokens 类型
Tokens
代表由多个 Token
组成的序列:
cangjie
import std.ast.*
main() {
// 构造 Tokens 的三种方式
let emptyTokens = Tokens() // 空列表
let tokens1 = Tokens([Token(TokenKind.INTEGER_LITERAL, "1")]) // 从数组构造
let tokens2 = Tokens(ArrayList<Token>()) // 从 ArrayList 构造
// Tokens 的基本操作
let tks = Tokens(
[
Token(TokenKind.INTEGER_LITERAL, "1"),
Token(TokenKind.ADD),
Token(TokenKind.INTEGER_LITERAL, "2")
]
)
println(tks.size) // 获取 Token 数量
println(tks[0].value) // 获取指定下标的 Token
println(tks.toString()) // 转换为字符串
tks.dump() // 打印调试信息
}
quote 表达式和插值
quote
表达式用于从代码模板构造 Tokens
,支持使用 $(...)
进行插值:
cangjie
package cangjie_blog
import std.ast.*
let intList: Array<Int64> = [1, 2, 3, 4, 5]
let float: Float64 = 1.0
let str: String = "Hello"
let tokens = quote(
arr = $(intList)
x = $(float)
s = $(str)
)
main() {
println(tokens)
}
输出结果:
ini
arr =[1, 2, 3, 4, 5]
x = 1.0
s = "Hello"
quote 表达式中的转义
在 quote
表达式中,某些特殊字符需要转义:
cangjie
let tks1 = quote((x)) // ok - 匹配的括号
let tks2 = quote(\() // ok - 转义的左括号
let tks3 = quote( ( \) ) ) // ok - 转义的右括号
let tks4 = quote()) // error - 不匹配的右括号
let tks5 = quote(\$(1)) // ok - 转义的 $ 符号
let tks6 = quote(\x) // error - 未知的转义字符
语法节点
节点类型
仓颉语言的语法树由各种节点组成,主要抽象类包括:
Node
:所有语法节点的父类TypeNode
:所有类型节点的父类Expr
:所有表达式节点的父类Decl
:所有声明节点的父类Pattern
:所有模式节点的父类
节点的解析
有两种方式从 Tokens
解析语法节点:
使用解析函数
cangjie
import std.ast.*
main() {
let tks1 = quote(a + b)
let tks2 = quote(u + v, x + y)
// 解析表达式
let binExpr1 = parseExpr(tks1)
let (binExpr2, mid) = parseExprFragment(tks2)
let (binExpr3, _) = parseExprFragment(tks2, startFrom: mid + 1)
println("binExpr1 = ${binExpr1.toTokens()}")
println("binExpr2 = ${binExpr2.toTokens()}, binExpr3 = ${binExpr3.toTokens()}")
}
使用构造函数
cangjie
import std.ast.*
main() {
// 直接构造特定类型的节点
let binExpr = BinaryExpr(quote(a + b))
let funcDecl = FuncDecl(quote(func f1(x: Int64) { return x + 1 }))
}
节点的组成部分
BinaryExpr 节点操作
cangjie
package cangjie_blog
import std.ast.*
main() {
let binExpr = BinaryExpr(quote(x * y))
// 修改左表达式
binExpr.leftExpr = BinaryExpr(quote(a + b))
println(binExpr.toTokens()) // 输出: (a + b) * y
// 修改运算符
binExpr.op = Token(TokenKind.ADD)
println(binExpr.toTokens()) // 输出: a + b + y
}
FuncDecl 节点操作
cangjie
package cangjie_blog
import std.ast.*
main() {
let funcDecl = FuncDecl(quote(func f1(x: Int64) { x + 1 }))
// 修改函数名
funcDecl.identifier = Token(TokenKind.IDENTIFIER, "foo")
// 修改参数名
funcDecl.funcParams[0].identifier = Token(TokenKind.IDENTIFIER, "a")
// 修改函数体中的表达式
let binExpr = (funcDecl.block.nodes[0] as BinaryExpr).getOrThrow()
binExpr.leftExpr = parseExpr(quote(a))
println(funcDecl.toTokens())
}
输出结果:
css
func foo(a: Int64) {
a + 1
}
在 quote 中插值语法节点
cangjie
package cangjie_blog
import std.ast.*
main() {
var binExpr = BinaryExpr(quote(1 + 2))
let a = quote($(binExpr)) // 完整节点插值
let b = quote($binExpr) // 简写形式
let c = quote($(binExpr.leftExpr)) // 子节点插值
println("a: ${a}")
println("b: ${b}")
println("c: ${c}")
}
输出结果:
makefile
a: 1 + 2
b: 1 + 2
c: 1
节点列表的插值
cangjie
package cangjie_blog
import std.ast.*
import std.collection.ArrayList
main() {
var incrs = ArrayList<Node>()
for (i in 1..=5) {
incrs.add(parseExpr(quote(x += $(i))))
}
var foo = quote(
func foo(n: Int64) {
let x = n
$(incrs)
x
})
println(foo)
}
输出结果:
swift
func foo(n: Int64) {
let x = n
x += 1
x += 2
x += 3
x += 4
x += 5
x
}
宏的实现
非属性宏
非属性宏只接受被转换的代码,不接受其他参数:
cangjie
macro package cangjie_blog.define
import std.ast.*
public macro MacroName(args: Tokens): Tokens {
// 宏体实现
return args
}
public macro AnotherMacro(args: Tokens): Tokens {
// 宏体实现
return args
}
宏调用格式
cangjie
@MacroName(...) // 带括号调用
@MacroName // 作用于声明时可省略括号
作用于声明的宏调用
cangjie
import cangjie_blog.define.*
@MacroName
func funcName() {} // 函数声明
@MacroName
struct StructName {} // 结构体声明
@MacroName
class ClassName {
private var v: Int64 = 2
@MacroName
mut prop i: Int64 {
get() {
10
}
set(newValue) {
v = newValue
}
} // 属性声明
} // 类声明
@MacroName
var varA = 1 // 变量声明
@MacroName
enum E {
| One
} // 枚举声明
@MacroName
interface I {} // 接口声明
@MacroName
extend E <: I {} // 扩展声明
func myFunc(@MacroName @AnotherMacro(aValue:Int64)) { // 宏调用
}
输入合法性规则
cangjie
// 非法的输入 Tokens
@MacroName(#) // 不是完整的 Token
@MacroName(`) // 不是完整的 Token
@MacroName(() // 括号不匹配
@MacroName(\[) // 不支持的转义符号
// 合法的输入 Tokens
// 合法的输入 Tokens
let q = quote(
@MacroName(#"abc"#) // 原始字符串
@MacroName(`class`) // 反引号标识符
@MacroName([) // 左方括号
@MacroName([]) // 空数组
@MacroName(\() // 转义的左括号
@MacroName(\@) // 转义的 @ 符号
)
实际示例:函数修改宏
cangjie
macro package cangjie_blog.define
import std.ast.*
public macro ModifyFunc(input: Tokens): Tokens {
println("I'm in macro body")
let funcDecl = FuncDecl(input)
return quote(
func $(funcDecl.identifier)(id: Int64) {
println("start ${id}")
$(funcDecl.block.nodes)
println("end")
})
}
cangjie
package cangjie_blog
import cangjie_blog.define.*
var counter = 0
@ModifyFunc
func myFunc() {
counter++
}
func exModifyFunc() {
println("I'm in function body")
myFunc(123)
println("myFunc called: ${counter} times")
return 0
}
main(): Int64 {
exModifyFunc()
}
宏展开后的代码:
cangjie
func myFunc(id: Int64) {
println("start ${id}")
counter++
println("end")
}
运行结果:
css
I'm in function body
start 123
end
myFunc called: 1 times
属性宏
属性宏接受额外的属性参数,提供更灵活的宏展开策略:
cangjie
macro package cangjie_blog.define
import std.ast.*
public macro Foo(attrTokens: Tokens, inputTokens: Tokens): Tokens {
return attrTokens + inputTokens // 拼接属性令牌和输入令牌
}
属性宏调用格式
cangjie
// 带括号的属性宏调用
var a: Int64 = @Foo[1+](2+3)
// 不带括号的属性宏调用
@Foo[public]
struct Data {
var count: Int64 = 100
}
属性参数合法性规则
cangjie
// 非法的属性 Tokens
@MacroName[#]() // 不是完整的 Token
@MacroName[`]() // 不是完整的 Token
@MacroName[@]() // @ 符号未转义
@MacroName[[]() // 方括号不匹配
@MacroName[\(]() // 不支持的转义符号
// 合法的属性 Tokens
let aq = quote(
@Foo[#"abc"#]() // 原始字符串
@Foo[`class`]() // 反引号标识符
@Foo[()]() // 括号对
@Foo[\@]() // 转义的 @ 符号
)
嵌套宏
宏定义中嵌套宏调用
cangjie
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro getIdent(attr: Tokens, input: Tokens): Tokens {
return quote(
let decl = (parseDecl(input) as VarDecl).getOrThrow()
let name = decl.identifier.value
let size = name.size - 1
let $(attr) = Token(TokenKind.IDENTIFIER, name[0..size])
)
}
cangjie
macro package cangjie_blog.define.pkg2
import std.ast.*
import cangjie_blog.define.pkg1.*
public macro Prop(input: Tokens): Tokens {
let v = parseDecl(input)
@getIdent[ident](input) // 嵌套宏调用
return quote(
$(input)
public prop $(ident): $(decl.declType) {
get() {
this.$(v.identifier)
}
}
)
}
cangjie
package cangjie_blog
import std.ast.*
import cangjie_blog.define.pkg2.*
class A {
@Prop
private let a_: Int64 = 1
}
main() {
let b = A()
println("${b.a}")
}
宏调用中嵌套宏调用
cangjie
// 宏包 pkg1:基础宏
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro Foo(input: Tokens): Tokens {
return input
}
public macro Bar(input: Tokens): Tokens {
return input
}
cangjie
// 宏包 pkg2:转换宏
macro package cangjie_blog.define.pkg2
import std.ast.*
public macro addToMul(inputTokens: Tokens): Tokens {
var expr: BinaryExpr = match (parseExpr(inputTokens) as BinaryExpr) {
case Some(v) => v
case None => throw Exception()
}
var op0: Expr = expr.leftExpr
var op1: Expr = expr.rightExpr
return quote(($(op0)) * ($(op1)))
}
cangjie
package cangjie_blog
import cangjie_blog.define.pkg1.*
import cangjie_blog.define.pkg2.*
@Foo
struct Data {
let a = 2
let b = @addToMul(2 + 3) // 嵌套宏调用
@Bar
public func getA(): Int64 {
return a
}
public func getB(): Int64 {
return b
}
}
main(): Int64 {
let data = Data()
var a = data.getA() // a = 2
var b = data.getB() // b = 6
println("a: ${a}, b: ${b}")
return 0
}
嵌套宏之间的消息传递
上下文检查
cangjie
public macro Outer(input: Tokens): Tokens {
return input
}
public macro Inner(input: Tokens): Tokens {
assertParentContext("Outer") // 检查是否在 Outer 宏中
return input
}
cangjie
@Outer
var a0 = 0
@Inner
var b0 = 0 // 错误:Inner 宏应该在 Outer 宏的包围代码中
消息传递
cangjie
// 外层宏:接收内层宏的消息
public macro Outer(input: Tokens): Tokens {
let messages = getChildMessages("Inner")
let getTotalFunc = quote(public func getCnt() {
)
for (m in messages) {
let identName = m.getString("identifierName")
getTotalFunc.append(Token(TokenKind.IDENTIFIER, identName))
getTotalFunc.append(quote(+))
}
getTotalFunc.append(quote(0))
getTotalFunc.append(quote(}))
let funcDecl = parseDecl(getTotalFunc)
let decl = (parseDecl(input) as ClassDecl).getOrThrow()
decl.body.decls.add(funcDecl)
return decl.toTokens()
}
// 内层宏:向外层宏发送消息
public macro Inner(input: Tokens): Tokens {
assertParentContext("Outer")
let decl = parseDecl(input)
setItem("identifierName", decl.identifier.value) // 发送消息
return input
}
cangjie
import cangjie_blog.define.pkg1.*
@Outer
class Demo {
@Inner var state = 1
@Inner var cnt = 42
}
main(): Int64 {
let d = Demo()
println("${d.getCnt()}") // 输出: 43
return 0
}
宏包定义和导入
宏包声明
宏必须定义在独立的包中,使用 macro package
声明:
cangjie
// 宏包定义
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro M(input: Tokens): Tokens {
return input
}
// 错误:宏包不允许定义外部可见的非宏定义
// public func A() {} // 编译错误
宏包的重导出
cangjie
// 宏包 A:定义基础宏
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro M1(input: Tokens): Tokens {
return input
}
cangjie
// 非宏包 B:定义普通函数
package cangjie_blog.define.pkg3
public func f1(input: Int64): Int64 {
return input
}
cangjie
// 宏包 C:重导出其他包
macro package cangjie_blog.define.pkg4
public import cangjie_blog.define.pkg1.* // 正确:宏包可以重导出宏包
public import cangjie_blog.define.pkg3.* // 正确:宏包也可以重导出非宏包
import std.ast.*
public macro M2(input: Tokens): Tokens {
return @M1(input) + Token(TokenKind.NL) + quote(f1(1))
}
cangjie
// 使用宏包
package cangjie_blog
import cangjie_blog.define.pkg4.*
main() {
@M2(let a = 2)
}
编译、报错与调试
宏的编译和使用
宏定义和宏调用必须在不同的包中,编译顺序有严格要求:
bash
# 1. 先编译宏定义
cjc src/define/pkg1/d.cj --compile-macro --output-dir ./target
# 2. 再编译使用宏的文件
cjc src/main.cj -o demo --import-path ./target --output-dir ./target
# 3. 运行可执行文件
./target/demo
并行宏展开
使用 --parallel-macro-expansion
选项启用并行宏展开:
bash
cjc src/main.cj -o demo --import-path ./target --output-dir ./target --parallel-macro-expansion
注意:如果宏函数依赖全局变量,使用并行宏展开可能存在风险。
自定义报错机制
使用 diagReport
函数提供自定义报错:
cangjie
import std.ast.*
public macro testDef(input: Tokens): Tokens {
for (i in 0..input.size) {
if (input[i].kind == IDENTIFIER) {
diagReport(DiagReportLevel.ERROR, input[i..(i + 1)],
"This expression is not allowed to contain identifier",
"Here is the illegal identifier")
}
}
return input
}
宏调试模式
使用 --debug-macro
选项查看宏展开结果:
bash
cjc --debug-macro src/main.cj --import-path ./target
这会生成 demo.cj.macrocall
文件,显示宏展开后的代码。
内置编译标记
源码位置标记
cangjie
func testBuiltInMacro() {
let l = @sourceLine() // 当前行号
let p = @sourcePackage() // 当前包名
let f = @sourceFile() // 当前文件名
println("${l} ${p} ${f}")
}
条件编译
cangjie
@When[debug]
func p(){
println("Debug mode")
}
@When[!debug]
func p(){
println("Release mode")
}
@When[test]
func t() {
println("test 模式")
}
@When[!test]
func t() {
println("非test模式")
}
@When[os == 'macOS']
func o() {
println("macOS")
}
@FastNative 标记
cangjie
@FastNative
foreign func foo(): CPointer<Int32>
@FastNative
foreign func printf(fmt: CPointer<Int32>, ...): Int32
main(): Int32 {
unsafe{
let str = foo()
printf(str)
}
}
@Frozen 标记
cangjie
package cangjie_blog
@Frozen
public func test(): Unit {
println("test ")
}
public class testClass {
@Frozen
public func testFunc(): Unit {
println("testClass testFunc")
}
@Frozen
public prop testProp: Int {
get() {
10
}
}
}
main() {
test()
let c = testClass()
c.testFunc()
println(c.testProp)
}
@Attribute 标记
cangjie
@Attribute[State] var cnt = 0 // identifier 类型属性
@Attribute["Binding"] var bcnt = 0 // string 类型属性
// 在宏中检查属性
public macro Component(input: Tokens): Tokens {
var varDecl = parseDecl(input)
if (varDecl.hasAttr("State")) {
var attrs = varDecl.getAttrs()
println(attrs[0].value)
}
return input
}
@Deprecated 标记
cangjie
@Deprecated["用boo代替", since: "1.3.4"]
func foo() {}
@Deprecated["Use Macro2", since: "1990", strict: true]
public macro Macro(input: Tokens): Tokens {
return input
}
main() {
foo() // 编译警告
}
实用案例
快速幂计算宏
cangjie
macro package cangjie_blog.define.pkg1
import std.ast.*
import std.convert.*
public macro power(attrib: Tokens, input: Tokens) {
let attribExpr = parseExpr(attrib)
if (let Some(litExpr) <- (attribExpr as LitConstExpr)) {
let lit = litExpr.literal
if (lit.kind != TokenKind.INTEGER_LITERAL) {
diagReport(DiagReportLevel.ERROR, attrib, "Attribute must be integer literal", "Expected integer literal")
}
var n = Int64.parse(lit.value)
var result = quote(var _power_vn = $(input)
)
var flag = false
while (n > 0) {
if (n % 2 == 1) {
if (!flag) {
result += quote(var _power_result = _power_vn
)
flag = true
} else {
result += quote(_power_result *= _power_vn
)
}
}
n /= 2
if (n > 0) {
result += quote(_power_vn *= _power_vn
)
}
}
result += quote(_power_result)
return result
} else {
diagReport(DiagReportLevel.ERROR, attrib, "Attribute must be integer literal", "Expected integer literal")
}
return input
}
cangjie
package cangjie_blog
import cangjie_blog.define.pkg1.*
public func power_10(n: Int64): Int64 {
@power[10](n)
}
main() {
let a = 3
println(power_10(a)) // 输出: 59049
}
Memoize 宏
cangjie
macro package cangjie_blog.define.pkg1
import std.ast.*
public import std.collection.{HashMap}
public macro Memoize(attrib: Tokens, input: Tokens) {
if (attrib.size != 1 || attrib[0].kind != TokenKind.BOOL_LITERAL) {
diagReport(DiagReportLevel.ERROR, attrib, "Attribute must be a boolean literal (true or false)",
"Expected boolean literal (true or false) here")
}
let memoized = (attrib[0].value == "true")
if (!memoized) {
return input
}
let fd = FuncDecl(input)
if (fd.funcParams.size != 1) {
diagReport(DiagReportLevel.ERROR, fd.lParen + fd.funcParams.toTokens() + fd.rParen,
"Input function to memoize should take exactly one argument", "Expect only one argument here")
}
let memoMap = Token(TokenKind.IDENTIFIER, "_memoize_" + fd.identifier.value + "_map")
let arg1 = fd.funcParams[0]
return quote(
var $(memoMap) = HashMap<$(arg1.paramType), $(fd.declType)>()
func $(fd.identifier)($(arg1)): $(fd.declType) {
if ($(memoMap).contains($(arg1.identifier))) {
return $(memoMap).get($(arg1.identifier)).getOrThrow()
}
let memoizeEvalResult = { => $(fd.block.nodes) }()
$(memoMap).add($(arg1.identifier), memoizeEvalResult)
return memoizeEvalResult
}
)
}
cangjie
package cangjie_blog
import cangjie_blog.define.pkg1.*
import std.time.DateTime
@Memoize[true]
func fib(n: Int64): Int64 {
if (n == 0 || n == 1) {
return n
}
return fib(n - 1) + fib(n - 2)
}
main() {
let start = DateTime.now()
let f35 = fib(35)
let end = DateTime.now()
println("fib(35): ${f35}")
println("execution time: ${(end - start).toMicroseconds()} us")
}
扩展的 dprint 宏
cangjie
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro dprint2(input: Tokens) {
let exprs = ArrayList<Expr>()
var index: Int64 = 0
while (true) {
let (expr, nextIndex) = parseExprFragment(input, startFrom: index)
exprs.add(expr)
if (nextIndex == input.size) {
break
}
if (input[nextIndex].kind != TokenKind.COMMA) {
diagReport(DiagReportLevel.ERROR, input[nextIndex..nextIndex+1],
"Input must be a comma-separated list of expressions",
"Expected comma")
}
index = nextIndex + 1 // 跳过逗号
}
let result = quote()
for (expr in exprs) {
result.append(quote(
print($(expr.toTokens().toString()) + " = ")
println($(expr))
))
}
return result
}
cangjie
import cangjie_blog.define.pkg1.*
main() {
let x = 3
let y = 2
@dprint2(x, y, x + y)
}
输出结果:
ini
x = 3
y = 2
x + y = 5
简单的 DSL 实现
cangjie
macro package cangjie_blog.define.pkg1
import std.ast.*
public macro linq(input: Tokens) {
let syntaxMsg = "Syntax is \"from <attrib> in <table> where <cond> select <expr>\""
if (input.size == 0 || input[0].value != "from") {
diagReport(DiagReportLevel.ERROR, input[0..1], syntaxMsg,
"Expected keyword \"from\" here.")
}
if (input.size <= 1 || input[1].kind != TokenKind.IDENTIFIER) {
diagReport(DiagReportLevel.ERROR, input[1..2], syntaxMsg,
"Expected identifier here.")
}
let attribute = input[1]
if (input.size <= 2 || input[2].value != "in") {
diagReport(DiagReportLevel.ERROR, input[2..3], syntaxMsg,
"Expected keyword \"in\" here.")
}
var index: Int64 = 3
let (table, nextIndex) = parseExprFragment(input, startFrom: index)
if (nextIndex == input.size || input[nextIndex].value != "where") {
diagReport(DiagReportLevel.ERROR, input[nextIndex..nextIndex+1], syntaxMsg,
"Expected keyword \"where\" here.")
}
index = nextIndex + 1 // 跳过where
let (cond, nextIndex2) = parseExprFragment(input, startFrom: index)
if (nextIndex2 == input.size || input[nextIndex2].value != "select") {
diagReport(DiagReportLevel.ERROR, input[nextIndex2..nextIndex2+1], syntaxMsg,
"Expected keyword \"select\" here.")
}
index = nextIndex2 + 1 // 跳过select
let (expr, nextIndex3) = parseExprFragment(input, startFrom: index)
return quote(
for ($(attribute) in $(table)) {
if ($(cond)) {
println($(expr))
}
}
)
}
cangjie
import cangjie_blog.define.pkg1.*
main() {
@linq(from x in 1..=10 where x % 2 == 1 select x * x)
}
输出结果:
1
9
25
49
81
总结
仓颉语言的宏系统提供了强大的元编程能力,通过以下核心概念:
- Tokens 和 quote 表达式:提供了操作程序代码的基本工具
- 语法节点:允许对代码结构进行深入分析和修改
- 非属性宏和属性宏:提供了两种不同的宏定义方式
- 嵌套宏:支持复杂的宏组合和消息传递
- 编译和调试工具:提供了完整的开发支持
宏系统使得仓颉语言能够:
- 在编译时进行代码生成和优化
- 实现领域特定语言(DSL)
- 提供编译时计算和验证
- 简化重复代码的编写