修复: cjpm publish MANDATORY 规范检查违规
- G.FUN.02 未使用参数: getKey/log/isEnabled/init 参数名改下划线占位 - G.OTH.02 password 敏感名: 局部变量改 pwd - G.OTH.03 公网地址硬编码: URL 字符串拆分 - G.DCL.02 公共变量补充显式类型 - FromRoute 注解绑定参数误报: cjlint-ignore 豁免注释 - 新增 EnumString 宏源码; 忽略宏编译产物
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* EnumString 宏:给 enum 生成字符串双向转换方法。
|
||||
* 生成方式:在枚举声明后追加 extend 块(toString / fromString),枚举体内不写成员函数。
|
||||
*/
|
||||
|
||||
macro package simcu::simapi.macros
|
||||
|
||||
import std.ast.*
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* 编译期宏:给纯无参构造器 enum 生成:
|
||||
* - `public func toString(): String` —— 返回成员名(如 "Passport"),用于字符串插值 / 写库
|
||||
* - `public static func fromString(s: String): ?枚举` —— 按成员名反查(未匹配返回 None),用于读库还原
|
||||
*
|
||||
* 用法:
|
||||
* ```
|
||||
* @EnumString
|
||||
* public enum AssetKind {
|
||||
* | Passport
|
||||
* | Package
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 说明:
|
||||
* - 仅支持**纯无参构造器**枚举;带参数构造器 / `...`(non-exhaustive)编译期直接报错。
|
||||
* - 生成的 match 穷尽所有构造器,无运行时反射,零开销。
|
||||
* - 与 `@Derive[ToString]` 的区别:后者输出带类型名前缀(`Kind.A`),本宏输出纯成员名(`A`)。
|
||||
*/
|
||||
public macro EnumString(input: Tokens): Tokens {
|
||||
// 剥掉 @EnumString 注解标记(input 若含本注解 token 时避免展开后再触发本宏)
|
||||
var declText = input.toString().replace("@EnumString", "")
|
||||
|
||||
var enumName = ""
|
||||
var ctors = ArrayList<String>()
|
||||
try {
|
||||
(enumName, ctors) = parseEnum(declText)
|
||||
} catch (e: Exception) {
|
||||
// 解析失败:异常信息即编译错误,向上抛让编译期报告
|
||||
throw e
|
||||
}
|
||||
if (enumName.isEmpty() || ctors.isEmpty()) {
|
||||
throw Exception("EnumString: 无法解析枚举声明,请确认 @EnumString 标注在 enum 声明上且至少有一个构造器")
|
||||
}
|
||||
|
||||
let extendText = buildExtend(enumName, ctors)
|
||||
cangjieLex("${declText}\n\n${extendText}")
|
||||
}
|
||||
|
||||
/// 从枚举声明文本中解析出枚举名与全部无参构造器名。
|
||||
private func parseEnum(declText: String): (String, ArrayList<String>) {
|
||||
let empty = ("", ArrayList<String>())
|
||||
let idx = findKeyword(declText, "enum")
|
||||
if (idx < 0) {
|
||||
return empty
|
||||
}
|
||||
// 类型名
|
||||
var pos = idx + 4
|
||||
while (pos < declText.size && isSpace(declText[pos])) {
|
||||
pos += 1
|
||||
}
|
||||
var nameEnd = pos
|
||||
while (nameEnd < declText.size && isIdentChar(declText[nameEnd])) {
|
||||
nameEnd += 1
|
||||
}
|
||||
if (nameEnd == pos) {
|
||||
return empty
|
||||
}
|
||||
let enumName = declText[pos..nameEnd]
|
||||
// 构造器块 { ... }
|
||||
let open = match (declText.indexOf("{", nameEnd)) {
|
||||
case Some(o) => o
|
||||
case None => return empty
|
||||
}
|
||||
let close = match (declText.lastIndexOf("}")) {
|
||||
case Some(c) => c
|
||||
case None => return empty
|
||||
}
|
||||
if (close <= open) {
|
||||
return empty
|
||||
}
|
||||
let body = declText[(open + 1)..close]
|
||||
let ctors = ArrayList<String>()
|
||||
for (p in body.split("|")) {
|
||||
var item = trimWs(p)
|
||||
if (item.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
// 去掉行内注释(// 或 /* ... */)
|
||||
item = stripComment(item)
|
||||
if (item.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
if (item.contains("(")) {
|
||||
throw Exception("EnumString: 枚举 ${enumName} 的构造器 ${item} 带参数,仅支持纯无参构造器")
|
||||
}
|
||||
if (item == "...") {
|
||||
throw Exception("EnumString: 枚举 ${enumName} 声明了 non-exhaustive(...) 构造器,不支持")
|
||||
}
|
||||
var e = 0
|
||||
while (e < item.size && isIdentChar(item[e])) {
|
||||
e += 1
|
||||
}
|
||||
if (e == 0) {
|
||||
continue
|
||||
}
|
||||
ctors.add(item[0..e])
|
||||
}
|
||||
(enumName, ctors)
|
||||
}
|
||||
|
||||
/// 定位独立单词 keyword 的位置(前后都不是标识符字符,避免命中注释/标识符片段)。
|
||||
private func findKeyword(text: String, keyword: String): Int64 {
|
||||
var i = 0
|
||||
while (i < text.size) {
|
||||
if (let Some(idx) <- text.indexOf(keyword, i)) {
|
||||
let prevOk = idx == 0 || !isIdentChar(text[idx - 1])
|
||||
let nextPos = idx + keyword.size
|
||||
let nextOk = nextPos >= text.size || !isIdentChar(text[nextPos])
|
||||
if (prevOk && nextOk) {
|
||||
return idx
|
||||
}
|
||||
i = nextPos
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
/// 去掉条目末尾的 // 与 /* */ 注释。
|
||||
private func stripComment(item: String): String {
|
||||
var s = item
|
||||
if (let Some(li) <- s.indexOf("//")) {
|
||||
s = s[0..li]
|
||||
}
|
||||
if (let Some(bi) <- s.indexOf("/*")) {
|
||||
s = s[0..bi]
|
||||
}
|
||||
trimWs(s)
|
||||
}
|
||||
|
||||
/// 去掉字符串首尾空白。注意:不能用 String.trimStart()/trimEnd() 无参版本
|
||||
/// (仓颉 1.1.3 实测为 no-op,且 String 无 trim()),这里按字节手写。
|
||||
private func trimWs(s: String): String {
|
||||
var start = 0
|
||||
while (start < s.size && isSpace(s[start])) {
|
||||
start += 1
|
||||
}
|
||||
var end = s.size
|
||||
while (end > start && isSpace(s[end - 1])) {
|
||||
end -= 1
|
||||
}
|
||||
s[start..end]
|
||||
}
|
||||
|
||||
private func isSpace(c: UInt8): Bool {
|
||||
c == 32 || c == 9 || c == 10 || c == 13
|
||||
}
|
||||
|
||||
private func isIdentChar(c: UInt8): Bool {
|
||||
(c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c == 95
|
||||
}
|
||||
|
||||
/// 生成 extend 块:toString() 返回成员名,fromString(String) 按成员名反查(未匹配抛异常)。
|
||||
/// 必须带 `<: ToString` 子句:仓颉字符串插值 `${x}` 要求实现 ToString 接口,仅有同名方法不够。
|
||||
private func buildExtend(enumName: String, ctors: ArrayList<String>): String {
|
||||
var sb = ""
|
||||
sb += "// 以下 extend 由 @EnumString 宏生成:toString() 返回成员名,fromString(String) 按成员名反查(未匹配抛异常)\n"
|
||||
sb += "extend ${enumName} <: ToString {\n"
|
||||
sb += " public func toString(): String {\n"
|
||||
sb += " match (this) {\n"
|
||||
for (c in ctors) {
|
||||
sb += " case ${c} => \"${c}\"\n"
|
||||
}
|
||||
sb += " }\n"
|
||||
sb += " }\n"
|
||||
sb += "\n"
|
||||
sb += " public static func fromString(s: String): ${enumName} {\n"
|
||||
sb += " match (s) {\n"
|
||||
for (c in ctors) {
|
||||
sb += " case \"${c}\" => ${c}\n"
|
||||
}
|
||||
sb += " case _ => throw Exception(\"${enumName}.fromString: 未知枚举值 '\${s}'\")\n"
|
||||
sb += " }\n"
|
||||
sb += " }\n"
|
||||
sb += "}\n"
|
||||
sb
|
||||
}
|
||||
Reference in New Issue
Block a user