@codehz/json-expr 0.3.0 → 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 +82 -94
- package/dist/index.mjs +363 -287
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -537,191 +537,311 @@ function needsParens(child, parent, position) {
|
|
|
537
537
|
}
|
|
538
538
|
return false;
|
|
539
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
|
+
}
|
|
540
573
|
/**
|
|
541
|
-
*
|
|
574
|
+
* 检查对象是否是任意 Proxy (variable 或 expression)
|
|
542
575
|
*/
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
case "ArrayExpr":
|
|
571
|
-
n.elements.forEach(visit);
|
|
572
|
-
break;
|
|
573
|
-
case "ObjectExpr":
|
|
574
|
-
n.properties.forEach((prop) => {
|
|
575
|
-
if (prop.computed) visit(prop.key);
|
|
576
|
-
visit(prop.value);
|
|
577
|
-
});
|
|
578
|
-
break;
|
|
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
|
|
583
|
+
/**
|
|
584
|
+
* 使用 Symbol.description 生成占位符
|
|
585
|
+
* 用于在表达式源码中标识变量
|
|
586
|
+
*/
|
|
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);
|
|
579
603
|
}
|
|
580
604
|
}
|
|
581
|
-
|
|
582
|
-
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}$$`;
|
|
583
753
|
}
|
|
584
754
|
|
|
585
755
|
//#endregion
|
|
586
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
|
+
]);
|
|
775
|
+
/**
|
|
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
|
+
}
|
|
587
794
|
/**
|
|
588
|
-
*
|
|
795
|
+
* 将 Proxy Expression 编译为可序列化的 JSON 结构
|
|
589
796
|
*
|
|
590
797
|
* @template TResult - 表达式结果类型
|
|
591
|
-
* @param expression -
|
|
798
|
+
* @param expression - Proxy Expression
|
|
592
799
|
* @param variables - 所有使用的变量定义
|
|
593
800
|
* @param options - 编译选项
|
|
594
801
|
* @returns 编译后的数据结构 [变量名列表, 表达式1, 表达式2, ...]
|
|
595
802
|
*
|
|
596
|
-
* @throws
|
|
803
|
+
* @throws 如果传入无效的表达式或未定义的变量引用
|
|
597
804
|
*
|
|
598
805
|
* @example
|
|
599
806
|
* ```ts
|
|
600
|
-
* const x = variable
|
|
601
|
-
* const y = variable
|
|
602
|
-
* const sum = expr({ x, y })
|
|
603
|
-
* 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")
|
|
604
811
|
* const compiled = compile(result, { x, y })
|
|
605
|
-
* // => [["x", "y"], "($0+$1)
|
|
812
|
+
* // => [["x", "y"], "($0+$1)*$0"]
|
|
606
813
|
* ```
|
|
607
814
|
*/
|
|
608
815
|
function compile(expression, variables, options = {}) {
|
|
609
|
-
const {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (id === void 0) {
|
|
619
|
-
id = Symbol("expr");
|
|
620
|
-
exprIdMap.set(expr, id);
|
|
621
|
-
}
|
|
622
|
-
return id;
|
|
623
|
-
}
|
|
624
|
-
const nodeMap = /* @__PURE__ */ new Map();
|
|
625
|
-
const variableNodes = /* @__PURE__ */ new Map();
|
|
626
|
-
const visited = /* @__PURE__ */ new Set();
|
|
627
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
628
|
-
for (const [name] of Object.entries(variables)) {
|
|
629
|
-
const id = Symbol(`var:${name}`);
|
|
630
|
-
const node = {
|
|
631
|
-
id,
|
|
632
|
-
tag: "variable"
|
|
633
|
-
};
|
|
634
|
-
nodeMap.set(id, node);
|
|
635
|
-
variableNodes.set(name, node);
|
|
636
|
-
}
|
|
637
|
-
const exprNodes = /* @__PURE__ */ new Map();
|
|
638
|
-
function collectNodes(expr) {
|
|
639
|
-
const exprId = getExprId(expr);
|
|
640
|
-
if (visited.has(exprId)) return nodeMap.get(exprId);
|
|
641
|
-
if (visiting.has(exprId)) throw new Error("Circular dependency detected in expressions");
|
|
642
|
-
visiting.add(exprId);
|
|
643
|
-
const contextNodes = {};
|
|
644
|
-
for (const [key, contextItem] of Object.entries(expr.context)) {
|
|
645
|
-
const item = contextItem;
|
|
646
|
-
if (item._tag === "variable") {
|
|
647
|
-
const varNode = variableNodes.get(key);
|
|
648
|
-
if (!varNode) throw new Error(`Undefined variable reference: ${key}`);
|
|
649
|
-
contextNodes[key] = varNode;
|
|
650
|
-
} else if (item._tag === "expression") contextNodes[key] = collectNodes(item);
|
|
651
|
-
}
|
|
652
|
-
const node = {
|
|
653
|
-
id: exprId,
|
|
654
|
-
tag: "expression",
|
|
655
|
-
context: contextNodes,
|
|
656
|
-
source: expr.source
|
|
657
|
-
};
|
|
658
|
-
nodeMap.set(exprId, node);
|
|
659
|
-
exprNodes.set(exprId, node);
|
|
660
|
-
visited.add(exprId);
|
|
661
|
-
visiting.delete(exprId);
|
|
662
|
-
return node;
|
|
663
|
-
}
|
|
664
|
-
const rootNode = collectNodes(expression);
|
|
665
|
-
const sortedExprNodes = [];
|
|
666
|
-
const exprVisited = /* @__PURE__ */ new Set();
|
|
667
|
-
function topologicalSort(node) {
|
|
668
|
-
if (exprVisited.has(node.id)) return;
|
|
669
|
-
exprVisited.add(node.id);
|
|
670
|
-
if (node.tag === "expression" && node.context) for (const contextNode of Object.values(node.context)) topologicalSort(contextNode);
|
|
671
|
-
if (node.tag === "expression") sortedExprNodes.push(node);
|
|
672
|
-
}
|
|
673
|
-
topologicalSort(rootNode);
|
|
674
|
-
for (const [name, varNode] of variableNodes.entries()) if (!context.nodeToIndex.has(varNode.id)) {
|
|
675
|
-
context.nodeToIndex.set(varNode.id, context.variableOrder.length);
|
|
676
|
-
context.variableOrder.push(name);
|
|
677
|
-
}
|
|
678
|
-
const refCount = /* @__PURE__ */ new Map();
|
|
679
|
-
for (const exprNode of sortedExprNodes) refCount.set(exprNode.id, 0);
|
|
680
|
-
for (const exprNode of sortedExprNodes) if (exprNode.context) {
|
|
681
|
-
for (const contextNode of Object.values(exprNode.context)) if (contextNode.tag === "expression") refCount.set(contextNode.id, (refCount.get(contextNode.id) ?? 0) + 1);
|
|
682
|
-
}
|
|
683
|
-
function canInline(node) {
|
|
684
|
-
if (!inline) return false;
|
|
685
|
-
if (node.id === rootNode.id) return false;
|
|
686
|
-
return (refCount.get(node.id) ?? 0) === 1;
|
|
687
|
-
}
|
|
688
|
-
let exprIndex = 0;
|
|
689
|
-
for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
|
|
690
|
-
const index = context.variableOrder.length + exprIndex;
|
|
691
|
-
context.nodeToIndex.set(exprNode.id, index);
|
|
692
|
-
exprIndex++;
|
|
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);
|
|
693
825
|
}
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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 {
|
|
698
831
|
type: "Identifier",
|
|
699
832
|
name: `$${index}`
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const transformed = inlineTransform(parse(exprNode.source), (name) => {
|
|
707
|
-
const contextNode = exprNode.context[name];
|
|
708
|
-
if (!contextNode) return null;
|
|
709
|
-
if (contextNode.tag === "variable") return nodeAstMap.get(contextNode.id) ?? null;
|
|
710
|
-
else if (canInline(contextNode)) return nodeAstMap.get(contextNode.id) ?? null;
|
|
711
|
-
else return {
|
|
712
|
-
type: "Identifier",
|
|
713
|
-
name: `$${context.nodeToIndex.get(contextNode.id)}`
|
|
714
|
-
};
|
|
715
|
-
});
|
|
716
|
-
nodeAstMap.set(exprNode.id, transformed);
|
|
717
|
-
}
|
|
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 = [];
|
|
718
839
|
if (shortCircuit) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
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);
|
|
725
845
|
const idx = nextIndex++;
|
|
726
846
|
expressions.push(exprStr);
|
|
727
847
|
return idx;
|
|
@@ -773,141 +893,60 @@ function compile(expression, variables, options = {}) {
|
|
|
773
893
|
expressions.push(["phi"]);
|
|
774
894
|
return phiIdx;
|
|
775
895
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const ast = nodeAstMap.get(exprNode.id);
|
|
780
|
-
context.expressions.push(generate(ast));
|
|
781
|
-
}
|
|
782
|
-
return [context.variableOrder, ...context.expressions];
|
|
896
|
+
compileAst(transformed);
|
|
897
|
+
} else expressions.push(generate(transformed));
|
|
898
|
+
return [variableOrder, ...expressions];
|
|
783
899
|
}
|
|
784
900
|
/**
|
|
785
|
-
* 将 AST 中的标识符替换为对应的 AST
|
|
901
|
+
* 将 AST 中的标识符替换为对应的 AST 节点
|
|
786
902
|
*
|
|
787
903
|
* @param node - 要转换的 AST 节点
|
|
788
904
|
* @param getReplacementAst - 根据标识符名称返回替换的 AST 节点,返回 null 表示不替换
|
|
789
905
|
* @returns 转换后的 AST 节点
|
|
790
906
|
*/
|
|
791
|
-
function
|
|
907
|
+
function transformIdentifiers(node, getReplacementAst) {
|
|
792
908
|
switch (node.type) {
|
|
793
909
|
case "Identifier": return getReplacementAst(node.name) ?? node;
|
|
794
910
|
case "BinaryExpr": return {
|
|
795
911
|
...node,
|
|
796
|
-
left:
|
|
797
|
-
right:
|
|
912
|
+
left: transformIdentifiers(node.left, getReplacementAst),
|
|
913
|
+
right: transformIdentifiers(node.right, getReplacementAst)
|
|
798
914
|
};
|
|
799
915
|
case "UnaryExpr": return {
|
|
800
916
|
...node,
|
|
801
|
-
argument:
|
|
917
|
+
argument: transformIdentifiers(node.argument, getReplacementAst)
|
|
802
918
|
};
|
|
803
919
|
case "ConditionalExpr": return {
|
|
804
920
|
...node,
|
|
805
|
-
test:
|
|
806
|
-
consequent:
|
|
807
|
-
alternate:
|
|
921
|
+
test: transformIdentifiers(node.test, getReplacementAst),
|
|
922
|
+
consequent: transformIdentifiers(node.consequent, getReplacementAst),
|
|
923
|
+
alternate: transformIdentifiers(node.alternate, getReplacementAst)
|
|
808
924
|
};
|
|
809
925
|
case "MemberExpr": return {
|
|
810
926
|
...node,
|
|
811
|
-
object:
|
|
812
|
-
property: node.computed ?
|
|
927
|
+
object: transformIdentifiers(node.object, getReplacementAst),
|
|
928
|
+
property: node.computed ? transformIdentifiers(node.property, getReplacementAst) : node.property
|
|
813
929
|
};
|
|
814
930
|
case "CallExpr": return {
|
|
815
931
|
...node,
|
|
816
|
-
callee:
|
|
817
|
-
arguments: node.arguments.map((arg) =>
|
|
932
|
+
callee: transformIdentifiers(node.callee, getReplacementAst),
|
|
933
|
+
arguments: node.arguments.map((arg) => transformIdentifiers(arg, getReplacementAst))
|
|
818
934
|
};
|
|
819
935
|
case "ArrayExpr": return {
|
|
820
936
|
...node,
|
|
821
|
-
elements: node.elements.map((el) =>
|
|
937
|
+
elements: node.elements.map((el) => transformIdentifiers(el, getReplacementAst))
|
|
822
938
|
};
|
|
823
939
|
case "ObjectExpr": return {
|
|
824
940
|
...node,
|
|
825
941
|
properties: node.properties.map((prop) => ({
|
|
826
942
|
...prop,
|
|
827
|
-
key: prop.computed ?
|
|
828
|
-
value:
|
|
943
|
+
key: prop.computed ? transformIdentifiers(prop.key, getReplacementAst) : prop.key,
|
|
944
|
+
value: transformIdentifiers(prop.value, getReplacementAst)
|
|
829
945
|
}))
|
|
830
946
|
};
|
|
831
947
|
default: return node;
|
|
832
948
|
}
|
|
833
949
|
}
|
|
834
|
-
/**
|
|
835
|
-
* 允许在表达式中直接使用的全局对象
|
|
836
|
-
* 这些对象不需要在上下文中定义
|
|
837
|
-
*/
|
|
838
|
-
const ALLOWED_GLOBALS = new Set([
|
|
839
|
-
"Math",
|
|
840
|
-
"JSON",
|
|
841
|
-
"Number",
|
|
842
|
-
"String",
|
|
843
|
-
"Boolean",
|
|
844
|
-
"Array",
|
|
845
|
-
"Object",
|
|
846
|
-
"Date",
|
|
847
|
-
"RegExp",
|
|
848
|
-
"undefined",
|
|
849
|
-
"NaN",
|
|
850
|
-
"Infinity",
|
|
851
|
-
"isNaN",
|
|
852
|
-
"isFinite",
|
|
853
|
-
"parseInt",
|
|
854
|
-
"parseFloat"
|
|
855
|
-
]);
|
|
856
|
-
/**
|
|
857
|
-
* 从表达式源码中提取所有使用的变量名
|
|
858
|
-
* 通过 AST 解析实现精确提取
|
|
859
|
-
* 排除允许的全局对象
|
|
860
|
-
*
|
|
861
|
-
* @param source - 表达式源码字符串
|
|
862
|
-
* @returns 使用的变量名列表(去重,不含全局对象)
|
|
863
|
-
*
|
|
864
|
-
* @example
|
|
865
|
-
* ```ts
|
|
866
|
-
* extractVariableNames("x + y * Math.PI")
|
|
867
|
-
* // => ["x", "y"] // Math 被排除
|
|
868
|
-
* ```
|
|
869
|
-
*/
|
|
870
|
-
function extractVariableNames(source) {
|
|
871
|
-
const identifiers = collectIdentifiers(parse(source));
|
|
872
|
-
return Array.from(identifiers).filter((name) => !ALLOWED_GLOBALS.has(name));
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
//#endregion
|
|
876
|
-
//#region src/constant.ts
|
|
877
|
-
/**
|
|
878
|
-
* 创建一个编译期常量表达式
|
|
879
|
-
*
|
|
880
|
-
* 这是 `expr({})(JSON.stringify(value))` 的快速路径,
|
|
881
|
-
* 用于在表达式中嵌入静态值,避免在运行时传入或在多处重复编写。
|
|
882
|
-
*
|
|
883
|
-
* @template T - 常量值类型(必须是 JSON 可序列化的)
|
|
884
|
-
* @param value - 要嵌入的常量值
|
|
885
|
-
* @returns 返回一个 Expression 对象,其结果类型为 T
|
|
886
|
-
*
|
|
887
|
-
* @example
|
|
888
|
-
* ```ts
|
|
889
|
-
* // 创建一个数字常量
|
|
890
|
-
* const PI = constant(3.14159)
|
|
891
|
-
*
|
|
892
|
-
* // 创建一个字符串常量
|
|
893
|
-
* const greeting = constant("Hello, World!")
|
|
894
|
-
*
|
|
895
|
-
* // 创建一个对象常量
|
|
896
|
-
* const config = constant({ maxRetries: 3, timeout: 5000 })
|
|
897
|
-
*
|
|
898
|
-
* // 在表达式中使用常量
|
|
899
|
-
* const radius = variable(z.number())
|
|
900
|
-
* const area = expr({ PI, radius })("PI * radius * radius")
|
|
901
|
-
* ```
|
|
902
|
-
*/
|
|
903
|
-
function constant(value) {
|
|
904
|
-
return {
|
|
905
|
-
_tag: "expression",
|
|
906
|
-
context: {},
|
|
907
|
-
source: JSON.stringify(value),
|
|
908
|
-
_type: void 0
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
950
|
|
|
912
951
|
//#endregion
|
|
913
952
|
//#region src/evaluate.ts
|
|
@@ -1035,11 +1074,12 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1035
1074
|
//#endregion
|
|
1036
1075
|
//#region src/expr.ts
|
|
1037
1076
|
/**
|
|
1038
|
-
*
|
|
1077
|
+
* 创建表达式
|
|
1078
|
+
* 返回 Proxy Expression,可以继续链式调用
|
|
1039
1079
|
*
|
|
1040
|
-
* @template TContext -
|
|
1041
|
-
* @param context - 包含 Variable 或 Expression 的上下文对象
|
|
1042
|
-
* @returns 返回一个函数,该函数接收表达式源码字符串并返回 Expression
|
|
1080
|
+
* @template TContext - 表达式上下文类型
|
|
1081
|
+
* @param context - 包含 Variable 或 Proxy Expression 的上下文对象
|
|
1082
|
+
* @returns 返回一个函数,该函数接收表达式源码字符串并返回 Proxy Expression
|
|
1043
1083
|
*
|
|
1044
1084
|
* 类型系统会:
|
|
1045
1085
|
* 1. 验证表达式中使用的所有标识符都在 context 中定义
|
|
@@ -1047,8 +1087,8 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1047
1087
|
*
|
|
1048
1088
|
* @example
|
|
1049
1089
|
* ```ts
|
|
1050
|
-
* const x = variable
|
|
1051
|
-
* const y = variable
|
|
1090
|
+
* const x = variable<number>();
|
|
1091
|
+
* const y = variable<number>();
|
|
1052
1092
|
*
|
|
1053
1093
|
* // 自动推导返回类型为 number
|
|
1054
1094
|
* const sum = expr({ x, y })("x + y")
|
|
@@ -1062,40 +1102,76 @@ function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
|
|
|
1062
1102
|
*/
|
|
1063
1103
|
function expr(context) {
|
|
1064
1104
|
return (source) => {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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);
|
|
1071
1132
|
};
|
|
1072
1133
|
}
|
|
1073
1134
|
|
|
1074
1135
|
//#endregion
|
|
1075
|
-
//#region src/
|
|
1136
|
+
//#region src/template.ts
|
|
1076
1137
|
/**
|
|
1077
|
-
*
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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 函数,用于创建包含变量的字符串表达式
|
|
1081
1145
|
*
|
|
1082
1146
|
* @example
|
|
1083
1147
|
* ```ts
|
|
1084
|
-
* const
|
|
1085
|
-
* const
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
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!"
|
|
1090
1156
|
* ```
|
|
1091
1157
|
*/
|
|
1092
|
-
function
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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);
|
|
1097
1173
|
}
|
|
1098
1174
|
|
|
1099
1175
|
//#endregion
|
|
1100
|
-
export { compile,
|
|
1176
|
+
export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, t, variable };
|
|
1101
1177
|
//# sourceMappingURL=index.mjs.map
|