@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/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
- * 收集 AST 中所有使用的标识符名称
574
+ * 检查对象是否是任意 Proxy (variable 或 expression)
542
575
  */
543
- function collectIdentifiers(node) {
544
- const identifiers = /* @__PURE__ */ new Set();
545
- function visit(n) {
546
- switch (n.type) {
547
- case "Identifier":
548
- identifiers.add(n.name);
549
- break;
550
- case "BinaryExpr":
551
- visit(n.left);
552
- visit(n.right);
553
- break;
554
- case "UnaryExpr":
555
- visit(n.argument);
556
- break;
557
- case "ConditionalExpr":
558
- visit(n.test);
559
- visit(n.consequent);
560
- visit(n.alternate);
561
- break;
562
- case "MemberExpr":
563
- visit(n.object);
564
- if (n.computed) visit(n.property);
565
- break;
566
- case "CallExpr":
567
- visit(n.callee);
568
- n.arguments.forEach(visit);
569
- break;
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
- visit(node);
582
- return identifiers;
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
- * 将表达式树编译为可序列化的 JSON 结构
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(z.number())
601
- * const y = variable(z.number())
602
- * const sum = expr({ x, y })<number>("x + y")
603
- * const result = expr({ sum })<number>("sum * 2")
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)*2"] // 内联优化后
812
+ * // => [["x", "y"], "($0+$1)*$0"]
606
813
  * ```
607
814
  */
608
815
  function compile(expression, variables, options = {}) {
609
- const { inline = true, shortCircuit = true } = options;
610
- const context = {
611
- variableOrder: [],
612
- nodeToIndex: /* @__PURE__ */ new Map(),
613
- expressions: []
614
- };
615
- const exprIdMap = /* @__PURE__ */ new WeakMap();
616
- function getExprId(expr) {
617
- let id = exprIdMap.get(expr);
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 nodeAstMap = /* @__PURE__ */ new Map();
695
- for (const [, varNode] of variableNodes.entries()) {
696
- const index = context.nodeToIndex.get(varNode.id);
697
- 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 {
698
831
  type: "Identifier",
699
832
  name: `$${index}`
700
- });
701
- }
702
- for (const exprNode of sortedExprNodes) {
703
- if (!exprNode.context || !exprNode.source) throw new Error("Invalid expression node");
704
- const usedVariables = extractVariableNames(exprNode.source);
705
- for (const varName of usedVariables) if (!(varName in exprNode.context)) throw new Error(`Undefined variable reference: ${varName} (available: ${Object.keys(exprNode.context).join(", ")})`);
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
- const expressions = [];
720
- let nextIndex = context.variableOrder.length;
721
- function compileAst(ast) {
722
- if (ast.type === "BinaryExpr" && (ast.operator === "||" || ast.operator === "&&" || ast.operator === "??")) return compileShortCircuit(ast);
723
- if (ast.type === "ConditionalExpr") return compileConditional(ast);
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
- for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) compileAst(nodeAstMap.get(exprNode.id));
777
- context.expressions = expressions;
778
- } else for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
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 inlineTransform(node, getReplacementAst) {
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: inlineTransform(node.left, getReplacementAst),
797
- right: inlineTransform(node.right, getReplacementAst)
912
+ left: transformIdentifiers(node.left, getReplacementAst),
913
+ right: transformIdentifiers(node.right, getReplacementAst)
798
914
  };
799
915
  case "UnaryExpr": return {
800
916
  ...node,
801
- argument: inlineTransform(node.argument, getReplacementAst)
917
+ argument: transformIdentifiers(node.argument, getReplacementAst)
802
918
  };
803
919
  case "ConditionalExpr": return {
804
920
  ...node,
805
- test: inlineTransform(node.test, getReplacementAst),
806
- consequent: inlineTransform(node.consequent, getReplacementAst),
807
- alternate: inlineTransform(node.alternate, getReplacementAst)
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: inlineTransform(node.object, getReplacementAst),
812
- property: node.computed ? inlineTransform(node.property, getReplacementAst) : node.property
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: inlineTransform(node.callee, getReplacementAst),
817
- arguments: node.arguments.map((arg) => inlineTransform(arg, getReplacementAst))
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) => inlineTransform(el, getReplacementAst))
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 ? inlineTransform(prop.key, getReplacementAst) : prop.key,
828
- value: inlineTransform(prop.value, getReplacementAst)
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 - 表达式上下文类型(Variable 或 Expression 的映射)
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(z.number())
1051
- * const y = variable(z.number())
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
- return {
1066
- _tag: "expression",
1067
- context,
1068
- source,
1069
- _type: void 0
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/variable.ts
1136
+ //#region src/template.ts
1076
1137
  /**
1077
- * 创建一个类型化变量
1078
- *
1079
- * @template T - 变量的值类型
1080
- * @returns 返回 Variable 对象,包含 _tag 标记
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 x = variable<number>()
1085
- * const name = variable<string>()
1086
- * const config = variable<{
1087
- * count: number,
1088
- * enabled: boolean
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 variable() {
1093
- return {
1094
- _tag: "variable",
1095
- _type: void 0
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, constant, evaluate, expr, variable };
1176
+ export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, t, variable };
1101
1177
  //# sourceMappingURL=index.mjs.map