仓颉开发之——宏(18)

一 概述

  • 宏的简介
  • Tokens+quote
  • 语法节点
  • 宏实现
  • 内置编译标记

二 宏的简介

2.1 概念

  • 宏是一种特殊的函数
  • 宏的输入和输出都是程序本身
  • 在调用宏时使用 @ 加上宏的名称

2.2 示例

1-定义

1
2
3
4
5
6
7
8
9
10
11
macro package define

import std.ast.*

public macro dprint(input: Tokens): Tokens {
let inputStr = input.toString()
let result = quote(
print($(inputStr) + " = ")
println($(input)))
return result
}

2-使用

1
2
3
4
5
6
7
8
import define.*

main() {
let x = 3
let y = 2
@dprint(x)
@dprint(x + y)
}

三 Tokens+quote

3.1 Token

1
2
3
4
5
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") // 字符串字面量

说明:

  • Token为用户可操作的词法单元
  • Token 可能是标识符、字面量、关键字或运算符

3.2 Tokens

1
2
3
Tokens()   // 构造空列表
Tokens(tks: Array<Token>)
Tokens(tks: ArrayList<Token>)

说明:

  • 一个 Tokens 由多个 Token 组成的序列
  • 宏操作的基本类型是 Tokens,代表一个程序片段

3.3 quote

1
2
3
4
5
6
7
let tks = Tokens(Array<Token>([
Token(TokenKind.INTEGER_LITERAL, "1"),
Token(TokenKind.ADD),
Token(TokenKind.INTEGER_LITERAL, "2")
]))
println(tks)
tks.dump()

说明:

  • 直接构造和拼接 Tokens 会比较繁琐
  • quote 表达式来从代码模版来构造 Tokens
  • 插入的表达式 $(...)的类型需要支持被转换为 Tokens

四 语法节点

4.1 语法树

  • 代码(词法分析)=>Tokens,Tokens(语法解析)=>语法树
  • 语法树的节点可能是一个表达式、声明、类型、模式等

4.2 节点解析

  • 使用解析表达式和声明的函数
  • 使用构造函数进行解析

4.3 quote 插值语法节点

1
2
3
4
5
6
7
var binExpr1 = BinaryExpr(quote(x + y))
var binExpr2 = BinaryExpr(quote($(binExpr1) * z)) // 错误:得到 x + y * z
println("binExpr2: ${binExpr2.toTokens()}")
println("binExpr2.leftExpr: ${binExpr2.leftExpr.toTokens()}")
println("binExpr2.rightExpr: ${binExpr2.rightExpr.toTokens()}")
var binExpr3 = BinaryExpr(quote(($(binExpr1)) * z)) // 正确:得到 (x + y) * z
println("binExpr3: ${binExpr3.toTokens()}")

五 宏实现

5.1 非属性宏

1
2
3
4
5
6
7
8
9
@MacroName func name() {}        // Before a FuncDecl
@MacroName struct name {} // Before a StructDecl
@MacroName class name {} // Before a ClassDecl
@MacroName var a = 1 // Before a VarDecl
@MacroName enum e {} // Before a enum
@MacroName interface i {} // Before a InterfaceDecl
@MacroName extend e <: i {} // Before a ExtendDecl
@MacroName mut prop i: Int64 {} // Before a PropDecl
@MacroName @AnotherMacro(input) // Before a macro call

5.2 属性宏

1
2
3
4
5
6
7
8
9
10
11
12
// Macro definition with attribute
public macro Foo(attrTokens: Tokens, inputTokens: Tokens): Tokens {
return attrTokens + inputTokens // Concatenate attrTokens and inputTokens.
}
// attribute macro with parentheses
var a: Int64 = @Foo[1+](2+3)

// attribute macro without parentheses
@Foo[public]
struct Data {
var count: Int64 = 100
}

六 内置编译标记

1
2
3
4
5
6
7
8
func test1() {
let s: String = @sourceFile() // The value of `s` is the current source file name
}

func test2(n!: Int64 = @sourceLine()) { /* at line 5 */
// The default value of `n` is the source file line number of the definition of `test2`
println(n) // print 5
}

说明:

  • @sourcePackage() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的包名
  • @sourceFile() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的文件名
  • @sourceLine() 展开后是一个 Int64 类型的字面量,内容为当前宏所在的源码的代码行

七 思维导图

五 参考

  • 仓颉官方文档—宏
  • 仓颉编程语言入门教程