基于华为开发者空间,仓颉宏实现语言集成查询LINQ
通过该案例,开发者可以了解体验仓颉宏的基本使用,和使用宏实现一个简单的LINQ语法,加深大家对仓颉宏特性的理解。
1 概述
1.1 背景介绍
仓颉宏是一种编译时代码生成工具,允许开发者操作代码片段(Tokens)并生成新的代码逻辑,从而减少重复代码并提升抽象能力。而LINQ(Language Integrated Query,语言集成查询)是一种DSL(Domain Specific Language,领域特定语言),它是微软.NET框架中的一个关键技术,它允许开发者使用熟悉的编程语言(如C#和Visual Basic)来编写查询。
通过该案例,开发者可以了解体验仓颉宏的基本使用,和使用宏实现一个简单的LINQ语法,加深大家对仓颉宏特性的理解。
1.2 适用对象
- 个人开发者
- 高校学生
1.3 案例时间
本案例总时长预计20分钟。
1.4 案例流程
说明:
① 用户登录开发者空间,体验使用仓颉宏;
② 使用仓颉宏实现简单的语音集成查询LINQ。
1.5 资源总览
本案例预计花费总计0元。
资源名称 | 规格 | 单价(元) | 时长(分钟) |
---|---|---|---|
开发者空间 - 云主机 | 鲲鹏通用计算增强型 kc2 | 4vCPUs | 8G | Ubuntu | 免费 | 20 |
最新案例动态,请查阅 《仓颉宏实现语言集成查询LINQ》。小伙伴快来领取华为开发者空间,进入云主机桌面版实操吧!
2 操作步骤
2.1 仓颉宏快速入门
2.1.1 宏简介
仓颉宏是一种在编译时进行代码替换的元编程工具,允许开发者通过操作抽象语法树(AST)动态生成或修改代码。为了把宏的调用和函数调用区分开来,在调用宏时使用 @ 加上宏的名称。与运行时注解(如Java注解)不同,宏在编译期展开,无需运行时处理,因此性能更高。其核心特点包括:
- 输入/输出为Tokens:宏接收代码片段(Tokens类型),处理后返回新的代码片段;
- 独立宏包声明:宏需定义在通过macro package声明的独立包中,避免与普通函数冲突;
- 属性与非属性宏:属性宏可接收额外参数(如@Cradle[console|logfile]),非属性宏仅处理代码片段。
2.1.2 相关概念
1、Tokens:
- 定义:表示代码片段的词法单元序列,包含标识符、运算符、字面量等。
- 操作:支持拼接、解析为语法树节点(如 parseExpr 解析表达式)。
2、quote 表达式:
- 功能:用于在宏内构建新的代码片段,支持插值语法$(…)动态嵌入变量或表达式。
- 示例:
let tokens = quote(
arr = $(intList) // 插值嵌入数组
s = $(str) // 插值嵌入字符串
);
3、语法节点:
- 类型:包括表达式节点(Expr)、声明节点(Decl)等,用于代码结构化解析。
- 应用:宏可通过解析 Tokens 为语法节点(如 parseDecl)实现复杂逻辑生成,例如自动生成类的属性。
2.1.3 快速入门
我们通过一个简单的示例体验一下仓颉宏。
如下示例代码实现了在调试过程中打印某个表达式的值,同时打印出表达式本身。
let x = 3
let y = 2
@dprint(x) // 打印 "x = 3"
@dprint(x + y) // 打印 "x + y = 5"
显然,dprint 不能被写为常规的函数,因为函数只能获得输入的值,不能获得输入的程序片段。但可以将 dprint 实现为一个宏。实现如下:
macro package demo.define
import std.ast.*
public macro dprint(input: Tokens): Tokens {
let inputStr = input.toString()
let result = quote(
print($(inputStr) + " = ")
println($(input)))
return result
}
在解释每行代码之前,先测试这个宏可以达到预期的效果。
首先,登录开发者空间,点击进入桌面进入到云主机。
在云主机桌面打开CodeArts IDE for Cangjie。
点击“新建工程”创建仓颉工程,名称定义为demo,产物类型选择executable。
产物类型说明:
- executable,可执行文件;
- static,静态库,是一组预先编译好的目标文件的集合;
- dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。
创建项目后,打开src目录下main.cj文件,替换成以下测试代码(代码替换后爆红可以不用处理,等main.cj和dprint.cj都修改/创建后,直接运行main.cj即可)。
package demo
import demo.define.*
main() {
let x = 3
let y = 2
@dprint(x)
@dprint(x + y)
}
在src目录下创建一个define 文件夹,并在define文件夹中创建 dprint.cj 文件,将上面宏示例代码内容复制到 dprint.cj。
点击右上角运行按钮运行程序,查看运行结果看到dprint宏不仅返回了结果,也把代码片段返回了。
选择main.cj,点击Build Cangjie Project,生成main.cj.macrocall文件,打开该文件可以看出到宏展开后的代码:
接下来我们通过dprint宏代码来了解一下一个简单的宏都是由哪些部分组成:
第 1 行:macro package demo.define
宏必须声明在独立的包中(不能和其他 public 函数一起),含有宏的包使用 macro package 来声明。这里在项目目录下声明了一个名为 define 的宏包。
第 2 行:import std.ast.*
实现宏需要的数据类型,例如 Tokens 和后面会讲到的语法节点类型,位于仓颉标准库的ast 包中,因此任何宏的实现都需要首先引入 ast 包。
第 3 行:public macro dprint(input: Tokens): Tokens
在这里声明一个名为 dprint 的宏。由于这个宏是一个非属性宏(之后会解释这个概念),它接受一个类型为 Tokens 的参数。该输入代表传给宏的程序片段。宏的返回值也是一个程序片段。
第 4 行:let inputStr = input.toString()
在宏的实现中,首先将输入的程序片段转化为字符串。在前面的测试案例中,inputStr 成为"x" 或 “x + y”。
第 5-7 行:let result = quote(…)
这里 quote 表达式是用于构造 Tokens 的一种表达式,它将括号内的程序片段转换为 Tokens。在 quote 的输入中,可以使用插值 $(…) 来将括号内的表达式转换为 Tokens,然后插入到 quote 构建的 Tokens 中。对于以上代码,$(inputStr) 插入 inputStr 字符串的值(包含字符串两端的引号),$(input) 插入 input,即输入的程序片段。因此,如果输入的表达式是 x + y,那么形成的Tokens为:
print("x + y" + " = ")
println(x + y)
第 8 行:return result
最后,将构造出来的代码返回,这两行代码将被编译,运行时将输出 x + y = 5。
体验了仓颉宏的简单使用后,下面我们使用宏做一个DSL的案例。
2.2 仓颉宏实现LINQ
2.2.1 需求分析
使用仓颉宏实现一个简单的 DSL(Domain Specific Language,领域特定语言)——LINQ。LINQ(Language Integrated Query,语言集成查询)是微软 .NET 框架的一个组成部分,它提供了一种统一的数据查询语法,允许开发者使用类似 SQL 的查询语句来操作各种数据源。在仓颉中,可通过宏将类似语法转换为高效编译期代码。
目标语法:
from <variable> in <list> where <condition> select <expression>
其中,from、in、where、select是关键字,variable 是一个标识符,list、condition 和 expression 都是表达式。语法示例:
# 效果:筛选出1-20中3的倍数,然后输出其平方
from x in 1..=20 where x % 3 == 0 select x * x
2.2.2 实现方案
在云主机桌面打开CodeArts IDE for Cangjie。
点击新建工程创建仓颉工程,名称定义为linqdemo,产物类型选择executable。
在src目录下创建一个define 文件夹,并在 define 文件夹中创建 linq.cj 文件,将下面代码内容复制到 linq.cj。
基于2.2.1中目标语法的要求
from \<variable\> in \<list\> where \<condition\> select \<expression\>
我们实现宏的策略是先后提取标识符和表达式,同时检查中间的关键字是正确的。最后,生成由提取部分组成的查询结果。实现如下:
macro package linqdemo.define
import std.ast.*
public macro linq(input: Tokens) {
// 定义标准错误提示模板
let syntaxMsg = "Syntax is \"from <attrib> in <table> where <cond> select <expr>\""
// ----------------- 语法结构校验 -----------------
// 校验from关键字
if (input.size == 0 || input[0].value != "from") {
diagReport(DiagReportLevel.ERROR, input[0..1], syntaxMsg,
"Expected keyword \"from\" here.")
}
// 校验迭代变量标识符(如x)
if (input.size <= 1 || input[1].kind != TokenKind.IDENTIFIER) {
diagReport(DiagReportLevel.ERROR, input[1..2], syntaxMsg,
"Expected identifier here.")
}
let attribute = input[1] // 捕获迭代变量名(如x)
// 校验in关键字
if (input.size <= 2 || input[2].value != "in") {
diagReport(DiagReportLevel.ERROR, input[2..3], syntaxMsg,
"Expected keyword \"in\" here.")
}
// ----------------- 表达式解析 -----------------
var index: Int64 = 3
// 解析数据源表达式(如1..=20)
let (table, nextIndex) = parseExprFragment(input, startFrom: index)
// 校验where关键字
if (nextIndex == input.size || input[nextIndex].value != "where") {
diagReport(DiagReportLevel.ERROR, input[nextIndex..nextIndex+1], syntaxMsg,
"Expected keyword \"where\" here.")
}
index = nextIndex + 1 // 移动索引到where之后
// 解析条件表达式(如x % 3 == 0)
let (cond, nextIndex2) = parseExprFragment(input, startFrom: index)
// 校验select关键字
if (nextIndex2 == input.size || input[nextIndex2].value != "select") {
diagReport(DiagReportLevel.ERROR, input[nextIndex2..nextIndex2+1], syntaxMsg,
"Expected keyword \"select\" here.")
}
index = nextIndex2 + 1 // 移动索引到select之后
// 解析输出表达式(如x * x)
let (expr, _) = parseExprFragment(input, startFrom: index)
// ----------------- 代码生成 -----------------
return quote(
// 生成遍历逻辑:for (x in 1.to(20))
for ($(attribute) in $(table)) {
// 插入条件判断:if (x % 3 == 0)
if ($(cond)) {
// 插入输出表达式:println(x * x)
println($(expr))
}
}
)
}
在main.cj中调用linq宏(代码替换后爆红可以不用处理,等main.cj和dprint.cj都修改/创建后,直接运行main.cj即可)。
package linqdemo
import linqdemo.define.*
main() {
println("筛选出1-20中3的倍数,然后返回其平方:")
@linq(from x in 1..=20 where x % 3 == 0 select x * x)
}
这个例子从 1, 2, … 20 列表中筛选出3的倍数,然后返回其平方。输出结果为:
宏展开后的代码同样可以参考2.1.3快速入门生成main.cj.macrocall文件查看。通过该案例可以看出,宏的实现的很大部分用于解析并校验输入的 tokens,这对宏的可用性至关重要。
至此,使用仓颉宏实现一个简单的LINQ案例就完成啦。
如果想要了解更多仓颉宏及仓颉编程语言知识可以访问: https://cangjie-lang.cn/
如果想要体验更多仓颉示例可以访问:https://gitcode.com/Cangjie/Cangjie-Examples
更多推荐
所有评论(0)