@codehz/json-expr 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -83
- package/dist/index.d.mts +86 -103
- package/dist/index.mjs +363 -292
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -4
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
1
|
//#region src/parser.ts
|
|
4
2
|
const PRECEDENCE = {
|
|
5
3
|
"||": 1,
|
|
@@ -539,192 +537,311 @@ function needsParens(child, parent, position) {
|
|
|
539
537
|
}
|
|
540
538
|
return false;
|
|
541
539
|
}
|
|
540
|
+
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/proxy-metadata.ts
|
|
543
|
+
/**
|
|
544
|
+
* 全局 WeakMap 存储
|
|
545
|
+
*/
|
|
546
|
+
const proxyMetadata = /* @__PURE__ */ new WeakMap();
|
|
547
|
+
/**
|
|
548
|
+
* 设置 Proxy 元数据
|
|
549
|
+
*/
|
|
550
|
+
function setProxyMetadata(proxy, metadata) {
|
|
551
|
+
proxyMetadata.set(proxy, metadata);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 获取 Proxy 元数据
|
|
555
|
+
*/
|
|
556
|
+
function getProxyMetadata(proxy) {
|
|
557
|
+
return proxyMetadata.get(proxy);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* 检查对象是否是 Proxy variable
|
|
561
|
+
*/
|
|
562
|
+
function isProxyVariable(obj) {
|
|
563
|
+
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
|
|
564
|
+
return proxyMetadata.get(obj)?.type === "variable";
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* 检查对象是否是 Proxy expression
|
|
568
|
+
*/
|
|
569
|
+
function isProxyExpression(obj) {
|
|
570
|
+
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
|
|
571
|
+
return proxyMetadata.get(obj)?.type === "expression";
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 检查对象是否是任意 Proxy (variable 或 expression)
|
|
575
|
+
*/
|
|
576
|
+
function isProxy(obj) {
|
|
577
|
+
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
|
|
578
|
+
return proxyMetadata.has(obj);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/proxy-variable.ts
|
|
542
583
|
/**
|
|
543
|
-
*
|
|
584
|
+
* 使用 Symbol.description 生成占位符
|
|
585
|
+
* 用于在表达式源码中标识变量
|
|
544
586
|
*/
|
|
545
|
-
function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
visit(n.consequent);
|
|
562
|
-
visit(n.alternate);
|
|
563
|
-
break;
|
|
564
|
-
case "MemberExpr":
|
|
565
|
-
visit(n.object);
|
|
566
|
-
if (n.computed) visit(n.property);
|
|
567
|
-
break;
|
|
568
|
-
case "CallExpr":
|
|
569
|
-
visit(n.callee);
|
|
570
|
-
n.arguments.forEach(visit);
|
|
571
|
-
break;
|
|
572
|
-
case "ArrayExpr":
|
|
573
|
-
n.elements.forEach(visit);
|
|
574
|
-
break;
|
|
575
|
-
case "ObjectExpr":
|
|
576
|
-
n.properties.forEach((prop) => {
|
|
577
|
-
if (prop.computed) visit(prop.key);
|
|
578
|
-
visit(prop.value);
|
|
579
|
-
});
|
|
580
|
-
break;
|
|
587
|
+
function getVariablePlaceholder$1(id) {
|
|
588
|
+
return `$$VAR_${id.description}$$`;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* 序列化参数为表达式字符串
|
|
592
|
+
* - Proxy Variable/Expression:使用源码或占位符
|
|
593
|
+
* - 数组:递归处理元素
|
|
594
|
+
* - 对象:递归处理属性
|
|
595
|
+
* - 原始值:JSON.stringify
|
|
596
|
+
*/
|
|
597
|
+
function serializeArgument(arg) {
|
|
598
|
+
if ((typeof arg === "object" || typeof arg === "function") && arg !== null) {
|
|
599
|
+
const meta = getProxyMetadata(arg);
|
|
600
|
+
if (meta) {
|
|
601
|
+
if (meta.source) return meta.source;
|
|
602
|
+
if (meta.rootVariable) return getVariablePlaceholder$1(meta.rootVariable);
|
|
581
603
|
}
|
|
582
604
|
}
|
|
583
|
-
|
|
584
|
-
return
|
|
605
|
+
if (Array.isArray(arg)) return `[${arg.map(serializeArgument).join(", ")}]`;
|
|
606
|
+
if (typeof arg === "object" && arg !== null) return `{${Object.entries(arg).map(([k, v]) => {
|
|
607
|
+
return `${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k)}: ${serializeArgument(v)}`;
|
|
608
|
+
}).join(", ")}}`;
|
|
609
|
+
return JSON.stringify(arg);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* 从参数中收集依赖的 Symbol
|
|
613
|
+
* 递归遍历数组和对象,收集所有 Proxy 的依赖
|
|
614
|
+
*/
|
|
615
|
+
function collectDepsFromArgs(args, deps) {
|
|
616
|
+
for (const arg of args) if ((typeof arg === "object" || typeof arg === "function") && arg !== null) {
|
|
617
|
+
const meta = getProxyMetadata(arg);
|
|
618
|
+
if (meta?.dependencies) for (const dep of meta.dependencies) deps.add(dep);
|
|
619
|
+
else if (Array.isArray(arg)) collectDepsFromArgs(arg, deps);
|
|
620
|
+
else if (typeof arg === "object") collectDepsFromArgs(Object.values(arg), deps);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* 创建根 Variable Proxy
|
|
625
|
+
* 拦截属性访问,返回新的 expression proxy
|
|
626
|
+
* 不可直接调用(apply 应该只在链式调用后可用)
|
|
627
|
+
*
|
|
628
|
+
* @param id - 变量的唯一标识 Symbol
|
|
629
|
+
* @returns Proxy 包装的 Variable
|
|
630
|
+
*/
|
|
631
|
+
function createProxyVariable(id) {
|
|
632
|
+
const deps = new Set([id]);
|
|
633
|
+
const proxy = new Proxy(function() {}, {
|
|
634
|
+
get(_target, prop) {
|
|
635
|
+
if (typeof prop === "symbol") return void 0;
|
|
636
|
+
return createProxyExpression(id, [String(prop)], deps);
|
|
637
|
+
},
|
|
638
|
+
apply() {
|
|
639
|
+
throw new Error("Variable cannot be called directly");
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
setProxyMetadata(proxy, {
|
|
643
|
+
type: "variable",
|
|
644
|
+
path: [],
|
|
645
|
+
rootVariable: id,
|
|
646
|
+
dependencies: deps
|
|
647
|
+
});
|
|
648
|
+
return proxy;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* 创建属性访问后的 Proxy
|
|
652
|
+
* 继续拦截属性访问(链式访问)
|
|
653
|
+
* 拦截 apply 进行方法调用
|
|
654
|
+
*
|
|
655
|
+
* @param rootId - 根变量的 Symbol
|
|
656
|
+
* @param path - 属性访问路径
|
|
657
|
+
* @param deps - 依赖集合
|
|
658
|
+
* @returns Proxy 包装的 Expression
|
|
659
|
+
*/
|
|
660
|
+
function createProxyExpression(rootId, path, deps) {
|
|
661
|
+
const source = `${getVariablePlaceholder$1(rootId)}.${path.join(".")}`;
|
|
662
|
+
const proxy = new Proxy(function() {}, {
|
|
663
|
+
get(_target, prop) {
|
|
664
|
+
if (typeof prop === "symbol") return void 0;
|
|
665
|
+
return createProxyExpression(rootId, [...path, String(prop)], deps);
|
|
666
|
+
},
|
|
667
|
+
apply(_target, _thisArg, args) {
|
|
668
|
+
const callSource = `${source}(${args.map(serializeArgument).join(", ")})`;
|
|
669
|
+
const newDeps = new Set(deps);
|
|
670
|
+
collectDepsFromArgs(args, newDeps);
|
|
671
|
+
return createProxyExpressionWithSource(callSource, newDeps);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
setProxyMetadata(proxy, {
|
|
675
|
+
type: "expression",
|
|
676
|
+
path,
|
|
677
|
+
rootVariable: rootId,
|
|
678
|
+
source,
|
|
679
|
+
dependencies: deps
|
|
680
|
+
});
|
|
681
|
+
return proxy;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* 创建带完整源码的 Proxy(方法调用后)
|
|
685
|
+
* 可以继续链式访问和调用
|
|
686
|
+
*
|
|
687
|
+
* @param source - 完整的表达式源码
|
|
688
|
+
* @param deps - 依赖集合
|
|
689
|
+
* @returns Proxy 包装的 Expression
|
|
690
|
+
*/
|
|
691
|
+
function createProxyExpressionWithSource(source, deps) {
|
|
692
|
+
const proxy = new Proxy(function() {}, {
|
|
693
|
+
get(_target, prop) {
|
|
694
|
+
if (typeof prop === "symbol") return void 0;
|
|
695
|
+
return createProxyExpressionWithSource(`(${source}).${String(prop)}`, deps);
|
|
696
|
+
},
|
|
697
|
+
apply(_target, _thisArg, args) {
|
|
698
|
+
const callSource = `(${source})(${args.map(serializeArgument).join(", ")})`;
|
|
699
|
+
const newDeps = new Set(deps);
|
|
700
|
+
collectDepsFromArgs(args, newDeps);
|
|
701
|
+
return createProxyExpressionWithSource(callSource, newDeps);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
setProxyMetadata(proxy, {
|
|
705
|
+
type: "expression",
|
|
706
|
+
path: [source],
|
|
707
|
+
source,
|
|
708
|
+
dependencies: deps
|
|
709
|
+
});
|
|
710
|
+
return proxy;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/variable.ts
|
|
715
|
+
/**
|
|
716
|
+
* 跟踪每个 variable 的唯一 Symbol ID
|
|
717
|
+
*/
|
|
718
|
+
const variableIds = /* @__PURE__ */ new WeakMap();
|
|
719
|
+
/**
|
|
720
|
+
* 计数器用于生成唯一变量 ID
|
|
721
|
+
*/
|
|
722
|
+
let variableCounter = 0;
|
|
723
|
+
/**
|
|
724
|
+
* 创建一个类型化变量
|
|
725
|
+
* 返回 Proxy 对象,支持链式属性访问和方法调用
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```ts
|
|
729
|
+
* const x = variable<number>();
|
|
730
|
+
* const config = variable<{ timeout: number }>();
|
|
731
|
+
* const timeout = config.timeout; // Proxy expression
|
|
732
|
+
* ```
|
|
733
|
+
*/
|
|
734
|
+
function variable() {
|
|
735
|
+
const id = Symbol(`var_${variableCounter++}`);
|
|
736
|
+
const proxy = createProxyVariable(id);
|
|
737
|
+
variableIds.set(proxy, id);
|
|
738
|
+
return proxy;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* 获取 variable 的唯一 Symbol ID
|
|
742
|
+
*/
|
|
743
|
+
function getVariableId(variable) {
|
|
744
|
+
if (typeof variable !== "object" && typeof variable !== "function" || variable === null) return void 0;
|
|
745
|
+
return variableIds.get(variable);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* 生成变量占位符字符串
|
|
749
|
+
* 格式:$$VAR_var_N$$
|
|
750
|
+
*/
|
|
751
|
+
function getVariablePlaceholder(id) {
|
|
752
|
+
return `$$VAR_${id.description}$$`;
|
|
585
753
|
}
|
|
586
754
|
|
|
587
755
|
//#endregion
|
|
588
756
|
//#region src/compile.ts
|
|
757
|
+
const ALLOWED_GLOBALS = new Set([
|
|
758
|
+
"Math",
|
|
759
|
+
"JSON",
|
|
760
|
+
"Date",
|
|
761
|
+
"RegExp",
|
|
762
|
+
"Number",
|
|
763
|
+
"String",
|
|
764
|
+
"Boolean",
|
|
765
|
+
"Array",
|
|
766
|
+
"Object",
|
|
767
|
+
"undefined",
|
|
768
|
+
"NaN",
|
|
769
|
+
"Infinity",
|
|
770
|
+
"isNaN",
|
|
771
|
+
"isFinite",
|
|
772
|
+
"parseInt",
|
|
773
|
+
"parseFloat"
|
|
774
|
+
]);
|
|
589
775
|
/**
|
|
590
|
-
*
|
|
776
|
+
* 将占位符替换为实际变量名
|
|
777
|
+
*
|
|
778
|
+
* @param source - 包含占位符的表达式源码
|
|
779
|
+
* @param context - 变量名到值的映射
|
|
780
|
+
* @returns 替换后的源码
|
|
781
|
+
*/
|
|
782
|
+
function preprocessExpression(source, context) {
|
|
783
|
+
const descToName = /* @__PURE__ */ new Map();
|
|
784
|
+
for (const [name, value] of Object.entries(context)) {
|
|
785
|
+
const id = getVariableId(value);
|
|
786
|
+
if (id && id.description) descToName.set(id.description, name);
|
|
787
|
+
}
|
|
788
|
+
return source.replace(/\$\$VAR_([^$]+)\$\$/g, (match, desc) => {
|
|
789
|
+
const name = descToName.get(desc);
|
|
790
|
+
if (!name) throw new Error(`Unknown variable placeholder: ${match}`);
|
|
791
|
+
return name;
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 将 Proxy Expression 编译为可序列化的 JSON 结构
|
|
591
796
|
*
|
|
592
797
|
* @template TResult - 表达式结果类型
|
|
593
|
-
* @param expression -
|
|
798
|
+
* @param expression - Proxy Expression
|
|
594
799
|
* @param variables - 所有使用的变量定义
|
|
595
800
|
* @param options - 编译选项
|
|
596
801
|
* @returns 编译后的数据结构 [变量名列表, 表达式1, 表达式2, ...]
|
|
597
802
|
*
|
|
598
|
-
* @throws
|
|
803
|
+
* @throws 如果传入无效的表达式或未定义的变量引用
|
|
599
804
|
*
|
|
600
805
|
* @example
|
|
601
806
|
* ```ts
|
|
602
|
-
* const x = variable
|
|
603
|
-
* const y = variable
|
|
604
|
-
* const sum = expr({ x, y })
|
|
605
|
-
* const result = expr({ sum })
|
|
807
|
+
* const x = variable<number>()
|
|
808
|
+
* const y = variable<number>()
|
|
809
|
+
* const sum = expr({ x, y })("x + y")
|
|
810
|
+
* const result = expr({ sum, x })("sum * x")
|
|
606
811
|
* const compiled = compile(result, { x, y })
|
|
607
|
-
* // => [["x", "y"], "($0+$1)
|
|
812
|
+
* // => [["x", "y"], "($0+$1)*$0"]
|
|
608
813
|
* ```
|
|
609
814
|
*/
|
|
610
815
|
function compile(expression, variables, options = {}) {
|
|
611
|
-
const {
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (id === void 0) {
|
|
621
|
-
id = Symbol("expr");
|
|
622
|
-
exprIdMap.set(expr, id);
|
|
623
|
-
}
|
|
624
|
-
return id;
|
|
816
|
+
const { shortCircuit = true } = options;
|
|
817
|
+
const meta = getProxyMetadata(expression);
|
|
818
|
+
if (!meta?.source) throw new Error("Invalid expression: expected a Proxy Expression");
|
|
819
|
+
const source = preprocessExpression(meta.source, variables);
|
|
820
|
+
const variableOrder = [];
|
|
821
|
+
const variableToIndex = /* @__PURE__ */ new Map();
|
|
822
|
+
for (const name of Object.keys(variables)) if (!variableToIndex.has(name)) {
|
|
823
|
+
variableToIndex.set(name, variableOrder.length);
|
|
824
|
+
variableOrder.push(name);
|
|
625
825
|
}
|
|
626
|
-
const
|
|
627
|
-
const
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const id = Symbol(`var:${name}`);
|
|
632
|
-
const node = {
|
|
633
|
-
id,
|
|
634
|
-
tag: "variable",
|
|
635
|
-
schema: variable.schema
|
|
636
|
-
};
|
|
637
|
-
nodeMap.set(id, node);
|
|
638
|
-
variableNodes.set(name, node);
|
|
639
|
-
}
|
|
640
|
-
const exprNodes = /* @__PURE__ */ new Map();
|
|
641
|
-
function collectNodes(expr) {
|
|
642
|
-
const exprId = getExprId(expr);
|
|
643
|
-
if (visited.has(exprId)) return nodeMap.get(exprId);
|
|
644
|
-
if (visiting.has(exprId)) throw new Error("Circular dependency detected in expressions");
|
|
645
|
-
visiting.add(exprId);
|
|
646
|
-
const contextNodes = {};
|
|
647
|
-
for (const [key, contextItem] of Object.entries(expr.context)) {
|
|
648
|
-
const item = contextItem;
|
|
649
|
-
if (item._tag === "variable") {
|
|
650
|
-
const varNode = variableNodes.get(key);
|
|
651
|
-
if (!varNode) throw new Error(`Undefined variable reference: ${key}`);
|
|
652
|
-
contextNodes[key] = varNode;
|
|
653
|
-
} else if (item._tag === "expression") contextNodes[key] = collectNodes(item);
|
|
654
|
-
}
|
|
655
|
-
const node = {
|
|
656
|
-
id: exprId,
|
|
657
|
-
tag: "expression",
|
|
658
|
-
context: contextNodes,
|
|
659
|
-
source: expr.source
|
|
660
|
-
};
|
|
661
|
-
nodeMap.set(exprId, node);
|
|
662
|
-
exprNodes.set(exprId, node);
|
|
663
|
-
visited.add(exprId);
|
|
664
|
-
visiting.delete(exprId);
|
|
665
|
-
return node;
|
|
666
|
-
}
|
|
667
|
-
const rootNode = collectNodes(expression);
|
|
668
|
-
const sortedExprNodes = [];
|
|
669
|
-
const exprVisited = /* @__PURE__ */ new Set();
|
|
670
|
-
function topologicalSort(node) {
|
|
671
|
-
if (exprVisited.has(node.id)) return;
|
|
672
|
-
exprVisited.add(node.id);
|
|
673
|
-
if (node.tag === "expression" && node.context) for (const contextNode of Object.values(node.context)) topologicalSort(contextNode);
|
|
674
|
-
if (node.tag === "expression") sortedExprNodes.push(node);
|
|
675
|
-
}
|
|
676
|
-
topologicalSort(rootNode);
|
|
677
|
-
for (const [name, varNode] of variableNodes.entries()) if (!context.nodeToIndex.has(varNode.id)) {
|
|
678
|
-
context.nodeToIndex.set(varNode.id, context.variableOrder.length);
|
|
679
|
-
context.variableOrder.push(name);
|
|
680
|
-
}
|
|
681
|
-
const refCount = /* @__PURE__ */ new Map();
|
|
682
|
-
for (const exprNode of sortedExprNodes) refCount.set(exprNode.id, 0);
|
|
683
|
-
for (const exprNode of sortedExprNodes) if (exprNode.context) {
|
|
684
|
-
for (const contextNode of Object.values(exprNode.context)) if (contextNode.tag === "expression") refCount.set(contextNode.id, (refCount.get(contextNode.id) ?? 0) + 1);
|
|
685
|
-
}
|
|
686
|
-
function canInline(node) {
|
|
687
|
-
if (!inline) return false;
|
|
688
|
-
if (node.id === rootNode.id) return false;
|
|
689
|
-
return (refCount.get(node.id) ?? 0) === 1;
|
|
690
|
-
}
|
|
691
|
-
let exprIndex = 0;
|
|
692
|
-
for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
|
|
693
|
-
const index = context.variableOrder.length + exprIndex;
|
|
694
|
-
context.nodeToIndex.set(exprNode.id, index);
|
|
695
|
-
exprIndex++;
|
|
696
|
-
}
|
|
697
|
-
const nodeAstMap = /* @__PURE__ */ new Map();
|
|
698
|
-
for (const [, varNode] of variableNodes.entries()) {
|
|
699
|
-
const index = context.nodeToIndex.get(varNode.id);
|
|
700
|
-
nodeAstMap.set(varNode.id, {
|
|
826
|
+
const ast = parse(source);
|
|
827
|
+
const undefinedVars = [];
|
|
828
|
+
const transformed = transformIdentifiers(ast, (name) => {
|
|
829
|
+
const index = variableToIndex.get(name);
|
|
830
|
+
if (index !== void 0) return {
|
|
701
831
|
type: "Identifier",
|
|
702
832
|
name: `$${index}`
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const transformed = inlineTransform(parse(exprNode.source), (name) => {
|
|
710
|
-
const contextNode = exprNode.context[name];
|
|
711
|
-
if (!contextNode) return null;
|
|
712
|
-
if (contextNode.tag === "variable") return nodeAstMap.get(contextNode.id) ?? null;
|
|
713
|
-
else if (canInline(contextNode)) return nodeAstMap.get(contextNode.id) ?? null;
|
|
714
|
-
else return {
|
|
715
|
-
type: "Identifier",
|
|
716
|
-
name: `$${context.nodeToIndex.get(contextNode.id)}`
|
|
717
|
-
};
|
|
718
|
-
});
|
|
719
|
-
nodeAstMap.set(exprNode.id, transformed);
|
|
720
|
-
}
|
|
833
|
+
};
|
|
834
|
+
if (!ALLOWED_GLOBALS.has(name)) undefinedVars.push(name);
|
|
835
|
+
return null;
|
|
836
|
+
});
|
|
837
|
+
if (undefinedVars.length > 0) throw new Error(`Undefined variable(s): ${[...new Set(undefinedVars)].join(", ")}`);
|
|
838
|
+
const expressions = [];
|
|
721
839
|
if (shortCircuit) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (
|
|
726
|
-
|
|
727
|
-
const exprStr = generate(ast);
|
|
840
|
+
let nextIndex = variableOrder.length;
|
|
841
|
+
function compileAst(node) {
|
|
842
|
+
if (node.type === "BinaryExpr" && (node.operator === "||" || node.operator === "&&" || node.operator === "??")) return compileShortCircuit(node);
|
|
843
|
+
if (node.type === "ConditionalExpr") return compileConditional(node);
|
|
844
|
+
const exprStr = generate(node);
|
|
728
845
|
const idx = nextIndex++;
|
|
729
846
|
expressions.push(exprStr);
|
|
730
847
|
return idx;
|
|
@@ -776,141 +893,60 @@ function compile(expression, variables, options = {}) {
|
|
|
776
893
|
expressions.push(["phi"]);
|
|
777
894
|
return phiIdx;
|
|
778
895
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const ast = nodeAstMap.get(exprNode.id);
|
|
783
|
-
context.expressions.push(generate(ast));
|
|
784
|
-
}
|
|
785
|
-
return [context.variableOrder, ...context.expressions];
|
|
896
|
+
compileAst(transformed);
|
|
897
|
+
} else expressions.push(generate(transformed));
|
|
898
|
+
return [variableOrder, ...expressions];
|
|
786
899
|
}
|
|
787
900
|
/**
|
|
788
|
-
* 将 AST 中的标识符替换为对应的 AST
|
|
901
|
+
* 将 AST 中的标识符替换为对应的 AST 节点
|
|
789
902
|
*
|
|
790
903
|
* @param node - 要转换的 AST 节点
|
|
791
904
|
* @param getReplacementAst - 根据标识符名称返回替换的 AST 节点,返回 null 表示不替换
|
|
792
905
|
* @returns 转换后的 AST 节点
|
|
793
906
|
*/
|
|
794
|
-
function
|
|
907
|
+
function transformIdentifiers(node, getReplacementAst) {
|
|
795
908
|
switch (node.type) {
|
|
796
909
|
case "Identifier": return getReplacementAst(node.name) ?? node;
|
|
797
910
|
case "BinaryExpr": return {
|
|
798
911
|
...node,
|
|
799
|
-
left:
|
|
800
|
-
right:
|
|
912
|
+
left: transformIdentifiers(node.left, getReplacementAst),
|
|
913
|
+
right: transformIdentifiers(node.right, getReplacementAst)
|
|
801
914
|
};
|
|
802
915
|
case "UnaryExpr": return {
|
|
803
916
|
...node,
|
|
804
|
-
argument:
|
|
917
|
+
argument: transformIdentifiers(node.argument, getReplacementAst)
|
|
805
918
|
};
|
|
806
919
|
case "ConditionalExpr": return {
|
|
807
920
|
...node,
|
|
808
|
-
test:
|
|
809
|
-
consequent:
|
|
810
|
-
alternate:
|
|
921
|
+
test: transformIdentifiers(node.test, getReplacementAst),
|
|
922
|
+
consequent: transformIdentifiers(node.consequent, getReplacementAst),
|
|
923
|
+
alternate: transformIdentifiers(node.alternate, getReplacementAst)
|
|
811
924
|
};
|
|
812
925
|
case "MemberExpr": return {
|
|
813
926
|
...node,
|
|
814
|
-
object:
|
|
815
|
-
property: node.computed ?
|
|
927
|
+
object: transformIdentifiers(node.object, getReplacementAst),
|
|
928
|
+
property: node.computed ? transformIdentifiers(node.property, getReplacementAst) : node.property
|
|
816
929
|
};
|
|
817
930
|
case "CallExpr": return {
|
|
818
931
|
...node,
|
|
819
|
-
callee:
|
|
820
|
-
arguments: node.arguments.map((arg) =>
|
|
932
|
+
callee: transformIdentifiers(node.callee, getReplacementAst),
|
|
933
|
+
arguments: node.arguments.map((arg) => transformIdentifiers(arg, getReplacementAst))
|
|
821
934
|
};
|
|
822
935
|
case "ArrayExpr": return {
|
|
823
936
|
...node,
|
|
824
|
-
elements: node.elements.map((el) =>
|
|
937
|
+
elements: node.elements.map((el) => transformIdentifiers(el, getReplacementAst))
|
|
825
938
|
};
|
|
826
939
|
case "ObjectExpr": return {
|
|
827
940
|
...node,
|
|
828
941
|
properties: node.properties.map((prop) => ({
|
|
829
942
|
...prop,
|
|
830
|
-
key: prop.computed ?
|
|
831
|
-
value:
|
|
943
|
+
key: prop.computed ? transformIdentifiers(prop.key, getReplacementAst) : prop.key,
|
|
944
|
+
value: transformIdentifiers(prop.value, getReplacementAst)
|
|
832
945
|
}))
|
|
833
946
|
};
|
|
834
947
|
default: return node;
|
|
835
948
|
}
|
|
836
949
|
}
|
|
837
|
-
/**
|
|
838
|
-
* 允许在表达式中直接使用的全局对象
|
|
839
|
-
* 这些对象不需要在上下文中定义
|
|
840
|
-
*/
|
|
841
|
-
const ALLOWED_GLOBALS = new Set([
|
|
842
|
-
"Math",
|
|
843
|
-
"JSON",
|
|
844
|
-
"Number",
|
|
845
|
-
"String",
|
|
846
|
-
"Boolean",
|
|
847
|
-
"Array",
|
|
848
|
-
"Object",
|
|
849
|
-
"Date",
|
|
850
|
-
"RegExp",
|
|
851
|
-
"undefined",
|
|
852
|
-
"NaN",
|
|
853
|
-
"Infinity",
|
|
854
|
-
"isNaN",
|
|
855
|
-
"isFinite",
|
|
856
|
-
"parseInt",
|
|
857
|
-
"parseFloat"
|
|
858
|
-
]);
|
|
859
|
-
/**
|
|
860
|
-
* 从表达式源码中提取所有使用的变量名
|
|
861
|
-
* 通过 AST 解析实现精确提取
|
|
862
|
-
* 排除允许的全局对象
|
|
863
|
-
*
|
|
864
|
-
* @param source - 表达式源码字符串
|
|
865
|
-
* @returns 使用的变量名列表(去重,不含全局对象)
|
|
866
|
-
*
|
|
867
|
-
* @example
|
|
868
|
-
* ```ts
|
|
869
|
-
* extractVariableNames("x + y * Math.PI")
|
|
870
|
-
* // => ["x", "y"] // Math 被排除
|
|
871
|
-
* ```
|
|
872
|
-
*/
|
|
873
|
-
function extractVariableNames(source) {
|
|
874
|
-
const identifiers = collectIdentifiers(parse(source));
|
|
875
|
-
return Array.from(identifiers).filter((name) => !ALLOWED_GLOBALS.has(name));
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
//#endregion
|
|
879
|
-
//#region src/constant.ts
|
|
880
|
-
/**
|
|
881
|
-
* 创建一个编译期常量表达式
|
|
882
|
-
*
|
|
883
|
-
* 这是 `expr({})(JSON.stringify(value))` 的快速路径,
|
|
884
|
-
* 用于在表达式中嵌入静态值,避免在运行时传入或在多处重复编写。
|
|
885
|
-
*
|
|
886
|
-
* @template T - 常量值类型(必须是 JSON 可序列化的)
|
|
887
|
-
* @param value - 要嵌入的常量值
|
|
888
|
-
* @returns 返回一个 Expression 对象,其结果类型为 T
|
|
889
|
-
*
|
|
890
|
-
* @example
|
|
891
|
-
* ```ts
|
|
892
|
-
* // 创建一个数字常量
|
|
893
|
-
* const PI = constant(3.14159)
|
|
894
|
-
*
|
|
895
|
-
* // 创建一个字符串常量
|
|
896
|
-
* const greeting = constant("Hello, World!")
|
|
897
|
-
*
|
|
898
|
-
* // 创建一个对象常量
|
|
899
|
-
* const config = constant({ maxRetries: 3, timeout: 5000 })
|
|
900
|
-
*
|
|
901
|
-
* // 在表达式中使用常量
|
|
902
|
-
* const radius = variable(z.number())
|
|
903
|
-
* const area = expr({ PI, radius })("PI * radius * radius")
|
|
904
|
-
* ```
|
|
905
|
-
*/
|
|
906
|
-
function constant(value) {
|
|
907
|
-
return {
|
|
908
|
-
_tag: "expression",
|
|
909
|
-
context: {},
|
|
910
|
-
source: JSON.stringify(value),
|
|
911
|
-
_type: void 0
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
950
|
|
|
915
951
|
//#endregion
|
|
916
952
|
//#region src/evaluate.ts
|
|
@@ -1038,11 +1074,12 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1038
1074
|
//#endregion
|
|
1039
1075
|
//#region src/expr.ts
|
|
1040
1076
|
/**
|
|
1041
|
-
*
|
|
1077
|
+
* 创建表达式
|
|
1078
|
+
* 返回 Proxy Expression,可以继续链式调用
|
|
1042
1079
|
*
|
|
1043
|
-
* @template TContext -
|
|
1044
|
-
* @param context - 包含 Variable 或 Expression 的上下文对象
|
|
1045
|
-
* @returns 返回一个函数,该函数接收表达式源码字符串并返回 Expression
|
|
1080
|
+
* @template TContext - 表达式上下文类型
|
|
1081
|
+
* @param context - 包含 Variable 或 Proxy Expression 的上下文对象
|
|
1082
|
+
* @returns 返回一个函数,该函数接收表达式源码字符串并返回 Proxy Expression
|
|
1046
1083
|
*
|
|
1047
1084
|
* 类型系统会:
|
|
1048
1085
|
* 1. 验证表达式中使用的所有标识符都在 context 中定义
|
|
@@ -1050,8 +1087,8 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1050
1087
|
*
|
|
1051
1088
|
* @example
|
|
1052
1089
|
* ```ts
|
|
1053
|
-
* const x = variable
|
|
1054
|
-
* const y = variable
|
|
1090
|
+
* const x = variable<number>();
|
|
1091
|
+
* const y = variable<number>();
|
|
1055
1092
|
*
|
|
1056
1093
|
* // 自动推导返回类型为 number
|
|
1057
1094
|
* const sum = expr({ x, y })("x + y")
|
|
@@ -1065,42 +1102,76 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1065
1102
|
*/
|
|
1066
1103
|
function expr(context) {
|
|
1067
1104
|
return (source) => {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1105
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1106
|
+
const nameToId = /* @__PURE__ */ new Map();
|
|
1107
|
+
for (const [name, value] of Object.entries(context)) {
|
|
1108
|
+
const id = getVariableId(value);
|
|
1109
|
+
if (id) {
|
|
1110
|
+
deps.add(id);
|
|
1111
|
+
nameToId.set(name, id);
|
|
1112
|
+
} else {
|
|
1113
|
+
const meta = (typeof value === "object" || typeof value === "function") && value !== null ? getProxyMetadata(value) : void 0;
|
|
1114
|
+
if (meta?.dependencies) for (const dep of meta.dependencies) deps.add(dep);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
let transformedSource = source;
|
|
1118
|
+
for (const [name, id] of nameToId) {
|
|
1119
|
+
const escapedPlaceholder = getVariablePlaceholder(id).replace(/\$/g, "$$$$");
|
|
1120
|
+
const regex = new RegExp(`\\b${name}\\b`, "g");
|
|
1121
|
+
transformedSource = transformedSource.replace(regex, escapedPlaceholder);
|
|
1122
|
+
}
|
|
1123
|
+
for (const [name, value] of Object.entries(context)) if ((typeof value === "object" || typeof value === "function") && value !== null && !getVariableId(value)) {
|
|
1124
|
+
const meta = getProxyMetadata(value);
|
|
1125
|
+
if (meta?.source) {
|
|
1126
|
+
const regex = new RegExp(`\\b${name}\\b`, "g");
|
|
1127
|
+
const escapedSource = `(${meta.source})`.replace(/\$/g, "$$$$");
|
|
1128
|
+
transformedSource = transformedSource.replace(regex, escapedSource);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return createProxyExpressionWithSource(transformedSource, deps);
|
|
1074
1132
|
};
|
|
1075
1133
|
}
|
|
1076
1134
|
|
|
1077
1135
|
//#endregion
|
|
1078
|
-
//#region src/
|
|
1136
|
+
//#region src/template.ts
|
|
1079
1137
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1138
|
+
* 转义字符串字面量中的特殊字符
|
|
1139
|
+
*/
|
|
1140
|
+
function escapeStringLiteral(str) {
|
|
1141
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Tagged template 函数,用于创建包含变量的字符串表达式
|
|
1085
1145
|
*
|
|
1086
1146
|
* @example
|
|
1087
1147
|
* ```ts
|
|
1088
|
-
* const
|
|
1089
|
-
* const
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1148
|
+
* const name = variable<string>();
|
|
1149
|
+
* const count = variable<number>();
|
|
1150
|
+
*
|
|
1151
|
+
* const greeting = t`Hello, ${name}!`;
|
|
1152
|
+
* const message = t`You have ${count} items.`;
|
|
1153
|
+
*
|
|
1154
|
+
* const compiled = compile(greeting, { name });
|
|
1155
|
+
* const result = evaluate(compiled, { name: "Alice" }); // => "Hello, Alice!"
|
|
1094
1156
|
* ```
|
|
1095
1157
|
*/
|
|
1096
|
-
function
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1158
|
+
function t(strings, ...values) {
|
|
1159
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1160
|
+
collectDepsFromArgs(values, deps);
|
|
1161
|
+
const parts = [];
|
|
1162
|
+
for (let i = 0; i < strings.length; i++) {
|
|
1163
|
+
const str = strings[i];
|
|
1164
|
+
if (str.length > 0) parts.push(`"${escapeStringLiteral(str)}"`);
|
|
1165
|
+
if (i < values.length) {
|
|
1166
|
+
const serialized = serializeArgument(values[i]);
|
|
1167
|
+
parts.push(serialized);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (parts.length === 0) return createProxyExpressionWithSource("\"\"", deps);
|
|
1171
|
+
if (parts.length === 1) return createProxyExpressionWithSource(parts[0], deps);
|
|
1172
|
+
return createProxyExpressionWithSource("(" + parts.join(" + ") + ")", deps);
|
|
1102
1173
|
}
|
|
1103
1174
|
|
|
1104
1175
|
//#endregion
|
|
1105
|
-
export { compile,
|
|
1176
|
+
export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, t, variable };
|
|
1106
1177
|
//# sourceMappingURL=index.mjs.map
|