仓颉语言中的宏:从基础到进阶详解

一、宏的基本概念

宏在仓颉语言中可以理解为一种“代码缩写”工具,或者是扩展语言语法的方式。在编译或程序运行时,遇到宏就会将其替换为对应的实际代码,这个过程叫宏展开。就好比我们写文章时用“etc.”代表“等等”,看到“etc.”就知道要替换成“等等” ,宏也是类似道理,把简单的宏代码替换成更复杂完整的代码。如果有一些功能能用统一简单的代码表达,就可以用宏来处理。仓颉目前提供过程宏,未来还会有late - stage宏和模板宏等。

二、过程宏

(一)过程宏的原理

过程宏接受一个token序列作为输入(token是词法分析器产生的基本单元,比如一个关键字、一个标识符等 )。它对输入的token序列进行处理和变换,然后输出另一个token序列。这里要注意,输入的token序列只要满足仓颉的词法规则就行(比如不能把关键字拼错 ),不需要满足语法规则;但输出的token序列必须是合法的仓颉程序,要满足语法语义。

(二)过程宏示例及代码详解

我们来看一个具体例子:

@DebugLog( expensiveComputation() )

这里调用了DebugLog宏,参数是expensiveComputation() 。它的作用是在编译时根据程序运行模式(开发模式或生产模式 )来决定是否运行expensiveComputation()并打印调试输出。

下面是DebugLog宏的实现:

public macro DebugLog(input: Tokens) {
    // 检查当前程序配置是否为开发模式
    if (globalConfig.mode == Mode.development) {
        // 如果是开发模式,返回的代码是在输入的token序列外面加上println调用
        // 也就是除了执行input部分(这里是expensiveComputation() ),还会把执行结果打印出来
        return quote( println( ${input} ) ) 
    }
    else {
        // 如果不是开发模式,返回空的token序列,意味着完全忽略input部分,不会生成任何代码
        return quote() 
    }
}
  • public macro DebugLog(input: Tokens) :这行代码定义了一个名为DebugLog的宏,它像函数定义一样,有一个参数input,类型是Tokens ,也就是接受一个token序列作为输入。
  • if (globalConfig.mode == Mode.development) :这里通过判断globalConfig(假设是一个全局配置对象 )中的mode属性是否等于Mode.development来确定程序是否处于开发模式。
  • return quote( println( ${input} ) )quote函数的作用是把里面的内容转换为token序列。这里${input}是一种插值写法,会把之前传入的input(也就是expensiveComputation()对应的token序列 )插入到这里。整体意思就是返回一个新的token序列,这个序列的代码效果是执行expensiveComputation() ,然后把结果通过println打印出来。
  • return quote() :当程序不在开发模式时,返回一个空的token序列,这样在宏展开时就不会生成任何与expensiveComputation()相关的代码。

三、Late - stage宏

(一)Late - stage宏产生的背景

过程宏的输入token序列不包含程序的语义信息(比如变量的类型等 )。但有些时候我们希望宏能根据变量类型、类和接口声明等信息来处理。看下面的例子:

@FindType
var x1: Employee = Employee("Fred Johnson")
// getting the type info of `x1`: easy, it's right there

@FindType
var x2 = Employee("Bob Houston")
// getting the type info of `x2`: hard, requires type inference

这里定义了FindType宏,目的是获取变量声明中变量的类型并打印或记录日志。对于x1 ,它的类型Employee在语法中明确给出,从输入的token序列能提取出来。但对于x2 ,它的类型在声明中没明确给出,需要类型推断才能得到。而过程宏展开在语法分析阶段,那时类型推断还没进行,所以无法获取x2的类型信息。

(二)Late - stage宏的特点

Late - stage宏把宏展开延迟到类型推断之后,这样就能获取和利用程序的各种语义信息,包括推断出的类型信息。它允许基于类型信息和代码中的非局部定义生成代码,功能很强大。但它也有局限性,因为类型已知后,对现有代码做根本更改不太可能了。比如不能再改变变量的类型等。

四、模板宏

(一)模板宏的适用场景

当我们想要对一些语法模式很固定的代码进行重写时,模板宏是比普通过程宏更易用的选择。

(二)模板宏示例及代码详解

先看模板宏的定义:

public template macro unless {
    template (cond: Expr, block: Block) {
        @unless (cond) block
            =>
        if (! cond) block
    }
}
  • public template macro unless :这行定义了一个名为unless的模板宏。
  • template (cond: Expr, block: Block) :这里定义了模板宏的模板,它有两个参数,cond类型是Expr(表达式 ),block类型是Block(代码块 )。
  • @unless (cond) block => if (! cond) block :这部分是模板匹配和转换规则。@unless (cond) block是源模板,也就是我们写代码时使用宏的样子;=>表示转换方向;if (! cond) block是目标模板,即宏展开后的代码样子。

再看使用这个模板宏的代码:

@unless (x > 0) {
    print("x not greater than 0")
}

宏展开时,会根据模板宏的定义,把x > 0匹配到cond ,把{ print("x not greater than 0") }匹配到block ,然后转换为:

if (! x > 0) {
    print("x not greater than 0")
}

模板宏的优点是直接描述源程序代码和目标程序代码,专注于关键代码段的转换。相比之下,过程宏实现同样功能代码会更冗长,也更容易出错。

通过这几种宏,仓颉语言为开发者提供了灵活扩展语言功能和简化代码编写的能力,不同类型的宏适用于不同的场景,开发者可以根据需求选择合适的宏来使用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐