BLOGE
0.9.9-RC1在线版 · 编制 2026-11-02 · English
附录 A —— 内置函数
bloge DSL 中所有的 guard、constraint 与表达式共享同一组内置函数。本附录是这套工 具箱的唯一参考:每个函数名、每个签名、每个陷阱都在这里。当你开始写真实的 DSL, 就把它收藏起来。
什么时候需要这份参考
| 问题 | 章节 |
|---|---|
coalesce / ifNull / isNull 各自做什么? | 第 5 节:Null 与默认值 |
when: 守卫里能用正则吗? | 第 1 节:字符串函数 |
| 怎么对列表字段求和或求平均? | 第 2 节:数学与聚合 |
| 怎么读嵌套 map 里的值? | 第 4 节:Map 与对象 |
| 怎么格式化时间戳或计算时长? | 第 7 节:日期/时间 |
| 怎么签名或哈希一段载荷? | 可选模块:bloge-functions-crypto |
| 怎么判断某个函数能否在编译期使用? | 纯函数与编译期求值 |
按任务查函数
| 我要做什么 | 先查哪里 |
|---|---|
| 清洗、拆分、匹配或格式化文本 | 字符串函数 |
| 汇总、比较、取整或聚合数值 | 数学与聚合 |
| 过滤、映射、展平或检查列表 | 集合函数 |
| 安全读取嵌套对象或 map | Map/对象与 null 处理 |
| 在 schema 校验前转换类型 | 类型转换 |
| 计算 deadline 或 duration | 日期/时间函数 |
| 创建摘要或签名 | 可选 crypto 模块 |
第 5 章 (会分支的图) 在 when: guard 里引入了这些函数。第 7 章 (设计良好的算子) 在
算子的 constraints 与 usageExample 中使用它们。第 15 章 (状态机) 在转换 guard
中使用它们。在 bloge DSL 中只要见到表达式,能调用的就是本附录列出的这套函数。
函数调用语法
所有函数都使用标准调用语法:
when: concat("Hello, ", ctx.name) == "Hello, Alice"
when: contains(split(ctx.tags, ","), "premium")
when: padLeft(toString(ctx.id), 10, "0")
bloge 没有 Unix 风格的管道操作符 (|)。| 在词法分析中保留给逻辑或 (||)。
要组合多个函数,就把调用嵌套起来。
Null 处理约定
核心内置函数默认 null 安全。传 null 时要么:
- 返回
null(safe-nav 语义);要么 - 返回一个合理的默认值 ——
size(null) → 0、isEmpty(null) → true、concat(null, "x") → "x"等等。
你可以依赖这个约定,少写很多防御性判断。可选的加密模块是唯一的例外,参见下文。
纯函数与编译期求值
绝大多数函数是纯函数 —— 相同输入总是产生相同输出,没有副作用。当参数是常量 时,DSL 编译器允许在编译期折叠这些纯函数调用。
少数函数是非纯函数,不可折叠:
now()—— 当前时间戳,来自引擎的 time sourcetoday()—— 当前日期,来自引擎的 time sourceuuid()—— 随机 UUIDsecret(name)—— 密钥查询 (加密模块)
在严格的"编译期专属"位置 (常量、schema constraint),编译器会拒绝非纯函数调用。把 它们留到运行期的 guard 里使用。
1. 字符串函数 (17)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
concat | concat(arg1, arg2, …) | String | 把所有参数按字符串拼接。null 视为空串。 |
substring | substring(str, start [, end]) | String | 从 start (含) 到 end (不含);自动钳制越界。 |
uppercase / upper | uppercase(str) | String | 转大写。 |
lowercase / lower | lowercase(str) | String | 转小写。 |
trim | trim(str) | String | 去掉首尾空白。 |
replace | replace(str, oldStr, newStr) | String | 把所有字面量 oldStr 替换为 newStr。 |
startsWith | startsWith(str, prefix) | Boolean | 前缀判断;遇 null 返回 false。 |
endsWith | endsWith(str, suffix) | Boolean | 后缀判断;遇 null 返回 false。 |
indexOf | indexOf(str, target) | Number | 第一次出现的下标,找不到返回 -1。 |
length / len | length(str) | Number | 字符串长度;列表、map 同样适用;null 返回 0。 |
matches | matches(str, regex) | Boolean | 正则匹配;null 或正则错误返回 false (不抛异常)。 |
replaceAll | replaceAll(str, regex, replacement) | String | 正则全量替换;null 或正则错误返回原值。 |
split | split(str, delimiter) | List | 按字面分隔符 (非正则) 切分;null 返回空列表。 |
join | join(list, delimiter) | String | 用分隔符拼接列表元素;null 元素变空串。 |
padLeft | padLeft(str, length, padChar) | String | 用单字符左填充到指定长度。 |
padRight | padRight(str, length, padChar) | String | 用单字符右填充到指定长度。 |
when: startsWith(ctx.email, "admin@") and contains(ctx.email, "@")
when: matches(ctx.phone, "^\\+?[0-9 ]{6,}$")
when: padLeft(toString(ctx.orderId), 8, "0") == "00012345"
2. 数学与聚合 (10)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
abs | abs(n) | Number | 绝对值。 |
min | min(a, b) | Number | 两数最小值。 |
max | max(a, b) | Number | 两数最大值。 |
round | round(n) | Number | 四舍五入到整数 (Math.round)。 |
ceil | ceil(n) | Number | 向上取整。 |
floor | floor(n) | Number | 向下取整。 |
clamp | clamp(n, min, max) | Number | 把 n 钳到 [min, max]。 |
pow | pow(base, exponent) | Number | base ^ exponent。 |
sum | sum(list) | Number | 数值元素求和;null 跳过;空列表返回 0。 |
avg | avg(list) | Number | 数值元素求平均;空或 null 返回 null。 |
when: sum(ctx.items.*.price) > 1000
when: clamp(ctx.retries, 0, 5) == ctx.retries
3. 集合函数 (13)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
size | size(collection) | Number | 列表 / map / 字符串的长度;null 返回 0。 |
contains | contains(collection, item) | Boolean | 成员判断。列表→精确匹配;字符串→子串;map→是否含该键。 |
first | first(list) | any | 第一个元素;空或 null 返回 null。 |
last | last(list) | any | 最后一个元素;空或 null 返回 null。 |
isEmpty | isEmpty(collection) | Boolean | 空或 null 返回 true。 |
distinct | distinct(list) | List | 去重并保持原顺序。 |
flatten | flatten(listOfLists) | List | 展平一层;非列表元素透传。 |
sort | sort(list) | List | 自然序排序,落到 toString 作为后备;null 排在前面。 |
take | take(list, n) | List | 前 n 个元素。 |
drop | drop(list, n) | List | 跳过前 n 个元素。 |
reverse | reverse(list) | List | 反转顺序。 |
any | any(list, value) | Boolean | 是否存在等于 value 的元素。 |
all | all(list, value) | Boolean | 是否所有元素都等于 value;空列表返回 true。 |
when: contains(ctx.tags, "premium") and size(ctx.items) > 0
when: any(ctx.errors, "TIMEOUT")
when: all(distinct(ctx.statuses), "OK")
4. Map 与对象 (5)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
keys | keys(map) | List | 所有键作为列表返回。 |
values | values(map) | List | 所有值作为列表返回。 |
entries | entries(map) | List | 返回 {key, value} map 列表。 |
merge | merge(map1, map2) | Map | 浅合并;map2 覆盖 map1。 |
has | has(map, key) | Boolean | map 是否含该键。 |
when: has(ctx.headers, "X-Trace-Id")
when: size(keys(ctx.attributes)) > 3
5. Null 与默认值 (3)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
coalesce | coalesce(v1, v2, …) | any | 第一个非 null 的参数;全都是 null 则返回 null。 |
isNull | isNull(value) | Boolean | 是否为 null。 |
isNotNull | isNotNull(value) | Boolean | 是否非 null。 |
coalesce 是默认值的主力。如果只需要一个回退值,第 9 节“高级工具”中的
ifNull 读起来更自然。
when: isNotNull(ctx.userId)
let region = coalesce(ctx.region, ctx.country, "GLOBAL")
6. 类型转换 (5)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
toString | toString(value) | String | 任意值转字符串。 |
toNumber | toNumber(value) | Number | 从字符串解析数值,或直接返回数值;无法解析的字符串归约为 0。 |
toBoolean | toBoolean(value) | Boolean | 解析 "true" / "false" 字符串或透传布尔值。 |
toInt | toInt(value) | Number | 转整数;截断小数。 |
typeOf | typeOf(value) | String | 返回 "String"、"Number"、"Boolean"、"List"、"Map"、"Object" 或 "Null"。 |
when: toNumber(ctx.amountStr) > 100
when: typeOf(ctx.payload) == "Map"
7. 日期/时间 (6)
所有日期都以 ISO-8601 字符串 (yyyy-MM-ddTHH:mm:ssZ) 流转。用以下函数格式化、
解析、加减和计算时长。
| 函数 | 签名 | 返回 | 纯? | 说明 |
|---|---|---|---|---|
now | now() | String | ❌ | 来自 GraphEngine.currentTimeSource() 的当前时间戳。 |
today | today() | String | ❌ | 当前日期 yyyy-MM-dd (UTC)。 |
formatDate | formatDate(isoStr, pattern) | String | ✅ | 用 DateTimeFormatter 模式格式化 ISO 时间 (如 "yyyy-MM-dd HH:mm")。 |
parseDate | parseDate(str, pattern) | String | ✅ | 按模式解析;返回 ISO-8601 时间。 |
addDuration | addDuration(isoStr, durationStr) | String | ✅ | 加一段时长。支持 "2h"、"30m"、"1d"、"500ms" 或 ISO-8601 形式 "PT2H"。 |
diffDuration | diffDuration(iso1, iso2, unit) | Number | ✅ | 两个时间点之间的距离。unit: "s"/"seconds"、"m"/"minutes"、"h"/"hours"、"d"/"days"。 |
when: diffDuration(ctx.createdAt, now(), "h") > 24
let expiresAt = addDuration(now(), "30m")
确定性小贴士。 在对重放敏感的场景 (durable session、确定性测试) 中,优先用 基于存储时间戳的
parseDate/addDuration,而不要直接调用now()/today()。
8. 工具 / ID (2)
| 函数 | 签名 | 返回 | 纯? | 说明 |
|---|---|---|---|---|
uuid | uuid() | String | ❌ | 随机 UUID v4。 |
format | format(template, arg1, arg2, …) | String | ✅ | 使用 Java String.format 风格的占位符 (%s、%d 等);格式错误时返回模板原文。 |
let traceId = uuid()
let message = format("Order %s failed after %d retries", ctx.orderId, ctx.retries)
9. 高级工具 (8)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
regexExtract | regexExtract(str, pattern [, group]) | String | 取正则匹配的分组 (默认 1);未匹配返回 null。 |
regexExtractAll | regexExtractAll(str, pattern) | List | 所有不重叠的匹配。 |
getField | getField(obj, fieldName) | any | 通过反 射读 map 或 bean 的字段;null 安全。 |
setField | setField(obj, fieldName, value) | Map | 返回更新字段后的浅拷贝 (不可变语义)。 |
template | template(str, bindings) | String | 用 map 替换 ${key} 占位符。 |
range | range(start, end) | List | 整数序列 [start, end)。 |
mapOf | mapOf(k1, v1, k2, v2, …) | Map | 用交替的键值参数构造 map。 |
listOf | listOf(e1, e2, …) | List | 用参数构造列表。 |
ifNull | ifNull(value, default) | any | 非 null 返回 value,否则返回 default。 |
let host = regexExtract(ctx.url, "https?://([^/]+)/")
let updated = setField(ctx.user, "lastSeenAt", now())
let greeting = template("Hello, ${name}!", mapOf("name", ctx.name))
10. JSON 函数 (2)
当 DSL 编译器配置了 JsonCodec,JSON 工具就会注册到函数表里 ——
BuiltInFunctions.registerAll(registry, jsonCodec)。
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
toJson | toJson(obj) | String | 通过配置的 codec 序列化为 JSON。 |
fromJson | fromJson(str) | any | 反序列化为 Map / List / 基础类型。 |
let payload = toJson(mapOf("orderId", ctx.orderId, "total", ctx.total))
let parsed = fromJson(ctx.responseBody)
若未配置 JsonCodec,DSL 编译时将以"未知函数"报错。
可选模块 (bloge-functions-crypto)
当 bloge-functions-crypto JAR 出现在类路径上,Java ServiceLoader 会自动加载该模
块。它提供三组函数。涉及密钥解析的加密函数还需要在引擎上配置 SecretProvider。
加密 (5)
| 函数 | 签名 | 返回 | 纯? | 说明 |
|---|---|---|---|---|
hmacSha256 | hmacSha256(data, keyRef) | String | ✅ | HMAC-SHA256 hex 摘要;keyRef 通过 SecretProvider 解析。 |
hmacSha512 | hmacSha512(data, keyRef) | String | ✅ | HMAC-SHA512 hex 摘要。 |
aesEncrypt | aesEncrypt(plaintext, keyRef [, ivRef]) | String | ✅ | AES-256-CBC;base64 输出;不传 IV 时随机生成。 |
aesDecrypt | aesDecrypt(ciphertext, keyRef [, ivRef]) | String | ✅ | AES-256-CBC;输入 base64。 |
secret | secret(name) | String | ❌ | 从 SecretProvider 按名字解析密钥。 |
哈希 (4)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
md5 | md5(str) | String | MD5 hex。 |
sha1 | sha1(str) | String | SHA-1 hex。 |
sha256 | sha256(str) | String | SHA-256 hex。 |
sha512 | sha512(str) | String | SHA-512 hex。 |
编码 (6)
| 函数 | 签名 | 返回 | 说明 |
|---|---|---|---|
base64Encode | base64Encode(str) | String | UTF-8 字符串 base64 编码。 |
base64Decode | base64Decode(str) | String | base64 解码;输入非法返回 null。 |
hexEncode | hexEncode(str) | String | UTF-8 字符串 hex 编码。 |
hexDecode | hexDecode(str) | String | hex 解码;输入非法返回 null。 |
urlEncode | urlEncode(str) | String | URL 编码 (UTF-8)。 |
urlDecode | urlDecode(str) | String | URL 解码 (UTF-8);输入非法返回 null。 |
let signature = hmacSha256(ctx.requestBody, "webhook-secret")
let fingerprint = sha256(ctx.userId)
let safeQuery = urlEncode(ctx.search)
加密模块的例外。 与核心函数不同,加密 / 哈希函数遇到非法输入 (错误密钥、损坏 的密文) 可能抛异常。请在边界做校验,或把调用放进能处理对应失败模式的算子内部。
常见错误
- 管道操作符。
value | uppercase在 bloge DSL 不合法。要写uppercase(value)。词法器把|当作||的开头来读。 - 正则切分。
split(s, ",")是按字面字符串","切分,不是正则。如果需要按 模式切分,先用replaceAll预处理。 - 不传 ISO 字符串做日期运算。
addDuration("2024-01-01", "1d")会失败 —— 第一 个参数必须是 ISO-8601 时间。如果你拿到的是裸日期,先用addDuration(parseDate("2024-01-01", "yyyy-MM-dd"), "1d")。 - 就地修改 map。
setField不会原地修改,它返回一个副本。要绑定结果:let updated = setField(ctx.user, "name", "Alice")。 - 常量位置调用非纯函数。
now()、uuid()在编译期专属位置 (常量折叠、schema 常量) 会被拒绝。把它们移到运行期 guard 或算子输出里。 - 没配
SecretProvider就用加密。hmacSha256/aesEncrypt在缺少SecretProvider时会失败。在 Spring 或手工 bootstrap 里注入它 (见第 20 章)。 toBoolean不接受数字。toBoolean(1)返回null,不是true;只接受"true"/"false"字符串或原生布尔值。
与正文的关联
- 第 5 章 —— 会分支的图。
when:guard 的引入章节,也是组合这些函数最主要的 场所。 - 第 7 章 —— 设计良好的算子。 在算子的
constraints与usageExample表达式 中使用同一套函数。 - 第 14 章 —— 多轮 session 与 第 15 章 —— 状态机。 session 阶段转换与状
态机转换的 guard 都使用 guard 表达式,两者通过同一个
ExpressionEvaluator编 译。 - 第 9 章 —— 工具链工作流。
operator-metadata.json导出器会把这些函数名暴 露给 IDE,让补全和校验与编译器的能力保持一致。 - 第 20 章 —— Spring 与生产接线。
JsonCodec与SecretProvider在此被装配, 从而启用上文的 JSON / 加密扩展。
校验。 72 个核心函数对应 bloge-core 中的
BuiltInFunctionsTest.registerAll_populatesExpectedNumberOfEntries()。再加上 2 个
JSON 工具与 11 个 crypto/hash/encoding 工具,总数即本附录列出的 85 个。