88 lines
3.4 KiB
Plaintext
88 lines
3.4 KiB
Plaintext
/*
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
|
* @Seed 类级属性宏:把标注的普通类展开为数据填充(自动注册,无需手写注册表)。
|
|
*
|
|
* 用法:
|
|
* @Seed["初始化游戏服务器配置"]
|
|
* public class InitGames {
|
|
* public func run(ctx: DataContext): Unit {
|
|
* // 用应用自己的 DbContext 直接操作 ORM,自行保证幂等
|
|
* }
|
|
* }
|
|
*
|
|
* 展开行为:
|
|
* - seedId 取类名(如 InitGames),属性参数仅作为描述
|
|
* - 保留原类(用户类不继承任何基类,run 参数类型为应用自己的 DbContext)
|
|
* - 生成包装类 <: simcu::orm.seeds.Seed,override run(ctx: Any),
|
|
* 内部 new 用户类并 `ctx as <run 参数类型>` 转型后调用用户 run
|
|
* - 生成顶层 let registerSeed(包装类实例),模块加载时自动注册
|
|
*
|
|
* 约束:
|
|
* - 属性参数为描述(不能包含双引号/反斜杠/换行)
|
|
* - 类必须提供单参数方法 run(ctx)
|
|
* - 应用侧需 import simcu::orm.macros.* 与 simcu::orm.seeds.*
|
|
*/
|
|
|
|
macro package simcu::orm.macros
|
|
|
|
import std.ast.*
|
|
import std.collection.*
|
|
|
|
/// @Seed 属性宏:属性(attrTokens)为 ["描述"],input 为类声明,seedId 取类名。
|
|
public macro Seed(attrTokens: Tokens, inputTokens: Tokens): Tokens {
|
|
let description = parseAttr(attrTokens.toString())
|
|
if (description.contains("\"") || description.contains("\\") || description.contains("\n")) {
|
|
throw ASTException("@Seed 参数描述不能包含双引号、反斜杠或换行")
|
|
}
|
|
let cd = (parseDecl(inputTokens) as ClassDecl).getOrThrow()
|
|
// 找 run 方法并提取第一个参数类型(任意类型,由包装类在应用侧转型)
|
|
var ctxType: ?String = None
|
|
for (d in cd.body.decls) {
|
|
if (let fd: FuncDecl <- d) {
|
|
if (fd.identifier.value == "run") {
|
|
if (fd.funcParams.size == 1) {
|
|
ctxType = Some(fd.funcParams[0].paramType.toTokens().toString())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let paramType = match (ctxType) {
|
|
case Some(t) => t
|
|
case None =>
|
|
throw ASTException("@Seed 类必须提供单参数方法 run(ctx),参数为应用自己的 DbContext 类型")
|
|
}
|
|
let className = cd.identifier.value
|
|
let seedId = className
|
|
let wrapperName = "${className}_SeedWrapper"
|
|
let regName = "_seedRegistration_${className}"
|
|
let code = """
|
|
public class ${wrapperName} <: Seed {
|
|
public init() {
|
|
super("${seedId}", "${description}")
|
|
}
|
|
public override func run(ctx: Any): Unit {
|
|
let inst = ${className}()
|
|
inst.run((ctx as ${paramType}).getOrThrow())
|
|
}
|
|
}
|
|
let ${regName} = registerSeed(${wrapperName}())
|
|
"""
|
|
// inputTokens 末尾 } 与展开内容之间需换行分隔(cangjieLex 保留 NL)
|
|
inputTokens + cangjieLex("\n" + code)
|
|
}
|
|
|
|
/// 解析属性 tokens 文本(形如 "描述")中的字符串字面量。
|
|
/// 按引号对子串定位后字节切片(引号为 ASCII,边界安全,中文等多字节内容原样保留)。
|
|
private func parseAttr(text: String): String {
|
|
let open = text.indexOf("\"")
|
|
if (open.isNone()) {
|
|
throw ASTException("@Seed 需要一个字符串属性: @Seed[\"描述\"]")
|
|
}
|
|
let start = open.getOrThrow() + 1
|
|
let close = text.indexOf("\"", start)
|
|
if (close.isNone()) {
|
|
throw ASTException("@Seed 需要一个字符串属性: @Seed[\"描述\"]")
|
|
}
|
|
text[start..close.getOrThrow()]
|
|
}
|