@codehz/json-expr 0.5.5 → 0.6.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
@@ -333,27 +333,6 @@ function setProxyMetadata(proxy, metadata) {
333
333
  function getProxyMetadata(proxy) {
334
334
  return proxyMetadata.get(proxy);
335
335
  }
336
- /**
337
- * 检查对象是否是 Proxy variable
338
- */
339
- function isProxyVariable(obj) {
340
- if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
341
- return proxyMetadata.get(obj)?.type === "variable";
342
- }
343
- /**
344
- * 检查对象是否是 Proxy expression
345
- */
346
- function isProxyExpression(obj) {
347
- if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
348
- return proxyMetadata.get(obj)?.type === "expression";
349
- }
350
- /**
351
- * 检查对象是否是任意 Proxy (variable 或 expression)
352
- */
353
- function isProxy(obj) {
354
- if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false;
355
- return proxyMetadata.has(obj);
356
- }
357
336
 
358
337
  //#endregion
359
338
  //#region src/proxy-variable.ts
@@ -686,11 +665,10 @@ const ALLOWED_GLOBALS = new Set([
686
665
  * const sum = expr({ x, y })("x + y")
687
666
  * const result = expr({ sum, x })("sum * x")
688
667
  * const compiled = compile(result, { x, y })
689
- * // => [["x", "y"], "($0+$1)*$0"]
668
+ * // => [["x", "y"], "($[0]+$[1])*$[0]"]
690
669
  * ```
691
670
  */
692
- function compile(expression, variables, options = {}) {
693
- const { shortCircuit = true } = options;
671
+ function compile(expression, variables, _options = {}) {
694
672
  const ast = serializeArgumentToAST(expression);
695
673
  const variableOrder = [];
696
674
  const variableToIndex = /* @__PURE__ */ new Map();
@@ -708,13 +686,14 @@ function compile(expression, variables, options = {}) {
708
686
  if (!name) return null;
709
687
  const index = variableToIndex.get(name);
710
688
  if (index === void 0) return null;
711
- return `$${index}`;
689
+ return `$[${index}]`;
712
690
  });
713
691
  const undefinedVars = [];
714
692
  const transformed = transformIdentifiers(placeholderTransformed, (name) => {
715
- if (name.startsWith("$") && /^\$\d+$/.test(name)) return name;
693
+ if (name.startsWith("$[") && /^\$\[\d+\]$/.test(name)) return name;
694
+ if (/^_\d+$/.test(name)) return name;
716
695
  const index = variableToIndex.get(name);
717
- if (index !== void 0) return `$${index}`;
696
+ if (index !== void 0) return `$[${index}]`;
718
697
  if (!ALLOWED_GLOBALS.has(name)) undefinedVars.push(name);
719
698
  return name;
720
699
  });
@@ -722,61 +701,142 @@ function compile(expression, variables, options = {}) {
722
701
  const uniqueVars = [...new Set(undefinedVars)];
723
702
  throw new Error(`Undefined variable(s): ${uniqueVars.join(", ")}`);
724
703
  }
725
- const expressions = [];
726
- if (shortCircuit) {
727
- let nextIndex = variableOrder.length;
728
- function compileAst(node) {
729
- if (node.type === "BinaryExpr" && (node.operator === "||" || node.operator === "&&" || node.operator === "??")) return compileShortCircuit(node);
730
- if (node.type === "ConditionalExpr") return compileConditional(node);
731
- const exprStr = generate(node);
732
- expressions.push(exprStr);
733
- return nextIndex++;
734
- }
735
- function compileShortCircuit(node) {
736
- const leftIdx = compileAst(node.left);
737
- const branchConditions = {
738
- "||": `$${leftIdx}`,
739
- "&&": `!$${leftIdx}`,
740
- "??": `$${leftIdx}!=null`
741
- };
742
- const branchIdx = expressions.length;
743
- expressions.push([
744
- "br",
745
- branchConditions[node.operator],
746
- 0
747
- ]);
748
- nextIndex++;
749
- compileAst(node.right);
750
- const skipCount = expressions.length - branchIdx - 1;
751
- expressions[branchIdx][2] = skipCount;
752
- const phiIdx = nextIndex++;
753
- expressions.push(["phi"]);
754
- return phiIdx;
755
- }
756
- function compileConditional(node) {
757
- const testIdx = compileAst(node.test);
758
- const branchIdx = expressions.length;
759
- expressions.push([
760
- "br",
761
- `$${testIdx}`,
762
- 0
763
- ]);
764
- nextIndex++;
765
- compileAst(node.alternate);
766
- const jmpIdx = expressions.length;
767
- expressions.push(["jmp", 0]);
768
- nextIndex++;
769
- compileAst(node.consequent);
770
- const thenEndIdx = expressions.length;
771
- expressions[branchIdx][2] = jmpIdx - branchIdx;
772
- expressions[jmpIdx][1] = thenEndIdx - jmpIdx - 1;
773
- const phiIdx = nextIndex++;
774
- expressions.push(["phi"]);
775
- return phiIdx;
776
- }
777
- compileAst(transformed);
778
- } else expressions.push(generate(transformed));
779
- return [variableOrder, ...expressions];
704
+ const topLevelExprs = [];
705
+ compileAst(transformed, {
706
+ nextParamIndex: 0,
707
+ expressionStack: [topLevelExprs],
708
+ nextIndex: variableOrder.length,
709
+ variableCount: variableOrder.length
710
+ });
711
+ return [variableOrder, ...topLevelExprs];
712
+ }
713
+ function currentExprs(ctx) {
714
+ return ctx.expressionStack[ctx.expressionStack.length - 1];
715
+ }
716
+ /**
717
+ * 提取 AST 中所有 ArrowFunctionExpr 节点,编译为 FnNode,
718
+ * 并将原始位置替换为 $[N] 标识符引用。
719
+ */
720
+ function extractAndCompileArrowFunctions(node, ctx) {
721
+ switch (node.type) {
722
+ case "ArrowFunctionExpr": return {
723
+ type: "Identifier",
724
+ name: `$[${compileArrowFunction(node, ctx)}]`
725
+ };
726
+ case "BinaryExpr": return {
727
+ ...node,
728
+ left: extractAndCompileArrowFunctions(node.left, ctx),
729
+ right: extractAndCompileArrowFunctions(node.right, ctx)
730
+ };
731
+ case "UnaryExpr": return {
732
+ ...node,
733
+ argument: extractAndCompileArrowFunctions(node.argument, ctx)
734
+ };
735
+ case "ConditionalExpr": return {
736
+ ...node,
737
+ test: extractAndCompileArrowFunctions(node.test, ctx),
738
+ consequent: extractAndCompileArrowFunctions(node.consequent, ctx),
739
+ alternate: extractAndCompileArrowFunctions(node.alternate, ctx)
740
+ };
741
+ case "MemberExpr": return {
742
+ ...node,
743
+ object: extractAndCompileArrowFunctions(node.object, ctx),
744
+ property: node.computed ? extractAndCompileArrowFunctions(node.property, ctx) : node.property
745
+ };
746
+ case "CallExpr": return {
747
+ ...node,
748
+ callee: extractAndCompileArrowFunctions(node.callee, ctx),
749
+ arguments: node.arguments.map((arg) => extractAndCompileArrowFunctions(arg, ctx))
750
+ };
751
+ case "ArrayExpr": return {
752
+ ...node,
753
+ elements: node.elements.map((el) => extractAndCompileArrowFunctions(el, ctx))
754
+ };
755
+ case "ObjectExpr": return {
756
+ ...node,
757
+ properties: node.properties.map((prop) => ({
758
+ ...prop,
759
+ key: prop.computed ? extractAndCompileArrowFunctions(prop.key, ctx) : prop.key,
760
+ value: extractAndCompileArrowFunctions(prop.value, ctx)
761
+ }))
762
+ };
763
+ default: return node;
764
+ }
765
+ }
766
+ function compileAst(node, ctx) {
767
+ if (node.type === "BinaryExpr" && (node.operator === "||" || node.operator === "&&" || node.operator === "??")) return compileShortCircuit(node, ctx);
768
+ if (node.type === "ConditionalExpr") return compileConditional(node, ctx);
769
+ if (node.type === "ArrowFunctionExpr") return compileArrowFunction(node, ctx);
770
+ const exprStr = generate(extractAndCompileArrowFunctions(node, ctx));
771
+ currentExprs(ctx).push(exprStr);
772
+ return ctx.nextIndex++;
773
+ }
774
+ function compileShortCircuit(node, ctx) {
775
+ const exprs = currentExprs(ctx);
776
+ const leftIdx = compileAst(node.left, ctx);
777
+ const branchConditions = {
778
+ "||": `$[${leftIdx}]`,
779
+ "&&": `!$[${leftIdx}]`,
780
+ "??": `$[${leftIdx}]!=null`
781
+ };
782
+ const branchIdx = exprs.length;
783
+ exprs.push([
784
+ "br",
785
+ branchConditions[node.operator],
786
+ 0
787
+ ]);
788
+ ctx.nextIndex++;
789
+ compileAst(node.right, ctx);
790
+ const skipCount = exprs.length - branchIdx - 1;
791
+ exprs[branchIdx][2] = skipCount;
792
+ const phiIdx = ctx.nextIndex++;
793
+ exprs.push(["phi"]);
794
+ return phiIdx;
795
+ }
796
+ function compileConditional(node, ctx) {
797
+ const exprs = currentExprs(ctx);
798
+ const testIdx = compileAst(node.test, ctx);
799
+ const branchIdx = exprs.length;
800
+ exprs.push([
801
+ "br",
802
+ `$[${testIdx}]`,
803
+ 0
804
+ ]);
805
+ ctx.nextIndex++;
806
+ compileAst(node.alternate, ctx);
807
+ const jmpIdx = exprs.length;
808
+ exprs.push(["jmp", 0]);
809
+ ctx.nextIndex++;
810
+ compileAst(node.consequent, ctx);
811
+ const thenEndIdx = exprs.length;
812
+ exprs[branchIdx][2] = jmpIdx - branchIdx;
813
+ exprs[jmpIdx][1] = thenEndIdx - jmpIdx - 1;
814
+ const phiIdx = ctx.nextIndex++;
815
+ exprs.push(["phi"]);
816
+ return phiIdx;
817
+ }
818
+ function compileArrowFunction(node, ctx) {
819
+ const paramCount = node.params.length;
820
+ const fnIndex = ctx.nextIndex++;
821
+ const paramMapping = /* @__PURE__ */ new Map();
822
+ for (const param of node.params) if (param.type === "Placeholder") {
823
+ const paramName = `_${ctx.nextParamIndex++}`;
824
+ paramMapping.set(param.id, paramName);
825
+ }
826
+ const transformedBody = transformPlaceholders(node.body, (id) => {
827
+ return paramMapping.get(id) ?? null;
828
+ });
829
+ const lambdaStmts = [];
830
+ ctx.expressionStack.push(lambdaStmts);
831
+ compileAst(transformedBody, ctx);
832
+ ctx.expressionStack.pop();
833
+ const fnNode = [
834
+ "fn",
835
+ paramCount,
836
+ ...lambdaStmts
837
+ ];
838
+ currentExprs(ctx).push(fnNode);
839
+ return fnIndex;
780
840
  }
781
841
 
782
842
  //#endregion
@@ -786,10 +846,146 @@ function compile(expression, variables, options = {}) {
786
846
  */
787
847
  const evaluatorCache = /* @__PURE__ */ new Map();
788
848
  /**
789
- * 检测编译数据是否包含控制流节点(V2 格式)
849
+ * 判断是否是 FnNode
850
+ */
851
+ function isFnNode(expr) {
852
+ return Array.isArray(expr) && expr[0] === "fn" && typeof expr[1] === "number";
853
+ }
854
+ /**
855
+ * 判断是否是控制流节点
856
+ */
857
+ function isControlFlowNode(expr) {
858
+ return Array.isArray(expr) && (expr[0] === "br" || expr[0] === "jmp" || expr[0] === "phi");
859
+ }
860
+ /**
861
+ * 生成 lambda 函数代码
862
+ *
863
+ * @param fnNode - FnNode 节点
864
+ * @param indexCounter - 全局索引计数器
865
+ * @param paramCounter - 参数计数器
866
+ * @returns lambda 函数代码字符串
790
867
  */
791
- function isV2Format(expressions) {
792
- return expressions.some((expr) => Array.isArray(expr));
868
+ function generateLambdaCode(fnNode, indexCounter, paramCounter) {
869
+ const [, paramCount, ...stmts] = fnNode;
870
+ const paramStartIndex = paramCounter.value;
871
+ paramCounter.value += paramCount;
872
+ return `(${Array.from({ length: paramCount }, (_, i) => `_${paramStartIndex + i}`).join(",")})=>{${translateStmts(stmts, indexCounter, paramCounter)}}`;
873
+ }
874
+ /**
875
+ * 翻译表达式列表为 JavaScript 代码
876
+ * 使用全局索引计数器分配 $[N] 槽位
877
+ */
878
+ function translateStmts(expressions, indexCounter, paramCounter) {
879
+ for (let i = 0; i < expressions.length; i++) {
880
+ const expr = expressions[i];
881
+ if (!expr) continue;
882
+ if (isControlFlowNode(expr)) {
883
+ const [type, , offset] = expr;
884
+ if ((type === "br" || type === "jmp") && offset !== void 0) {
885
+ const target = i + offset + 1;
886
+ if (target < 0 || target > expressions.length) throw new Error(`Unable to translate: invalid jump target at index ${i}. Target would be ${target}, but expression length is ${expressions.length}`);
887
+ }
888
+ }
889
+ }
890
+ const lines = ["let $lastValue;"];
891
+ const indexMap = [];
892
+ const savedStart = indexCounter.value;
893
+ for (let i = 0; i < expressions.length; i++) {
894
+ indexMap.push(indexCounter.value);
895
+ const expr = expressions[i];
896
+ if (isFnNode(expr)) {
897
+ indexCounter.value++;
898
+ countFnNodeIndices(expr, indexCounter);
899
+ } else indexCounter.value++;
900
+ }
901
+ indexCounter.value = savedStart;
902
+ const processed = /* @__PURE__ */ new Set();
903
+ function translateRange(start, end, indent = "") {
904
+ let i = start;
905
+ while (i < end) {
906
+ if (processed.has(i)) {
907
+ i++;
908
+ continue;
909
+ }
910
+ const expr = expressions[i];
911
+ if (!expr) {
912
+ i++;
913
+ continue;
914
+ }
915
+ const varIdx = indexMap[i];
916
+ if (isFnNode(expr)) {
917
+ processed.add(i);
918
+ indexCounter.value++;
919
+ const lambdaCode = generateLambdaCode(expr, indexCounter, paramCounter);
920
+ lines.push(`${indent}$[${varIdx}] = $lastValue = ${lambdaCode};`);
921
+ i++;
922
+ } else if (typeof expr === "string") {
923
+ processed.add(i);
924
+ indexCounter.value++;
925
+ lines.push(`${indent}$[${varIdx}] = $lastValue = ${expr};`);
926
+ i++;
927
+ } else if (!Array.isArray(expr)) i++;
928
+ else {
929
+ const [type] = expr;
930
+ if (type === "br") {
931
+ processed.add(i);
932
+ indexCounter.value++;
933
+ const [, condition, skipCount] = expr;
934
+ const falseBlockEnd = i + skipCount;
935
+ let jmpIdx = -1;
936
+ let trueBlockEnd = falseBlockEnd + 1;
937
+ if (falseBlockEnd < expressions.length) {
938
+ const lastInFalseBranch = expressions[falseBlockEnd];
939
+ if (Array.isArray(lastInFalseBranch) && lastInFalseBranch[0] === "jmp") {
940
+ jmpIdx = falseBlockEnd;
941
+ const jmpOffset = lastInFalseBranch[1];
942
+ trueBlockEnd = jmpIdx + jmpOffset;
943
+ processed.add(jmpIdx);
944
+ }
945
+ }
946
+ const hasElse = jmpIdx >= 0;
947
+ lines.push(`${indent}if (!(${condition})) {`);
948
+ translateRange(i + 1, falseBlockEnd + 1, indent + " ");
949
+ if (hasElse) {
950
+ lines.push(`${indent}} else {`);
951
+ translateRange(jmpIdx + 1, trueBlockEnd + 1, indent + " ");
952
+ lines.push(`${indent}}`);
953
+ i = trueBlockEnd + 1;
954
+ } else {
955
+ lines.push(`${indent}}`);
956
+ i = falseBlockEnd + 1;
957
+ }
958
+ } else if (type === "phi") {
959
+ processed.add(i);
960
+ indexCounter.value++;
961
+ lines.push(`${indent}$[${varIdx}] = $lastValue;`);
962
+ i++;
963
+ } else if (type === "jmp") {
964
+ indexCounter.value++;
965
+ throw new Error(`Unable to translate: unexpected jmp at index ${i}. This should have been paired with a preceding br. Unsupported control flow structure.`);
966
+ } else throw new Error(`Unable to translate: unknown control flow node type.`);
967
+ }
968
+ }
969
+ }
970
+ translateRange(0, expressions.length);
971
+ lines.push("return $lastValue;");
972
+ return lines.join("\n");
973
+ }
974
+ /**
975
+ * 计算 FnNode 内部 stmts 占用的索引数量(递归)
976
+ */
977
+ function countFnNodeIndices(fnNode, indexCounter) {
978
+ const [, , ...stmts] = fnNode;
979
+ for (const stmt of stmts) if (isFnNode(stmt)) {
980
+ indexCounter.value++;
981
+ countFnNodeIndices(stmt, indexCounter);
982
+ } else indexCounter.value++;
983
+ }
984
+ /**
985
+ * 分析并验证控制流结构,将 br/jmp/phi 翻译为 if-else 语句
986
+ */
987
+ function translateControlFlow(expressions, variableCount) {
988
+ return translateStmts(expressions, { value: variableCount }, { value: 0 });
793
989
  }
794
990
  /**
795
991
  * 执行编译后的表达式
@@ -803,7 +999,7 @@ function isV2Format(expressions) {
803
999
  *
804
1000
  * @example
805
1001
  * ```ts
806
- * const compiled = [["x", "y"], "$0+$1", "$1*2"]
1002
+ * const compiled = [["x", "y"], "$[0]+$[1]", "$[1]*2"]
807
1003
  * const result = evaluate<number>(compiled, { x: 2, y: 3 })
808
1004
  * // => 6 (3 * 2)
809
1005
  * ```
@@ -821,8 +1017,8 @@ function evaluate(data, values) {
821
1017
  const cacheKey = JSON.stringify(data);
822
1018
  let evaluator = evaluatorCache.get(cacheKey);
823
1019
  if (!evaluator) {
824
- const functionBody = isV2Format(expressions) ? buildEvaluatorFunctionBodyV2(expressions, variableNames.length) : buildEvaluatorFunctionBody(expressions, variableNames.length);
825
- evaluator = new Function("$values", functionBody);
1020
+ const functionBody = buildEvaluatorFunctionBody(expressions, variableNames.length);
1021
+ evaluator = new Function("$", functionBody);
826
1022
  evaluatorCache.set(cacheKey, evaluator);
827
1023
  }
828
1024
  try {
@@ -834,69 +1030,13 @@ function evaluate(data, values) {
834
1030
  /**
835
1031
  * 构造求值函数体
836
1032
  *
837
- * @param expressions - 表达式列表
1033
+ * @param expressions - 表达式列表(可包含控制流节点和 FnNode)
838
1034
  * @param variableCount - 变量数量
839
1035
  * @returns 函数体字符串
840
- *
841
- * @example
842
- * ```ts
843
- * buildEvaluatorFunctionBody(["$0+$1", "$2*2"], 2)
844
- * // 返回执行 $0+$1 并存储到 $values[2],然后执行 $2*2 的函数体
845
- * ```
846
1036
  */
847
1037
  function buildEvaluatorFunctionBody(expressions, variableCount) {
848
1038
  if (expressions.length === 0) throw new Error("No expressions to evaluate");
849
- return [
850
- ...Array.from({ length: variableCount }, (_, i) => `const $${i} = $values[${i}];`),
851
- ...expressions.map((expr, i) => {
852
- const idx = variableCount + i;
853
- return `const $${idx} = ${expr}; $values[${idx}] = $${idx};`;
854
- }),
855
- `return $values[$values.length - 1];`
856
- ].join("\n");
857
- }
858
- /**
859
- * 构造带控制流支持的求值函数体(V2 格式)
860
- *
861
- * @param expressions - 表达式列表(可包含控制流节点)
862
- * @param variableCount - 变量数量
863
- * @returns 函数体字符串
864
- */
865
- function buildEvaluatorFunctionBodyV2(expressions, variableCount) {
866
- if (expressions.length === 0) throw new Error("No expressions to evaluate");
867
- const lines = [
868
- ...Array.from({ length: variableCount }, (_, i) => `const $${i} = $values[${i}];`),
869
- "let $pc = 0;",
870
- "let $lastValue;",
871
- ...expressions.map((_, i) => `let $${variableCount + i};`),
872
- `while ($pc < ${expressions.length}) {`,
873
- " switch ($pc) {"
874
- ];
875
- expressions.forEach((expr, i) => {
876
- const idx = variableCount + i;
877
- lines.push(` case ${i}: {`);
878
- if (typeof expr === "string") {
879
- lines.push(` $${idx} = $lastValue = ${expr};`);
880
- lines.push(` $values[${idx}] = $${idx};`);
881
- lines.push(" $pc++; break;");
882
- } else {
883
- const [type] = expr;
884
- switch (type) {
885
- case "br":
886
- lines.push(` if (${expr[1]}) { $pc += ${expr[2] + 1}; } else { $pc++; } break;`);
887
- break;
888
- case "jmp":
889
- lines.push(` $pc += ${expr[1] + 1}; break;`);
890
- break;
891
- case "phi":
892
- lines.push(` $${idx} = $values[${idx}] = $lastValue; $pc++; break;`);
893
- break;
894
- }
895
- }
896
- lines.push(" }");
897
- });
898
- lines.push(" }", "}", "return $values[$values.length - 1];");
899
- return lines.join("\n");
1039
+ return translateControlFlow(expressions, variableCount);
900
1040
  }
901
1041
 
902
1042
  //#endregion
@@ -1682,5 +1822,5 @@ function wrap(value) {
1682
1822
  }
1683
1823
 
1684
1824
  //#endregion
1685
- export { compile, compileAndEvaluate, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, lambda, t, variable, wrap };
1825
+ export { compile, compileAndEvaluate, evaluate, expr, lambda, t, variable, wrap };
1686
1826
  //# sourceMappingURL=index.mjs.map