@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/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
- * 收集 AST 中所有使用的标识符名称
584
+ * 使用 Symbol.description 生成占位符
585
+ * 用于在表达式源码中标识变量
544
586
  */
545
- function collectIdentifiers(node) {
546
- const identifiers = /* @__PURE__ */ new Set();
547
- function visit(n) {
548
- switch (n.type) {
549
- case "Identifier":
550
- identifiers.add(n.name);
551
- break;
552
- case "BinaryExpr":
553
- visit(n.left);
554
- visit(n.right);
555
- break;
556
- case "UnaryExpr":
557
- visit(n.argument);
558
- break;
559
- case "ConditionalExpr":
560
- visit(n.test);
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
- visit(node);
584
- 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}$$`;
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
- * 将表达式树编译为可序列化的 JSON 结构
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(z.number())
603
- * const y = variable(z.number())
604
- * const sum = expr({ x, y })<number>("x + y")
605
- * 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")
606
811
  * const compiled = compile(result, { x, y })
607
- * // => [["x", "y"], "($0+$1)*2"] // 内联优化后
812
+ * // => [["x", "y"], "($0+$1)*$0"]
608
813
  * ```
609
814
  */
610
815
  function compile(expression, variables, options = {}) {
611
- const { inline = true, shortCircuit = true } = options;
612
- const context = {
613
- variableOrder: [],
614
- nodeToIndex: /* @__PURE__ */ new Map(),
615
- expressions: []
616
- };
617
- const exprIdMap = /* @__PURE__ */ new WeakMap();
618
- function getExprId(expr) {
619
- let id = exprIdMap.get(expr);
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 nodeMap = /* @__PURE__ */ new Map();
627
- const variableNodes = /* @__PURE__ */ new Map();
628
- const visited = /* @__PURE__ */ new Set();
629
- const visiting = /* @__PURE__ */ new Set();
630
- for (const [name, variable] of Object.entries(variables)) {
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
- for (const exprNode of sortedExprNodes) {
706
- if (!exprNode.context || !exprNode.source) throw new Error("Invalid expression node");
707
- const usedVariables = extractVariableNames(exprNode.source);
708
- for (const varName of usedVariables) if (!(varName in exprNode.context)) throw new Error(`Undefined variable reference: ${varName} (available: ${Object.keys(exprNode.context).join(", ")})`);
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
- const expressions = [];
723
- let nextIndex = context.variableOrder.length;
724
- function compileAst(ast) {
725
- if (ast.type === "BinaryExpr" && (ast.operator === "||" || ast.operator === "&&" || ast.operator === "??")) return compileShortCircuit(ast);
726
- if (ast.type === "ConditionalExpr") return compileConditional(ast);
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
- for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) compileAst(nodeAstMap.get(exprNode.id));
780
- context.expressions = expressions;
781
- } else for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
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 inlineTransform(node, getReplacementAst) {
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: inlineTransform(node.left, getReplacementAst),
800
- right: inlineTransform(node.right, getReplacementAst)
912
+ left: transformIdentifiers(node.left, getReplacementAst),
913
+ right: transformIdentifiers(node.right, getReplacementAst)
801
914
  };
802
915
  case "UnaryExpr": return {
803
916
  ...node,
804
- argument: inlineTransform(node.argument, getReplacementAst)
917
+ argument: transformIdentifiers(node.argument, getReplacementAst)
805
918
  };
806
919
  case "ConditionalExpr": return {
807
920
  ...node,
808
- test: inlineTransform(node.test, getReplacementAst),
809
- consequent: inlineTransform(node.consequent, getReplacementAst),
810
- alternate: inlineTransform(node.alternate, getReplacementAst)
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: inlineTransform(node.object, getReplacementAst),
815
- 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
816
929
  };
817
930
  case "CallExpr": return {
818
931
  ...node,
819
- callee: inlineTransform(node.callee, getReplacementAst),
820
- arguments: node.arguments.map((arg) => inlineTransform(arg, getReplacementAst))
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) => inlineTransform(el, getReplacementAst))
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 ? inlineTransform(prop.key, getReplacementAst) : prop.key,
831
- value: inlineTransform(prop.value, getReplacementAst)
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 - 表达式上下文类型(Variable 或 Expression 的映射)
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(z.number())
1054
- * const y = variable(z.number())
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
- return {
1069
- _tag: "expression",
1070
- context,
1071
- source,
1072
- _type: void 0
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/variable.ts
1136
+ //#region src/template.ts
1079
1137
  /**
1080
- * 创建一个类型化变量
1081
- *
1082
- * @template T - Zod schema 类型
1083
- * @param schema - Zod schema 对象,定义变量的类型和验证规则
1084
- * @returns 返回 Variable 对象,包含 _tag 标记和 schema
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 x = variable(z.number())
1089
- * const name = variable(z.string())
1090
- * const config = variable(z.object({
1091
- * count: z.number(),
1092
- * enabled: z.boolean()
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 variable(schema) {
1097
- return {
1098
- _tag: "variable",
1099
- schema,
1100
- _type: void 0
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, constant, evaluate, expr, variable };
1176
+ export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, t, variable };
1106
1177
  //# sourceMappingURL=index.mjs.map