Macro Implementation
Inspect one language lane at a time so line-level text and code deltas stay readable.
Diff Lane
English
0 modified sections0 code block delta0 anchor delta
Diff Lane
中文
5 modified sections0 code block delta0 anchor delta
modified非属性宏textcode+1 line
v1.0.5
Section Text
1
非属性宏只接受被转换的代码,不接受其他参数(属性),其定义格式如下:2
3
4
宏的调用格式如下:5
6
7
宏调用使用 `()` 括起来。括号里面可以是任意合法 `Tokens`,也可以是空。8
9
当宏作用于声明时,一般可以省略括号。参考如下示例:10
11
12
对于括号里 `Tokens` 的合法性有以下特殊说明:13
14
- 输入的内容必须是由合法的 `Token` 组成的序列,类似 "#"、" \` "、"\\" 等符号单独使用都不是合法的仓颉 `Token`,不支持其作为输入值。15
16
- 输入的内容中,若存在不匹配的小括号则必须使用转义符号 "\\" 对其进行转义。17
18
- 输入的内容中,若希望 "@" 作为输入的 `Token` 则必须使用转义符号 "\\" 对其进行转义。19
20
对于输入的特殊说明,可以参考如下示例:21
22
23
宏展开过程作用于仓颉语法树,宏展开后,编译器会继续进行后续的编译过程。因此,需要注意以下规则:24
25
- 宏展开后的代码依然是合法的仓颉代码,并且宏展开后的代码不允许出现包的声明与导入语句,否则可能引发编译问题。26
- 当宏用于声明时,如果省略括号,宏的输入必须是语法合法的声明。27
28
下面是几个宏应用的典型示例。29
30
- 示例 131
32
宏定义文件 `macro_definition.cj`33
34
<!-- verify -macro6 -->35
<!-- cfg="--compile-macro" -->36
37
```cangjie38
macro package macro_definition39
40
import std.ast.*41
42
public macro testDef(input: Tokens): Tokens {43
println("I'm in macro body")44
return input45
}46
```47
48
宏调用文件 `macro_call.cj`49
50
<!-- verify -macro6 -->51
52
```cangjie53
package macro_calling54
55
import macro_definition.*56
57
main(): Int64 {58
println("I'm in function body")59
let a: Int64 = @testDef(1 + 2)60
println("a = ${a}")61
return 062
}63
```64
65
上述代码的编译过程可以参考[宏的编译和使用](./compiling_error_reporting_and_debugging.md#宏的编译和使用)。66
67
在用例中添加了打印信息,其中宏定义中的 `I'm in macro body` 将在编译 `macro_call.cj` 的期间输出。同时,宏调用点被展开,如编译如下代码:68
69
```cangjie70
let a: Int64 = @testDef(1 + 2)71
```72
73
编译器将宏返回的 `Tokens` 更新到调用点的语法树上,得到如下代码:74
75
```cangjie76
let a: Int64 = 1 + 277
```78
79
也就是说,可执行程序中的代码实际变为了:80
81
```cangjie82
main(): Int64 {83
println("I'm in function body")84
let a: Int64 = 1 + 285
println("a = ${a}")86
return 087
}88
```89
90
`a` 经过计算得到的值为 3,在打印 `a` 的值时插值为 3。至此,上述程序的运行结果为:91
92
<!-- verify -macro6 -->93
94
```text95
I'm in function body96
a = 397
```98
99
下面看一个更有意义的用宏处理函数的例子,这个宏 ModifyFunc 宏的作用是给 myFunc 增加 id 参数,并在 `counter++` 前后插入一段代码。100
101
- 示例 2102
103
宏定义文件 `macro_definition.cj`104
105
<!-- verify -macro7 -->106
<!-- cfg="--compile-macro" -->107
108
```cangjie109
// file macro_definition.cj110
macro package macro_definition111
112
import std.ast.*113
114
public macro ModifyFunc(input: Tokens): Tokens {115
println("I'm in macro body")116
let funcDecl = FuncDecl(input)117
return quote(118
func $(funcDecl.identifier)(id: Int64) {119
println("start ${id}")120
$(funcDecl.block.nodes)121
println("end")122
})123
}124
```125
126
宏调用文件 `macro_call.cj`127
128
<!-- verify -macro7 -->129
130
```cangjie131
package macro_calling132
133
import macro_definition.*134
135
var counter = 0136
137
@ModifyFunc138
func myFunc() {139
counter++140
}141
142
func exModifyFunc() {143
println("I'm in function body")144
myFunc(123)145
println("myFunc called: ${counter} times")146
return 0147
}148
149
main(): Int64 {150
exModifyFunc()151
}152
```153
154
同样的,上述两段代码分别位于不同文件中,先编译宏定义文件 `macro_definition.cj`,再编译宏调用 `macro_call.cj` 生成可执行文件。155
156
这个例子中,ModifyFunc 宏的输入是一个函数声明,因此可以省略括号:157
158
```cangjie159
@ModifyFunc160
func myFunc() {161
counter++162
}163
```164
165
经过宏展开后,得到如下代码:166
167
```cangjie168
func myFunc(id: Int64) {169
println("start ${id}")170
counter++171
println("end")172
}173
```174
175
myFunc 会在 main 中调用,它接受的实参也是在 main 中定义的,从而形成了一段合法的仓颉程序。运行时打印如下:176
177
<!-- verify -macro7 -->178
179
```text180
I'm in function body181
start 123182
end183
myFunc called: 1 times184
```Code 1 · cangjie
1
import std.ast.*2
3
public macro MacroName(args: Tokens): Tokens {4
... // Macro body5
}Code 2 · cangjie
1
@MacroName(...)Code 3 · cangjie
1
@MacroName func name() {} // Before a FuncDecl2
@MacroName struct name {} // Before a StructDecl3
@MacroName class name {} // Before a ClassDecl4
@MacroName var a = 1 // Before a VarDecl5
@MacroName enum e {} // Before a Enum6
@MacroName interface i {} // Before a InterfaceDecl7
@MacroName extend e <: i {} // Before a ExtendDecl8
@MacroName mut prop i: Int64 {} // Before a PropDecl9
@MacroName @AnotherMacro(input) // Before a macro callCode 4 · cangjie
1
// Illegal input Tokens2
@MacroName(#) // Not a whole Token3
@MacroName(`) // Not a whole Token4
@MacroName(() // ( and ) not match5
@MacroName(\[) // Escape for unsupported symbol6
7
// Legal input Tokens8
@MacroName(#"abc"#)9
@MacroName(`class`)10
@MacroName([)11
@MacroName([])12
@MacroName(\()13
@MacroName(\@)v1.1.0
Section Text
1
非属性宏只接受被转换的代码,不接受其他参数(属性),其定义格式如下:2
3
<!-- code_no_check -->4
5
6
宏的调用格式如下:7
8
<!-- code_no_check -->9
10
11
宏调用使用 `()` 括起来。括号里面可以是任意合法 `Tokens`,也可以是空。12
13
当宏作用于声明时,一般可以省略括号。参考如下示例:14
15
<!-- code_no_check -->16
17
18
对于括号里 `Tokens` 的合法性有以下特殊说明:19
20
- 输入的内容必须是由合法的 `Token` 组成的序列,类似 "#"、" \` "、"\\" 等符号单独使用都不是合法的仓颉 `Token`,不支持其作为输入值。21
22
- 输入的内容中,若存在不匹配的小括号则必须使用转义符号 "\\" 对其进行转义。23
24
- 输入的内容中,若希望 "@" 作为输入的 `Token` 则必须使用转义符号 "\\" 对其进行转义。25
26
对于输入的特殊说明,可以参考如下示例:27
28
<!-- code_no_check -->29
30
31
宏展开过程作用于仓颉语法树,宏展开后,编译器会继续进行后续的编译过程。因此,需要注意以下规则:32
33
- 宏展开后的代码依然是合法的仓颉代码,并且宏展开后的代码不允许出现包的声明与导入语句,否则可能引发编译问题。34
- 当宏用于声明时,如果省略括号,宏的输入必须是语法合法的声明。35
36
下面是几个宏应用的典型示例。37
38
- 示例 139
40
宏定义文件 `macro_definition.cj`41
42
<!-- verify -macro6 -->43
<!-- cfg="--compile-macro" -->44
45
```cangjie46
macro package macro_definition47
48
import std.ast.*49
50
public macro testDef(input: Tokens): Tokens {51
println("I'm in macro body")52
return input53
}54
```55
56
宏调用文件 `macro_call.cj`57
58
<!-- verify -macro6 -->59
60
```cangjie61
package macro_calling62
63
import macro_definition.*64
65
main(): Int64 {66
println("I'm in function body")67
let a: Int64 = @testDef(1 + 2)68
println("a = ${a}")69
return 070
}71
```72
73
上述代码的编译过程可以参考[宏的编译和使用](./compiling_error_reporting_and_debugging.md#宏的编译和使用)。74
75
在用例中添加了打印信息,其中宏定义中的 `I'm in macro body` 将在编译 `macro_call.cj` 的期间输出。同时,宏调用点被展开,如编译如下代码:76
77
<!-- code_no_check -->78
79
```cangjie80
let a: Int64 = @testDef(1 + 2)81
```82
83
编译器将宏返回的 `Tokens` 更新到调用点的语法树上,得到如下代码:84
85
<!-- code_no_check -->86
87
```cangjie88
let a: Int64 = 1 + 289
```90
91
也就是说,可执行程序中的代码实际变为了:92
93
<!-- code_no_check -->94
95
```cangjie96
main(): Int64 {97
println("I'm in function body")98
let a: Int64 = 1 + 299
println("a = ${a}")100
return 0101
}102
```103
104
`a` 经过计算得到的值为 3,在打印 `a` 的值时插值为 3。至此,上述程序的运行结果为:105
106
<!-- verify -macro6 -->107
108
```text109
I'm in function body110
a = 3111
```112
113
下面看一个更有意义的用宏处理函数的例子,这个宏 ModifyFunc 宏的作用是给 myFunc 增加 id 参数,并在 `counter++` 前后插入一段代码。114
115
- 示例 2116
117
宏定义文件 `macro_definition.cj`118
119
<!-- verify -macro7 -->120
<!-- cfg="--compile-macro" -->121
122
```cangjie123
// file macro_definition.cj124
macro package macro_definition125
126
import std.ast.*127
128
public macro ModifyFunc(input: Tokens): Tokens {129
println("I'm in macro body")130
let funcDecl = FuncDecl(input)131
return quote(132
func $(funcDecl.identifier)(id: Int64) {133
println("start ${id}")134
$(funcDecl.block.nodes)135
println("end")136
})137
}138
```139
140
宏调用文件 `macro_call.cj`141
142
<!-- verify -macro7 -->143
144
```cangjie145
package macro_calling146
147
import macro_definition.*148
149
var counter = 0150
151
@ModifyFunc152
func myFunc() {153
counter++154
}155
156
func exModifyFunc() {157
println("I'm in function body")158
myFunc(123)159
println("myFunc called: ${counter} times")160
return 0161
}162
163
main(): Int64 {164
exModifyFunc()165
}166
```167
168
同样的,上述两段代码分别位于不同文件中,先编译宏定义文件 `macro_definition.cj`,再编译宏调用 `macro_call.cj` 生成可执行文件。169
170
这个例子中,ModifyFunc 宏的输入是一个函数声明,因此可以省略括号:171
172
<!-- code_no_check -->173
174
```cangjie175
@ModifyFunc176
func myFunc() {177
counter++178
}179
```180
181
经过宏展开后,得到如下代码:182
183
<!-- code_no_check -->184
185
```cangjie186
func myFunc(id: Int64) {187
println("start ${id}")188
counter++189
println("end")190
}191
```192
193
myFunc 会在 main 中调用,它接受的实参也是在 main 中定义的,从而形成了一段合法的仓颉程序。运行时打印如下:194
195
<!-- verify -macro7 -->196
197
```text198
I'm in function body199
start 123200
end201
myFunc called: 1 times202
```Code 1 · cangjie
1
import std.ast.*2
3
public macro MacroName(args: Tokens): Tokens {4
... // Macro body5
}Code 2 · cangjie
1
@MacroName(...)Code 3 · cangjie
1
@MacroName func name() {} // Before a FuncDecl2
@MacroName struct name {} // Before a StructDecl3
@MacroName class name {} // Before a ClassDecl4
@MacroName var a = 1 // Before a VarDecl5
@MacroName enum e {} // Before a Enum6
@MacroName interface i {} // Before a InterfaceDecl7
@MacroName extend e <: i {} // Before a ExtendDecl8
class C {9
@MacroName prop i: Int64 { // Before a PropDecl10
get() { 0 }11
}12
} 13
@MacroName @AnotherMacro(input) // Before a macro callCode 4 · cangjie
1
// Illegal input Tokens2
@MacroName(#) // Not a whole Token3
@MacroName(`) // Not a whole Token4
@MacroName(() // ( and ) not match5
@MacroName(\[) // Escape for unsupported symbol6
7
// Legal input Tokens8
@MacroName(#"abc"#)9
@MacroName(`class`)10
@MacroName([)11
@MacroName([])12
@MacroName(\()13
@MacroName(\@)modified属性宏textcode+3 lines
v1.0.5
Section Text
1
和非属性宏相比,属性宏的定义会增加一个 Tokens 类型的输入,这个增加的入参可以让开发者输入额外的信息。比如开发者可能希望在不同的调用场景下使用不同的宏展开策略,则可以通过这个属性入参进行标记位设置。同时,这个属性入参也可以传入任意 Tokens,这些 Tokens 可以与被宏修饰的代码进行组合拼接等。下面是一个简单的例子:2
3
4
如上面的宏定义,属性宏的入参数量为 2,入参类型为 `Tokens`,在宏定义内,可以对 `attrTokens` 和 `inputTokens` 进行一系列的组合、拼接等变换操作,最后返回新的 `Tokens`。5
6
带属性的宏与不带属性的宏的调用类似,属性宏调用时新增的入参 attrTokens 通过 [] 传入,其调用形式为:7
8
9
- 宏 Foo 调用,当参数是 `2+3` 时,与 `[]` 内的属性 `1+` 进行拼接,经过宏展开后,得到 `var a: Int64 = 1+2+3` 。10
- 宏 Foo 调用,当参数是 struct Data 时,与 `[]` 内的属性 `public` 进行拼接,经过宏展开后,得到11
12
```cangjie13
public struct Data {14
var count: Int64 = 10015
}16
```17
18
关于属性宏,需要注意以下几点:19
20
- 带属性的宏,与不带属性的宏相比,能修饰的 AST 是相同的,可以理解为带属性的宏对可传入参数做了增强。21
22
- 属性宏小括号内的参数合法性规则与非属性宏的参数合法性规则一致。23
24
- 属性宏中括号内的参数(属性)的合法性规则有如下特殊说明:25
26
- 输入的内容必须是由合法的 `Token` 组成的序列,类似 "#"、" \` "、"\\" 等符号单独使用都不是合法的仓颉 `Token`,不支持其作为输入值。27
28
- 输入的内容中,若存在不匹配的中括号则必须使用转义符号 "\\" 对其进行转义。29
30
- 输入的内容中,若希望 "@" 作为输入的 `Token` 则必须使用转义符号 "\\" 对其进行转义。31
32
```cangjie33
// Illegal attribute Tokens34
@MacroName[#]() // Not a whole Token35
@MacroName[`]() // Not a whole Token36
@MacroName[@]() // Not escape for @37
@MacroName[[]() // [ and ] not match38
@MacroName[\(]() // Escape for unsupported symbol39
40
// Legal attribute Tokens41
@MacroName[#"abc"#]()42
@MacroName[`class`]()43
@MacroName[(]()44
@MacroName[()]()45
@MacroName[\[]()46
@MacroName[\@]()47
```48
49
- 宏的定义和调用的类型要保持一致:如果宏定义有两个入参,即为属性宏定义,调用时必须加上 `[]`,且内容可以为空;如果宏定义有一个入参,即为非属性宏定义,调用时不能使用 `[]`。Code 1 · cangjie
1
// Macro definition with attribute2
public macro Foo(attrTokens: Tokens, inputTokens: Tokens): Tokens {3
return attrTokens + inputTokens // Concatenate attrTokens and inputTokens.4
}Code 2 · cangjie
1
// attribute macro with parentheses2
var a: Int64 = @Foo[1+](2+3)3
4
// attribute macro without parentheses5
@Foo[public]6
struct Data {7
var count: Int64 = 1008
}v1.1.0
Section Text
1
和非属性宏相比,属性宏的定义会增加一个 Tokens 类型的输入,这个增加的入参可以让开发者输入额外的信息。比如开发者可能希望在不同的调用场景下使用不同的宏展开策略,则可以通过这个属性入参进行标记位设置。同时,这个属性入参也可以传入任意 Tokens,这些 Tokens 可以与被宏修饰的代码进行组合拼接等。下面是一个简单的例子:2
3
<!-- run -macro72 -->4
<!-- cfg="--compile-macro" -->5
6
7
如上面的宏定义,属性宏的入参数量为 2,入参类型为 `Tokens`,在宏定义内,可以对 `attrTokens` 和 `inputTokens` 进行一系列的组合、拼接等变换操作,最后返回新的 `Tokens`。8
9
带属性的宏与不带属性的宏的调用类似,属性宏调用时新增的入参 attrTokens 通过 [] 传入,其调用形式为:10
11
<!-- run -macro72 -->12
13
14
- 宏 Foo 调用,当参数是 `2+3` 时,与 `[]` 内的属性 `1+` 进行拼接,经过宏展开后,得到 `var a: Int64 = 1+2+3` 。15
- 宏 Foo 调用,当参数是 struct Data 时,与 `[]` 内的属性 `public` 进行拼接,经过宏展开后,得到16
17
<!-- code_no_check -->18
19
```cangjie20
public struct Data {21
var count: Int64 = 10022
}23
```24
25
关于属性宏,需要注意以下几点:26
27
- 带属性的宏,与不带属性的宏相比,能修饰的 AST 是相同的,可以理解为带属性的宏对可传入参数做了增强。28
29
- 属性宏小括号内的参数合法性规则与非属性宏的参数合法性规则一致。30
31
- 属性宏中括号内的参数(属性)的合法性规则有如下特殊说明:32
33
- 输入的内容必须是由合法的 `Token` 组成的序列,类似 "#"、" \` "、"\\" 等符号单独使用都不是合法的仓颉 `Token`,不支持其作为输入值。34
35
- 输入的内容中,若存在不匹配的中括号则必须使用转义符号 "\\" 对其进行转义。36
37
- 输入的内容中,若希望 "@" 作为输入的 `Token` 则必须使用转义符号 "\\" 对其进行转义。38
39
<!-- code_no_check -->40
41
```cangjie42
// Illegal attribute Tokens43
@MacroName[#]() // Not a whole Token44
@MacroName[`]() // Not a whole Token45
@MacroName[@]() // Not escape for @46
@MacroName[[]() // [ and ] not match47
@MacroName[\(]() // Escape for unsupported symbol48
49
// Legal attribute Tokens50
@MacroName[#"abc"#]()51
@MacroName[`class`]()52
@MacroName[(]()53
@MacroName[()]()54
@MacroName[\[]()55
@MacroName[\@]()56
```57
58
- 宏的定义和调用的类型要保持一致:如果宏定义有两个入参,即为属性宏定义,调用时必须加上 `[]`,且内容可以为空;如果宏定义有一个入参,即为非属性宏定义,调用时不能使用 `[]`。Code 1 · cangjie
1
macro package define2
3
// Macro definition with attribute4
public macro Foo(attrTokens: Tokens, inputTokens: Tokens): Tokens {5
return attrTokens + inputTokens // Concatenate attrTokens and inputTokens.6
}Code 2 · cangjie
1
import define.Foo2
3
// attribute macro with parentheses4
var a: Int64 = @Foo[1+](2+3)5
6
// attribute macro without parentheses7
@Foo[public]8
struct Data {9
var count: Int64 = 10010
}11
12
main() {}modified宏定义中嵌套宏调用text+1 line
v1.0.5
Section Text
1
下面是一个宏定义中包含其他宏调用的例子。2
3
宏包 `pkg1` 中定义 `getIdent` 宏:4
5
<!-- compile -macro8 -->6
<!-- cfg="--compile-macro" -->7
8
9
宏包 `pkg2` 中定义 `Prop` 宏,其中嵌套了 `getIdent` 宏的调用:10
11
<!-- compile -macro8 -->12
<!-- cfg="--compile-macro" -->13
14
15
宏调用包 `pkg3` 中调用 `Prop` 宏:16
17
<!-- compile -macro8 -->18
19
20
注意,按照宏定义必须比宏调用点先编译的约束,上述 3 个文件的编译顺序必须是:pkg1 -> pkg2 -> pkg3。pkg2 中的 `Prop` 宏定义:21
22
23
会先被展开成如下代码,再进行编译。Code 1 · cangjie
1
macro package pkg12
3
import std.ast.*4
5
public macro getIdent(attr:Tokens, input:Tokens):Tokens {6
return quote(7
let decl = (parseDecl(input) as VarDecl).getOrThrow()8
let name = decl.identifier.value9
let size = name.size - 110
let $(attr) = Token(TokenKind.IDENTIFIER, name[0..size])11
)12
}Code 2 · cangjie
1
macro package pkg22
3
import std.ast.*4
import pkg1.*5
6
public macro Prop(input:Tokens):Tokens {7
let v = parseDecl(input)8
@getIdent[ident](input)9
return quote(10
$(input)11
public prop $(ident): $(decl.declType) {12
get() {13
this.$(v.identifier)14
}15
}16
)17
}Code 3 · cangjie
1
package pkg32
3
import pkg2.*4
class A {5
@Prop6
private let a_: Int64 = 17
}8
9
main() {10
let b = A()11
println("${b.a}")12
}Code 4 · cangjie
1
public macro Prop(input:Tokens):Tokens {2
let v = parseDecl(input)3
@getIdent[ident](input)4
return quote(5
$(input)6
public prop $(ident): $(decl.declType) {7
get() {8
this.$(v.identifier)9
}10
}11
)12
}Code 5 · cangjie
1
public macro Prop(input: Tokens): Tokens {2
let v = parseDecl(input)3
4
let decl = (parseDecl(input) as VarDecl).getOrThrow()5
let name = decl.identifier.value6
let size = name.size - 17
let ident = Token(TokenKind.IDENTIFIER, name[0 .. size])8
9
return quote(10
$(input)11
public prop $(ident): $(decl.declType) {12
get() {13
this.$(v.identifier)14
}15
}16
)17
}v1.1.0
Section Text
1
下面是一个宏定义中包含其他宏调用的例子。2
3
宏包 `pkg1` 中定义 `getIdent` 宏:4
5
<!-- compile -macro8 -->6
<!-- cfg="--compile-macro" -->7
8
9
宏包 `pkg2` 中定义 `Prop` 宏,其中嵌套了 `getIdent` 宏的调用:10
11
<!-- compile -macro8 -->12
<!-- cfg="--compile-macro" -->13
14
15
宏调用包 `pkg3` 中调用 `Prop` 宏:16
17
<!-- compile -macro8 -->18
19
20
注意,按照宏定义必须比宏调用点先编译的约束,上述 3 个文件的编译顺序必须是:pkg1 -> pkg2 -> pkg3。pkg2 中的 `Prop` 宏定义:21
22
<!-- code_no_check -->23
24
25
会先被展开成如下代码,再进行编译。26
27
<!-- code_no_check -->Code 1 · cangjie
1
macro package pkg12
3
import std.ast.*4
5
public macro getIdent(attr:Tokens, input:Tokens):Tokens {6
return quote(7
let decl = (parseDecl(input) as VarDecl).getOrThrow()8
let name = decl.identifier.value9
let size = name.size - 110
let $(attr) = Token(TokenKind.IDENTIFIER, name[0..size])11
)12
}Code 2 · cangjie
1
macro package pkg22
3
import std.ast.*4
import pkg1.*5
6
public macro Prop(input:Tokens):Tokens {7
let v = parseDecl(input)8
@getIdent[ident](input)9
return quote(10
$(input)11
public prop $(ident): $(decl.declType) {12
get() {13
this.$(v.identifier)14
}15
}16
)17
}Code 3 · cangjie
1
package pkg32
3
import pkg2.*4
class A {5
@Prop6
private let a_: Int64 = 17
}8
9
main() {10
let b = A()11
println("${b.a}")12
}Code 4 · cangjie
1
public macro Prop(input:Tokens):Tokens {2
let v = parseDecl(input)3
@getIdent[ident](input)4
return quote(5
$(input)6
public prop $(ident): $(decl.declType) {7
get() {8
this.$(v.identifier)9
}10
}11
)12
}Code 5 · cangjie
1
public macro Prop(input: Tokens): Tokens {2
let v = parseDecl(input)3
4
let decl = (parseDecl(input) as VarDecl).getOrThrow()5
let name = decl.identifier.value6
let size = name.size - 17
let ident = Token(TokenKind.IDENTIFIER, name[0 .. size])8
9
return quote(10
$(input)11
public prop $(ident): $(decl.declType) {12
get() {13
this.$(v.identifier)14
}15
}16
)17
}modified宏调用中嵌套宏调用text+1 line
v1.0.5
Section Text
1
嵌套宏的常见场景,是宏修饰的代码块中,出现了宏调用。一个具体的例子如下:2
3
`pkg1` 包中定义 `Foo` 和 `Bar` 宏:4
5
<!-- run -macro9 -->6
<!-- cfg="--compile-macro" -->7
8
9
`pkg2` 包中定义 `addToMul` 宏:10
11
<!-- run -macro9 -->12
<!-- cfg="--compile-macro" -->13
14
15
`pkg3` 包中使用上面定义的三个宏:16
17
<!-- run -macro9 -->18
19
20
如上代码所示,宏 `Foo` 修饰了 `struct Data`,而在 `struct Data` 内,出现了宏调用 `addToMul` 和 `Bar`。这种嵌套场景下,代码变换的规则是:将嵌套内层的宏(`addToMul` 和 `Bar`)展开后,再去展开外层的宏(`Foo`)。允许出现多层宏嵌套,代码变换的规则总是由内向外去依次展开宏。21
22
嵌套宏可以出现在带括号和不带括号的宏调用中,二者可以组合,但开发者需要保证没有歧义,且明确宏的展开顺序:Code 1 · cangjie
1
macro package pkg12
3
import std.ast.*4
5
public macro Foo(input: Tokens): Tokens {6
return input7
}8
9
public macro Bar(input: Tokens): Tokens {10
return input11
}Code 2 · cangjie
1
macro package pkg22
3
import std.ast.*4
5
public macro addToMul(inputTokens: Tokens): Tokens {6
var expr: BinaryExpr = match (parseExpr(inputTokens) as BinaryExpr) {7
case Some(v) => v8
case None => throw Exception()9
}10
var op0: Expr = expr.leftExpr11
var op1: Expr = expr.rightExpr12
return quote(($(op0)) * ($(op1)))13
}Code 3 · cangjie
1
package pkg32
3
import pkg1.*4
import pkg2.*5
@Foo6
struct Data {7
let a = 28
let b = @addToMul(2+3)9
10
@Bar11
public func getA() {12
return a13
}14
15
public func getB() {16
return b17
}18
}19
20
main(): Int64 {21
let data = Data()22
var a = data.getA() // a = 223
var b = data.getB() // b = 624
println("a: ${a}, b: ${b}")25
return 026
}Code 4 · cangjie
1
var a = @foo(@foo1(2 * 3)+@foo2(1 + 3)) // foo1, foo2 have to be defined.2
3
@Foo1 // Foo2 expands first, then Foo1 expands.4
@Foo2[attr: struct] // Attribute macro can be used in nested macro.5
struct Data{6
@Foo3 @Foo4[123] var a = @bar1(@bar2(2 + 3) + 3) // bar2, bar1, Foo4, Foo3 expands in order.7
public func getA() {8
return @foo(a + 2)9
}10
}v1.1.0
Section Text
1
嵌套宏的常见场景,是宏修饰的代码块中,出现了宏调用。一个具体的例子如下:2
3
`pkg1` 包中定义 `Foo` 和 `Bar` 宏:4
5
<!-- run -macro9 -->6
<!-- cfg="--compile-macro" -->7
8
9
`pkg2` 包中定义 `addToMul` 宏:10
11
<!-- run -macro9 -->12
<!-- cfg="--compile-macro" -->13
14
15
`pkg3` 包中使用上面定义的三个宏:16
17
<!-- run -macro9 -->18
19
20
如上代码所示,宏 `Foo` 修饰了 `struct Data`,而在 `struct Data` 内,出现了宏调用 `addToMul` 和 `Bar`。这种嵌套场景下,代码变换的规则是:将嵌套内层的宏(`addToMul` 和 `Bar`)展开后,再去展开外层的宏(`Foo`)。允许出现多层宏嵌套,代码变换的规则总是由内向外去依次展开宏。21
22
嵌套宏可以出现在带括号和不带括号的宏调用中,二者可以组合,但开发者需要保证没有歧义,且明确宏的展开顺序:23
24
<!-- code_no_check -->Code 1 · cangjie
1
macro package pkg12
3
import std.ast.*4
5
public macro Foo(input: Tokens): Tokens {6
return input7
}8
9
public macro Bar(input: Tokens): Tokens {10
return input11
}Code 2 · cangjie
1
macro package pkg22
3
import std.ast.*4
5
public macro addToMul(inputTokens: Tokens): Tokens {6
var expr: BinaryExpr = match (parseExpr(inputTokens) as BinaryExpr) {7
case Some(v) => v8
case None => throw Exception()9
}10
var op0: Expr = expr.leftExpr11
var op1: Expr = expr.rightExpr12
return quote(($(op0)) * ($(op1)))13
}Code 3 · cangjie
1
package pkg32
3
import pkg1.*4
import pkg2.*5
@Foo6
struct Data {7
let a = 28
let b = @addToMul(2+3)9
10
@Bar11
public func getA() {12
return a13
}14
15
public func getB() {16
return b17
}18
}19
20
main(): Int64 {21
let data = Data()22
var a = data.getA() // a = 223
var b = data.getB() // b = 624
println("a: ${a}, b: ${b}")25
return 026
}Code 4 · cangjie
1
var a = @foo(@foo1(2 * 3)+@foo2(1 + 3)) // foo1, foo2 have to be defined.2
3
@Foo1 // Foo2 expands first, then Foo1 expands.4
@Foo2[attr: struct] // Attribute macro can be used in nested macro.5
struct Data{6
@Foo3 @Foo4[123] var a = @bar1(@bar2(2 + 3) + 3) // bar2, bar1, Foo4, Foo3 expands in order.7
public func getA() {8
return @foo(a + 2)9
}10
}modified嵌套宏之间的消息传递textcode+3 lines, -1 line
v1.0.5
Section Text
1
这里指的是宏调用的嵌套。2
3
内层宏可以调用库函数 `assertParentContext` 来保证内层宏调用一定嵌套在特定的外层宏调用中。如果内层宏调用这个函数时没有嵌套在给定的外层宏调用中,该函数将抛出一个错误。库函数 `InsideParentContext` 同样用于检查内层宏调用是否嵌套在特定的外层宏调用中,该函数返回一个布尔值。下面是一个简单的例子。4
5
宏定义如下:6
7
8
宏调用如下:9
10
11
如上代码所示,`Inner` 宏在定义时使用了 `assertParentContext` 函数用于检查其在调用阶段是否位于 `Outer` 宏中,在代码示例的宏调用场景下,由于 `Outer` 和 `Inner` 在调用时不存在这样的嵌套关系,因此编译器将报告一个错误。12
13
内层宏也可以通过发送键/值对的方式与外层宏通信。当内层宏执行时,通过调用标准库函数 `setItem` 向外层宏发送信息;随后,当外层宏执行时,调用标准库函数 `getChildMessages` 接收每一个内层宏发送的信息(一组键/值对映射)。下面是一个简单的例子。14
15
宏定义如下:16
17
<!-- run -macro10 -->18
<!-- cfg="--compile-macro" -->19
20
21
宏调用如下:22
23
<!-- run -macro11 -->24
<!-- cfg="--compile-macro" -->25
26
27
在上面的代码中,`Outer` 接收两个 `Inner` 宏发送来的变量名,自动为类添加如下内容:28
29
30
具体流程为:内层宏 `Inner` 通过 `setItem` 向外层宏发送信息;`Outer` 宏通过 `getChildMessages` 函数接收到 `Inner` 发送的一组信息对象(`Outer` 中可以调用多次 `Inner`);最后通过该信息对象的 `getString` 函数接收对应的值。Code 1 · cangjie
1
public macro Outer(input: Tokens): Tokens {2
return input3
}4
5
public macro Inner(input: Tokens): Tokens {6
assertParentContext("Outer")7
return input8
}Code 2 · cangjie
1
@Outer var a = 02
@Inner var b = 0 // Error, The macro call 'Inner' should with the surround code contains a call 'Outer'.Code 3 · cangjie
1
macro package define2
3
import std.ast.*4
5
public macro Outer(input: Tokens): Tokens {6
let messages = getChildMessages("Inner")7
8
let getTotalFunc = quote(public func getCnt() {9
)10
for (m in messages) {11
let identName = m.getString("identifierName")12
// let value = m.getString("key") // 接收多组消息13
getTotalFunc.append(Token(TokenKind.IDENTIFIER, identName))14
getTotalFunc.append(quote(+))15
}16
getTotalFunc.append(quote(0))17
getTotalFunc.append(quote(}))18
let funcDecl = parseDecl(getTotalFunc)19
20
let decl = (parseDecl(input) as ClassDecl).getOrThrow()21
decl.body.decls.add(funcDecl)22
return decl.toTokens()23
24
}25
26
public macro Inner(input: Tokens): Tokens {27
assertParentContext("Outer")28
let decl = parseDecl(input)29
setItem("identifierName", decl.identifier.value)30
// setItem("key", "value") // 可以通过不同的key值传递多组消息31
return input32
}Code 4 · cangjie
1
import define.*2
3
@Outer4
class Demo {5
@Inner var state = 16
@Inner var cnt = 427
}8
9
main(): Int64 {10
let d = Demo()11
println("${d.getCnt()}")12
return 013
}Code 5 · cangjie
1
public func getCnt() {2
state + cnt + 03
}v1.1.0
Section Text
1
这里指的是宏调用的嵌套。2
3
内层宏可以调用库函数 `assertParentContext` 来保证内层宏调用一定嵌套在特定的外层宏调用中。如果内层宏调用这个函数时没有嵌套在给定的外层宏调用中,该函数将抛出一个错误。库函数 `InsideParentContext` 同样用于检查内层宏调用是否嵌套在特定的外层宏调用中,该函数返回一个布尔值。下面是一个简单的例子。4
5
宏定义如下:6
7
<!-- compile.error -macro92 -->8
<!-- cfg="--compile-macro" -->9
10
11
宏调用如下:12
13
<!-- compile.error -macro92 -->14
15
16
如上代码所示,`Inner` 宏在定义时使用了 `assertParentContext` 函数用于检查其在调用阶段是否位于 `Outer` 宏中,在代码示例的宏调用场景下,由于 `Outer` 和 `Inner` 在调用时不存在这样的嵌套关系,因此编译器将报告一个错误。17
18
内层宏也可以通过发送键/值对的方式与外层宏通信。当内层宏执行时,通过调用标准库函数 `setItem` 向外层宏发送信息;随后,当外层宏执行时,调用标准库函数 `getChildMessages` 接收每一个内层宏发送的信息(一组键/值对映射)。下面是一个简单的例子。19
20
宏定义如下:21
22
<!-- run -macro10 -->23
<!-- cfg="--compile-macro" -->24
25
26
宏调用如下:27
28
<!-- run -macro10 -->29
<!-- cfg="--debug-macro" -->30
31
32
在上面的代码中,`Outer` 接收两个 `Inner` 宏发送来的变量名,自动为类添加如下内容:33
34
<!-- code_no_check -->35
36
37
具体流程为:内层宏 `Inner` 通过 `setItem` 向外层宏发送信息;`Outer` 宏通过 `getChildMessages` 函数接收到 `Inner` 发送的一组信息对象(`Outer` 中可以调用多次 `Inner`);最后通过该信息对象的 `getString` 函数接收对应的值。Code 1 · cangjie
1
macro package define2
3
import std.ast.*4
5
public macro Outer(input: Tokens): Tokens {6
return input7
}8
9
public macro Inner(input: Tokens): Tokens {10
assertParentContext("Outer")11
return input12
}Code 2 · cangjie
1
import define.*2
3
@Outer4
var a = 05
6
@Inner7
var b = 0 // Error, The macro call 'Inner' should with the surround code contains a call 'Outer'.Code 3 · cangjie
1
macro package define2
3
import std.ast.*4
5
public macro Outer(input: Tokens): Tokens {6
let messages = getChildMessages("Inner")7
8
let getTotalFunc = quote(public func getCnt() {9
)10
for (m in messages) {11
let identName = m.getString("identifierName")12
// let value = m.getString("key") // 接收多组消息13
getTotalFunc.append(Token(TokenKind.IDENTIFIER, identName))14
getTotalFunc.append(quote(+))15
}16
getTotalFunc.append(quote(0))17
getTotalFunc.append(quote(}))18
let funcDecl = parseDecl(getTotalFunc)19
20
let decl = (parseDecl(input) as ClassDecl).getOrThrow()21
decl.body.decls.add(funcDecl)22
return decl.toTokens()23
24
}25
26
public macro Inner(input: Tokens): Tokens {27
assertParentContext("Outer")28
let decl = parseDecl(input)29
setItem("identifierName", decl.identifier.value)30
// setItem("key", "value") // 可以通过不同的key值传递多组消息31
return input32
}Code 4 · cangjie
1
import define.*2
3
@Outer4
class Demo {5
@Inner var state = 16
@Inner var cnt = 427
}8
9
main(): Int64 {10
let d = Demo()11
println("${d.getCnt()}")12
return 013
}Code 5 · cangjie
1
public func getCnt() {2
state + cnt + 03
}