重学仓颉-16宏系统完全指南

概述

宏是仓颉语言中一种强大的元编程工具,它允许开发者在编译时对代码进行变换和生成。与普通函数不同,宏的输入和输出都是程序本身,这使得宏能够实现传统函数无法完成的功能。

宏的基本概念

什么是宏

宏可以理解为一种特殊的函数:

  • 普通函数在输入的值上进行计算,然后输出一个新的值
  • 宏的输入和输出都是程序本身,在输入一段程序后,输出一段新的程序

为了区分宏调用和函数调用,宏调用使用 @ 加上宏的名称。

宏的基本示例

让我们从一个简单的调试打印宏开始:

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

总结

仓颉语言的宏系统提供了强大的元编程能力,通过以下核心概念:

  1. Tokens 和 quote 表达式:提供了操作程序代码的基本工具
  2. 语法节点:允许对代码结构进行深入分析和修改
  3. 非属性宏和属性宏:提供了两种不同的宏定义方式
  4. 嵌套宏:支持复杂的宏组合和消息传递
  5. 编译和调试工具:提供了完整的开发支持

宏系统使得仓颉语言能够:

  • 在编译时进行代码生成和优化
  • 实现领域特定语言(DSL)
  • 提供编译时计算和验证
  • 简化重复代码的编写

参考资料

相关推荐
鸿蒙小灰7 小时前
鸿蒙卡片常见问题与最佳实践
harmonyos
冯志浩9 小时前
Harmony Next - 手势的使用(二)
harmonyos·掘金·金石计划
爱笑的眼睛1110 小时前
HarmonyOS应用开发:深入探索Stage模型与ArkUI声明式开发
华为·harmonyos
HarderCoder12 小时前
重学仓颉-15网络编程完全指南
harmonyos
安卓开发者14 小时前
鸿蒙Next媒体展示组件实战:Video与动态布局全解析
华为·harmonyos·媒体
HarderCoder14 小时前
重学仓颉-14I/O 系统完全指南
harmonyos
森之鸟15 小时前
开发中使用——鸿蒙CoreSpeechKit语音识别
华为·语音识别·harmonyos
爱笑的眼睛1117 小时前
HarmonyOS 应用开发:基于API 12+的现代开发实践
华为·harmonyos
安卓开发者17 小时前
鸿蒙NEXT表单选择组件详解:Radio与Checkbox的使用指南
华为·harmonyos