This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/packages/sfeel/src/static-check.ts
T
JCAI Main AgentandClaude Opus 5 146e9bbfad M3(1/5): @sfeel —— S-FEEL 子集的解析与求值,前后端同源
- lexer / parser(递归下降)/ evaluate / static-check,四文件纯函数,零 IO 零外部调用
- 子集封顶(ADR-006):比较 · 逻辑 · 算术 · 区间(开闭四态)· 列表与 in ·
  封闭内建函数集(字符串 / 数字 / 日期);未知函数一律拦下
- 单元格谓词(DMN 一格):`-` 任意 / 比较 / 区间 / 逗号析取
- staticCheck 给「画图即校验」用:语法 + 未知函数 + 引用路径清单
- app 侧以它替下 passthroughEvaluator:监听内容条件真求值,求值异常按不命中处置并留痕

32 个 sfeel 测试;全仓 182 个测试通过,门禁五道全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 01:21:45 +08:00

92 lines
2.9 KiB
TypeScript

/**
* 静态可检 —— 编排画布「画图即校验」与安装静态审的依据(编码规范 §8.3;OS 03)。
*
* 检三样:①语法能不能过 ②引用的函数在不在封闭集合里 ③引用了哪些路径(供必填字段核对)。
*/
import type { Node, UnaryTest } from './ast.ts';
import { SfeelSyntaxError } from './lexer.ts';
import { parse, parseUnaryTest } from './parser.ts';
import { BUILTIN_FUNCTIONS } from './evaluate.ts';
export type StaticIssue = {
readonly kind: 'syntax' | 'unknown-function';
readonly message: string;
};
export type StaticCheckResult = {
readonly ok: boolean;
readonly issues: readonly StaticIssue[];
/** 表达式引用的全部根路径 —— 编排器据此核对上游载荷是否供得上 */
readonly references: readonly string[];
};
export function staticCheck(expression: string, mode: 'expression' | 'unary-test' = 'expression'): StaticCheckResult {
let node: Node | undefined;
const issues: StaticIssue[] = [];
try {
if (mode === 'unary-test') {
const test = parseUnaryTest(expression);
const nodes = collectTestNodes(test);
const references = new Set<string>();
for (const n of nodes) walk(n, issues, references);
return { ok: issues.length === 0, issues, references: [...references] };
}
node = parse(expression);
} catch (error) {
if (error instanceof SfeelSyntaxError) return { ok: false, issues: [{ kind: 'syntax', message: error.message }], references: [] };
throw error;
}
const references = new Set<string>();
walk(node, issues, references);
return { ok: issues.length === 0, issues, references: [...references] };
}
function collectTestNodes(test: UnaryTest): Node[] {
switch (test.kind) {
case 'any':
return [];
case 'compare':
return [test.operand];
case 'range':
return [test.node];
case 'disjunction':
return test.tests.flatMap(collectTestNodes);
}
}
function walk(node: Node, issues: StaticIssue[], references: Set<string>): void {
switch (node.kind) {
case 'literal':
return;
case 'path':
references.add(node.segments.join('.'));
return;
case 'unary':
walk(node.operand, issues, references);
return;
case 'binary':
walk(node.left, issues, references);
walk(node.right, issues, references);
return;
case 'list':
for (const item of node.items) walk(item, issues, references);
return;
case 'range':
walk(node.start, issues, references);
walk(node.end, issues, references);
return;
case 'in':
walk(node.value, issues, references);
walk(node.candidates, issues, references);
return;
case 'call':
if (!BUILTIN_FUNCTIONS.includes(node.name)) {
issues.push({ kind: 'unknown-function', message: `未知函数 ${node.name}(S-FEEL 子集封顶,无外部调用)` });
}
for (const arg of node.args) walk(arg, issues, references);
return;
}
}