表达式与函数
凡是流程需要"算"而不只是"存"的地方——If 的条件、Loop 的上界、边上的条件、Variable 的赋值、Input Text 要敲出的文字——干活的都是同一门小语言。本页是它的参考。
它小得很刻意。没有循环,没有赋值,没有任何通往文件系统或网络的路。一个表达式读若干值,返回一个值。
引用一个值
{{gem_count}} 变量 gem_count
${gem_count} 完全相同——两种写法都接受
vars.gem_count 同一个变量的完整写法
result.output.text 上游节点的输出
值从哪来,见变量。
运算符
优先级从高到低——就是解析器的处理顺序:
| 层级 | 运算符 | 说明 |
|---|---|---|
| 取用 | . [ ] | 属性与下标:split(x, ",")[0] |
| 一元 | ! - | 取反、取负 |
| 乘除 | * / % | |
| 加减 | + - | |
| 比较 | < <= > >= | |
| 相等 | == != | |
| 空合并 | ?? | 取左边,除非它是 null |
| 与 | && | 短路 |
| 或 | || | 短路 |
括号按你想的那样分组。
+ 拼接两个字符串,也相加两个数。它拒绝混合的一对——"5" + 1 直接报错,而不去猜你想要哪个。布尔值永远不是操作数。除以零报错。
这不是疏漏。放松 + 会悄悄改变每一个已发布流程的计算结果,所以类型转换是一个你能在表达式里看见的函数——number()、text()、int()——绝不是隐式强制转换。Variable 节点的意图行会替你写出这些调用:选"给计数器加 1",你就得到一次正确的转换,一个字都不用写。
函数
29 个名字,下面全部列出。每一个都是全函数——只要还能回答,它就回答而不是抛错——而且除 random 外每一个都是确定性的,所以条件重放的结果完全一致。
文本谓词把 null 当作空字符串,所以哪怕上面的检测什么都没读到,contains(result.output.text, "Start") 依然安全:你拿到 false,而不是一次死掉的运行。
判断文本
| 函数 | 返回 |
|---|---|
contains(text, part) | part 是否出现在 text 里 |
not_contains(text, part) | 相反 |
starts_with(text, part) | |
ends_with(text, part) | |
equals_ignore_case(a, b) | 忽略大小写的相等 |
is_empty(value) | 空字符串、空数组、空对象,或 null |
is_not_empty(value) | 相反 |
regex_match(text, pattern) | 该模式是否能在 text 中找到 |
加工文本
| 函数 | 返回 |
|---|---|
lower(text) / upper(text) | 大小写转换 |
trim(text) | 去掉首尾空白 |
concat(a, b, …) | 把每个参数当文本拼起来——+ 拒绝做的混合类型拼接 |
replace(text, find, replacement) | 每一处字面出现。 不是正则。find 为空则原样返回 |
split(text, separator) | 各个部分,是数组——可以取下标:split(x, ",")[0]。分隔符为空则报错 |
substring(text, start) | 从 start 到末尾。负的 start 从末尾往回数 |
substring(text, start, length) | 最多 length 个字符 |
extract(text, pattern) | 第一个捕获组,模式没有捕获组时则是整个匹配——没匹配上时是 null |
extract(text, pattern, group) | 指定的组 |
default(value, fallback) | value 为 null 或为空时取 fallback |
len(value) / length(value) | 字符数、数组长度或对象大小。null 是 0 |
substring 是夹取而不是报错。 起点超出末尾就给 ""。OCR 读回来比平时短是再普通不过的一天,不构成杀掉一次运行的理由。
extract 没匹配上返回 null,正是这一点让它可组合:extract({{ocr}}, "[0-9]+") ?? "0"。
default 比 ?? 更宽。 ?? 只覆盖 null;default() 还覆盖空字符串——那才是一次失败读取的日常形状。
数 字
| 函数 | 返回 |
|---|---|
number(value) | 数值——不是数就报错 |
number(value, fallback) | ……或者用回退值而不报错 |
text(value) | 值的文本形式 |
is_number(value) | number() 会不会成功 |
int(value) | 向零截断——int(-2.7) 是 -2 |
round(value) / round(value, digits) | 逢五远离零进位 |
floor(value) / ceil(value) | |
abs(value) | |
min(a, b, …) / max(a, b, …) | |
clamp(value, low, high) | 夹在界内。low > high 时报错 |
random(low, high) | [low, high] 内的整数,两端都含 |
number() 不猜分隔符"1,234" 是真的有歧义——逗号在英语里是千分位,在越南语里是小数点。number() 只接受朴素的数字字面量。要去掉分隔符, 请自己 replace(),好让语言习惯这个决定显式写在流程里,而不是被流程替你假定。
round 对你写下的那个数取整。 round(1.005, 2) 是 1.01。对二进制近似值取整会得到 1.0——理论上说得通,在支持工单里根本解释不清。
random 是唯一不确定的函数,而这正是它的全部用途:一个永远用同一个上限的流程,每次跑起来一模一样。它在每次求值时重新抽取——没有任何地方缓存它。两个界限顺序随便写,并且会向内取整,所以小数参数绝不会扩大范围。
random(1, 100) <= 20 五次里有一次
旧写法
老流程以及 no-code 构建器编译出来的东西可能用 camelCase:notContains、startsWith、endsWith、equalsIgnoreCase、isEmpty、isNotEmpty。它们仍然有效,与 snake_case 版本完全等价。新写的东西请用 snake_case。
实战写法
从有噪声的 OCR 结果里读出一个数。
number(extract({{ocr_text}}, "[0-9]+") ?? "0")
extract 从 "LEVEL 5" 里拽出数字,?? 在读空时给一个值,number 让它变得可以参与运算。没有那个回退值,一次空读就会抛错。
从一大段剪贴板文本里取第 n 条记录的第 n 个字段。
split({{noidung}}, "\n")[{{i}} * 5 + 1]
这是桌面数据录入示例的核心:一次 Ctrl+C 把整个文件取回成文本,一个 Loop 计数器每五行走一步。
永远不越界的百分比。
clamp(round({{done}} / {{total}} * 100), 0, 100)
常见错误
| 错误 | 后果 | 修复 |
|---|---|---|
对 OCR 结果写 {{ocr}} + 1 | 报错——字符串加数字 | number({{ocr}}, 0) + 1 |
对 "1,234" 用 number() | 报错 | number(replace({{x}}, ",", "")) |
拿 replace() 当正则用 | 什么都匹配不上——它是字面的 | 要模式请用 extract() |
对空字符串用 ?? | ?? 只覆盖 null | default(value, fallback) |
以为 extract 返回 "" | 它返回 null | 后面接 ?? 或 default() |
在期望可重放的条件里用 random | 它每次求值都重新抽 | 先存进变量 |
相关页面
- 变量、Input 与 Memory——值住在哪里
- If——最常装着表达式的节点
- Variable——赋值,以及替你写出这些调用的意图行
- Loop——计数器与上界