@codehz/json-expr 0.4.0 → 0.5.1
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 +603 -13
- package/dist/index.d.mts +173 -5
- package/dist/index.mjs +635 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -194,6 +194,8 @@ var Parser = class Parser {
|
|
|
194
194
|
if (ch === "[") return this.parseArray();
|
|
195
195
|
if (ch === "{") return this.parseObject();
|
|
196
196
|
if (ch === "(") {
|
|
197
|
+
const arrowFunc = this.tryParseArrowFunction();
|
|
198
|
+
if (arrowFunc) return arrowFunc;
|
|
197
199
|
this.advance();
|
|
198
200
|
this.skipWhitespace();
|
|
199
201
|
const expr = this.parseExpression();
|
|
@@ -214,7 +216,11 @@ var Parser = class Parser {
|
|
|
214
216
|
type: "Identifier",
|
|
215
217
|
name: "undefined"
|
|
216
218
|
};
|
|
217
|
-
if (this.isIdentifierStart(ch))
|
|
219
|
+
if (this.isIdentifierStart(ch)) {
|
|
220
|
+
const arrowFunc = this.tryParseSingleParamArrowFunction();
|
|
221
|
+
if (arrowFunc) return arrowFunc;
|
|
222
|
+
return this.parseIdentifier();
|
|
223
|
+
}
|
|
218
224
|
throw new Error(`Unexpected character at position ${this.pos}: ${ch}`);
|
|
219
225
|
}
|
|
220
226
|
parseNumber() {
|
|
@@ -359,6 +365,63 @@ var Parser = class Parser {
|
|
|
359
365
|
name
|
|
360
366
|
};
|
|
361
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* 尝试解析带括号的箭头函数: (a, b) => expr
|
|
370
|
+
* 使用回溯机制
|
|
371
|
+
*/
|
|
372
|
+
tryParseArrowFunction() {
|
|
373
|
+
const savedPos = this.pos;
|
|
374
|
+
try {
|
|
375
|
+
this.expect("(");
|
|
376
|
+
this.skipWhitespace();
|
|
377
|
+
const params = [];
|
|
378
|
+
while (this.peek() !== ")") {
|
|
379
|
+
if (!this.isIdentifierStart(this.peek())) throw new Error("Expected identifier");
|
|
380
|
+
params.push(this.parseIdentifier());
|
|
381
|
+
this.skipWhitespace();
|
|
382
|
+
if (this.peek() === ",") {
|
|
383
|
+
this.advance();
|
|
384
|
+
this.skipWhitespace();
|
|
385
|
+
} else break;
|
|
386
|
+
}
|
|
387
|
+
this.expect(")");
|
|
388
|
+
this.skipWhitespace();
|
|
389
|
+
if (this.source.slice(this.pos, this.pos + 2) !== "=>") throw new Error("Expected =>");
|
|
390
|
+
this.pos += 2;
|
|
391
|
+
this.skipWhitespace();
|
|
392
|
+
return {
|
|
393
|
+
type: "ArrowFunctionExpr",
|
|
394
|
+
params,
|
|
395
|
+
body: this.parseExpression()
|
|
396
|
+
};
|
|
397
|
+
} catch {
|
|
398
|
+
this.pos = savedPos;
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 尝试解析单参数无括号的箭头函数: a => expr
|
|
404
|
+
* 使用回溯机制
|
|
405
|
+
*/
|
|
406
|
+
tryParseSingleParamArrowFunction() {
|
|
407
|
+
const savedPos = this.pos;
|
|
408
|
+
try {
|
|
409
|
+
const param = this.parseIdentifier();
|
|
410
|
+
this.skipWhitespace();
|
|
411
|
+
if (this.source.slice(this.pos, this.pos + 2) !== "=>") throw new Error("Expected =>");
|
|
412
|
+
this.pos += 2;
|
|
413
|
+
this.skipWhitespace();
|
|
414
|
+
const body = this.parseExpression();
|
|
415
|
+
return {
|
|
416
|
+
type: "ArrowFunctionExpr",
|
|
417
|
+
params: [param],
|
|
418
|
+
body
|
|
419
|
+
};
|
|
420
|
+
} catch {
|
|
421
|
+
this.pos = savedPos;
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
362
425
|
parseArguments() {
|
|
363
426
|
const args = [];
|
|
364
427
|
this.skipWhitespace();
|
|
@@ -496,6 +559,27 @@ function generate(node) {
|
|
|
496
559
|
case "CallExpr": {
|
|
497
560
|
const callee = wrapIfNeeded(node.callee, node, "callee");
|
|
498
561
|
const args = node.arguments.map(generate).join(",");
|
|
562
|
+
if (node.callee.type === "Identifier" && [
|
|
563
|
+
"Date",
|
|
564
|
+
"RegExp",
|
|
565
|
+
"URL",
|
|
566
|
+
"URLSearchParams",
|
|
567
|
+
"Map",
|
|
568
|
+
"Set",
|
|
569
|
+
"Int8Array",
|
|
570
|
+
"Uint8Array",
|
|
571
|
+
"Uint8ClampedArray",
|
|
572
|
+
"Int16Array",
|
|
573
|
+
"Uint16Array",
|
|
574
|
+
"Int32Array",
|
|
575
|
+
"Uint32Array",
|
|
576
|
+
"Float32Array",
|
|
577
|
+
"Float64Array",
|
|
578
|
+
"BigInt64Array",
|
|
579
|
+
"BigUint64Array",
|
|
580
|
+
"ArrayBuffer",
|
|
581
|
+
"DataView"
|
|
582
|
+
].includes(node.callee.name)) return node.optional ? `new ${callee}?.(${args})` : `new ${callee}(${args})`;
|
|
499
583
|
return node.optional ? `${callee}?.(${args})` : `${callee}(${args})`;
|
|
500
584
|
}
|
|
501
585
|
case "ArrayExpr": return `[${node.elements.map(generate).join(",")}]`;
|
|
@@ -503,6 +587,13 @@ function generate(node) {
|
|
|
503
587
|
if (prop.shorthand) return generate(prop.key);
|
|
504
588
|
return `${prop.computed ? `[${generate(prop.key)}]` : generate(prop.key)}:${generate(prop.value)}`;
|
|
505
589
|
}).join(",")}}`;
|
|
590
|
+
case "ArrowFunctionExpr": {
|
|
591
|
+
const params = node.params.map((p) => p.name).join(",");
|
|
592
|
+
let body = generate(node.body);
|
|
593
|
+
if (node.body.type === "ObjectExpr") body = `(${body})`;
|
|
594
|
+
if (node.params.length === 1) return `${params}=>${body}`;
|
|
595
|
+
return `(${params})=>${body}`;
|
|
596
|
+
}
|
|
506
597
|
default: {
|
|
507
598
|
const nodeType = node.type ?? "unknown";
|
|
508
599
|
throw new Error(`Unknown node type: ${nodeType}`);
|
|
@@ -537,6 +628,68 @@ function needsParens(child, parent, position) {
|
|
|
537
628
|
}
|
|
538
629
|
return false;
|
|
539
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* 转换 AST 中的标识符
|
|
633
|
+
* 回调函数可以返回:
|
|
634
|
+
* - string: 替换标识符名称
|
|
635
|
+
* - ASTNode: 内联该 AST 节点(用于子表达式内联)
|
|
636
|
+
*/
|
|
637
|
+
function transformIdentifiers(node, transform) {
|
|
638
|
+
switch (node.type) {
|
|
639
|
+
case "Identifier": {
|
|
640
|
+
const result = transform(node.name);
|
|
641
|
+
return typeof result === "string" ? {
|
|
642
|
+
...node,
|
|
643
|
+
name: result
|
|
644
|
+
} : result;
|
|
645
|
+
}
|
|
646
|
+
case "BinaryExpr": return {
|
|
647
|
+
...node,
|
|
648
|
+
left: transformIdentifiers(node.left, transform),
|
|
649
|
+
right: transformIdentifiers(node.right, transform)
|
|
650
|
+
};
|
|
651
|
+
case "UnaryExpr": return {
|
|
652
|
+
...node,
|
|
653
|
+
argument: transformIdentifiers(node.argument, transform)
|
|
654
|
+
};
|
|
655
|
+
case "ConditionalExpr": return {
|
|
656
|
+
...node,
|
|
657
|
+
test: transformIdentifiers(node.test, transform),
|
|
658
|
+
consequent: transformIdentifiers(node.consequent, transform),
|
|
659
|
+
alternate: transformIdentifiers(node.alternate, transform)
|
|
660
|
+
};
|
|
661
|
+
case "MemberExpr": return {
|
|
662
|
+
...node,
|
|
663
|
+
object: transformIdentifiers(node.object, transform),
|
|
664
|
+
property: node.computed ? transformIdentifiers(node.property, transform) : node.property
|
|
665
|
+
};
|
|
666
|
+
case "CallExpr": return {
|
|
667
|
+
...node,
|
|
668
|
+
callee: transformIdentifiers(node.callee, transform),
|
|
669
|
+
arguments: node.arguments.map((arg) => transformIdentifiers(arg, transform))
|
|
670
|
+
};
|
|
671
|
+
case "ArrayExpr": return {
|
|
672
|
+
...node,
|
|
673
|
+
elements: node.elements.map((el) => transformIdentifiers(el, transform))
|
|
674
|
+
};
|
|
675
|
+
case "ObjectExpr": return {
|
|
676
|
+
...node,
|
|
677
|
+
properties: node.properties.map((prop) => ({
|
|
678
|
+
...prop,
|
|
679
|
+
key: prop.computed ? transformIdentifiers(prop.key, transform) : prop.key,
|
|
680
|
+
value: transformIdentifiers(prop.value, transform)
|
|
681
|
+
}))
|
|
682
|
+
};
|
|
683
|
+
case "ArrowFunctionExpr": {
|
|
684
|
+
const paramNames = new Set(node.params.map((p) => p.name));
|
|
685
|
+
return {
|
|
686
|
+
...node,
|
|
687
|
+
body: transformIdentifiers(node.body, (name) => paramNames.has(name) ? name : transform(name))
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
default: return node;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
540
693
|
|
|
541
694
|
//#endregion
|
|
542
695
|
//#region src/proxy-metadata.ts
|
|
@@ -588,25 +741,258 @@ function getVariablePlaceholder$1(id) {
|
|
|
588
741
|
return `$$VAR_${id.description}$$`;
|
|
589
742
|
}
|
|
590
743
|
/**
|
|
591
|
-
*
|
|
592
|
-
* - Proxy Variable/Expression
|
|
593
|
-
* -
|
|
594
|
-
* -
|
|
595
|
-
* -
|
|
744
|
+
* 序列化参数为 AST 节点
|
|
745
|
+
* - Proxy Variable/Expression:使用 ast 或占位符标识符
|
|
746
|
+
* - 数组:返回 ArrayExpr 节点
|
|
747
|
+
* - 对象:返回 ObjectExpr 节点
|
|
748
|
+
* - 原始值:返回对应的字面量节点
|
|
749
|
+
* - Date, RegExp, BigInt, URL, URLSearchParams, Map, Set, TypedArray, DataView: 构造函数调用
|
|
596
750
|
*/
|
|
597
|
-
function
|
|
751
|
+
function serializeArgumentToAST(arg) {
|
|
598
752
|
if ((typeof arg === "object" || typeof arg === "function") && arg !== null) {
|
|
599
753
|
const meta = getProxyMetadata(arg);
|
|
600
754
|
if (meta) {
|
|
601
|
-
if (meta.
|
|
602
|
-
if (meta.rootVariable) return
|
|
755
|
+
if (meta.ast) return meta.ast;
|
|
756
|
+
if (meta.rootVariable) return {
|
|
757
|
+
type: "Identifier",
|
|
758
|
+
name: getVariablePlaceholder$1(meta.rootVariable)
|
|
759
|
+
};
|
|
603
760
|
}
|
|
604
761
|
}
|
|
605
|
-
if (Array.isArray(arg)) return
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
762
|
+
if (Array.isArray(arg)) return {
|
|
763
|
+
type: "ArrayExpr",
|
|
764
|
+
elements: arg.map(serializeArgumentToAST)
|
|
765
|
+
};
|
|
766
|
+
if (typeof arg === "object" && arg !== null) {
|
|
767
|
+
if (arg instanceof Date) return {
|
|
768
|
+
type: "CallExpr",
|
|
769
|
+
callee: {
|
|
770
|
+
type: "Identifier",
|
|
771
|
+
name: "Date"
|
|
772
|
+
},
|
|
773
|
+
arguments: [{
|
|
774
|
+
type: "NumberLiteral",
|
|
775
|
+
value: arg.getTime(),
|
|
776
|
+
raw: String(arg.getTime())
|
|
777
|
+
}],
|
|
778
|
+
optional: false
|
|
779
|
+
};
|
|
780
|
+
if (arg instanceof RegExp) {
|
|
781
|
+
const args = [{
|
|
782
|
+
type: "StringLiteral",
|
|
783
|
+
value: arg.source,
|
|
784
|
+
quote: "\""
|
|
785
|
+
}];
|
|
786
|
+
if (arg.flags) args.push({
|
|
787
|
+
type: "StringLiteral",
|
|
788
|
+
value: arg.flags,
|
|
789
|
+
quote: "\""
|
|
790
|
+
});
|
|
791
|
+
return {
|
|
792
|
+
type: "CallExpr",
|
|
793
|
+
callee: {
|
|
794
|
+
type: "Identifier",
|
|
795
|
+
name: "RegExp"
|
|
796
|
+
},
|
|
797
|
+
arguments: args,
|
|
798
|
+
optional: false
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (typeof URL !== "undefined" && arg instanceof URL) return {
|
|
802
|
+
type: "CallExpr",
|
|
803
|
+
callee: {
|
|
804
|
+
type: "Identifier",
|
|
805
|
+
name: "URL"
|
|
806
|
+
},
|
|
807
|
+
arguments: [{
|
|
808
|
+
type: "StringLiteral",
|
|
809
|
+
value: arg.href,
|
|
810
|
+
quote: "\""
|
|
811
|
+
}],
|
|
812
|
+
optional: false
|
|
813
|
+
};
|
|
814
|
+
if (typeof URLSearchParams !== "undefined" && arg instanceof URLSearchParams) {
|
|
815
|
+
const entries = [];
|
|
816
|
+
arg.forEach((value, key) => {
|
|
817
|
+
entries.push({
|
|
818
|
+
type: "ArrayExpr",
|
|
819
|
+
elements: [{
|
|
820
|
+
type: "StringLiteral",
|
|
821
|
+
value: key,
|
|
822
|
+
quote: "\""
|
|
823
|
+
}, {
|
|
824
|
+
type: "StringLiteral",
|
|
825
|
+
value,
|
|
826
|
+
quote: "\""
|
|
827
|
+
}]
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
type: "CallExpr",
|
|
832
|
+
callee: {
|
|
833
|
+
type: "Identifier",
|
|
834
|
+
name: "URLSearchParams"
|
|
835
|
+
},
|
|
836
|
+
arguments: [{
|
|
837
|
+
type: "ArrayExpr",
|
|
838
|
+
elements: entries
|
|
839
|
+
}],
|
|
840
|
+
optional: false
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (arg instanceof Map) {
|
|
844
|
+
const entries = [];
|
|
845
|
+
arg.forEach((value, key) => {
|
|
846
|
+
entries.push({
|
|
847
|
+
type: "ArrayExpr",
|
|
848
|
+
elements: [serializeArgumentToAST(key), serializeArgumentToAST(value)]
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
return {
|
|
852
|
+
type: "CallExpr",
|
|
853
|
+
callee: {
|
|
854
|
+
type: "Identifier",
|
|
855
|
+
name: "Map"
|
|
856
|
+
},
|
|
857
|
+
arguments: [{
|
|
858
|
+
type: "ArrayExpr",
|
|
859
|
+
elements: entries
|
|
860
|
+
}],
|
|
861
|
+
optional: false
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
if (arg instanceof Set) {
|
|
865
|
+
const values = [];
|
|
866
|
+
arg.forEach((value) => {
|
|
867
|
+
values.push(serializeArgumentToAST(value));
|
|
868
|
+
});
|
|
869
|
+
return {
|
|
870
|
+
type: "CallExpr",
|
|
871
|
+
callee: {
|
|
872
|
+
type: "Identifier",
|
|
873
|
+
name: "Set"
|
|
874
|
+
},
|
|
875
|
+
arguments: [{
|
|
876
|
+
type: "ArrayExpr",
|
|
877
|
+
elements: values
|
|
878
|
+
}],
|
|
879
|
+
optional: false
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
for (const constructorName of [
|
|
883
|
+
"Int8Array",
|
|
884
|
+
"Uint8Array",
|
|
885
|
+
"Uint8ClampedArray",
|
|
886
|
+
"Int16Array",
|
|
887
|
+
"Uint16Array",
|
|
888
|
+
"Int32Array",
|
|
889
|
+
"Uint32Array",
|
|
890
|
+
"Float32Array",
|
|
891
|
+
"Float64Array",
|
|
892
|
+
"BigInt64Array",
|
|
893
|
+
"BigUint64Array"
|
|
894
|
+
]) if (typeof globalThis[constructorName] !== "undefined") {
|
|
895
|
+
if (arg instanceof globalThis[constructorName]) {
|
|
896
|
+
const values = [...arg].map((val) => serializeArgumentToAST(val));
|
|
897
|
+
return {
|
|
898
|
+
type: "CallExpr",
|
|
899
|
+
callee: {
|
|
900
|
+
type: "Identifier",
|
|
901
|
+
name: constructorName
|
|
902
|
+
},
|
|
903
|
+
arguments: [{
|
|
904
|
+
type: "ArrayExpr",
|
|
905
|
+
elements: values
|
|
906
|
+
}],
|
|
907
|
+
optional: false
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (arg instanceof ArrayBuffer) {
|
|
912
|
+
const uint8Array = new Uint8Array(arg);
|
|
913
|
+
return {
|
|
914
|
+
type: "MemberExpr",
|
|
915
|
+
object: {
|
|
916
|
+
type: "CallExpr",
|
|
917
|
+
callee: {
|
|
918
|
+
type: "Identifier",
|
|
919
|
+
name: "Uint8Array"
|
|
920
|
+
},
|
|
921
|
+
arguments: [{
|
|
922
|
+
type: "ArrayExpr",
|
|
923
|
+
elements: Array.from(uint8Array).map((val) => serializeArgumentToAST(val))
|
|
924
|
+
}],
|
|
925
|
+
optional: false
|
|
926
|
+
},
|
|
927
|
+
property: {
|
|
928
|
+
type: "Identifier",
|
|
929
|
+
name: "buffer"
|
|
930
|
+
},
|
|
931
|
+
computed: false,
|
|
932
|
+
optional: false
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
if (arg instanceof DataView) return {
|
|
936
|
+
type: "CallExpr",
|
|
937
|
+
callee: {
|
|
938
|
+
type: "Identifier",
|
|
939
|
+
name: "DataView"
|
|
940
|
+
},
|
|
941
|
+
arguments: [serializeArgumentToAST(arg.buffer)],
|
|
942
|
+
optional: false
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
if (typeof arg === "object" && arg !== null) return {
|
|
946
|
+
type: "ObjectExpr",
|
|
947
|
+
properties: Object.entries(arg).map(([k, v]) => {
|
|
948
|
+
return {
|
|
949
|
+
key: /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? {
|
|
950
|
+
type: "Identifier",
|
|
951
|
+
name: k
|
|
952
|
+
} : {
|
|
953
|
+
type: "StringLiteral",
|
|
954
|
+
value: k,
|
|
955
|
+
quote: "\""
|
|
956
|
+
},
|
|
957
|
+
value: serializeArgumentToAST(v),
|
|
958
|
+
computed: false,
|
|
959
|
+
shorthand: false
|
|
960
|
+
};
|
|
961
|
+
})
|
|
962
|
+
};
|
|
963
|
+
if (arg === null) return { type: "NullLiteral" };
|
|
964
|
+
if (arg === void 0) return {
|
|
965
|
+
type: "Identifier",
|
|
966
|
+
name: "undefined"
|
|
967
|
+
};
|
|
968
|
+
if (typeof arg === "boolean") return {
|
|
969
|
+
type: "BooleanLiteral",
|
|
970
|
+
value: arg
|
|
971
|
+
};
|
|
972
|
+
if (typeof arg === "number") return {
|
|
973
|
+
type: "NumberLiteral",
|
|
974
|
+
value: arg,
|
|
975
|
+
raw: String(arg)
|
|
976
|
+
};
|
|
977
|
+
if (typeof arg === "string") return {
|
|
978
|
+
type: "StringLiteral",
|
|
979
|
+
value: arg,
|
|
980
|
+
quote: "\""
|
|
981
|
+
};
|
|
982
|
+
if (typeof arg === "bigint") return {
|
|
983
|
+
type: "CallExpr",
|
|
984
|
+
callee: {
|
|
985
|
+
type: "Identifier",
|
|
986
|
+
name: "BigInt"
|
|
987
|
+
},
|
|
988
|
+
arguments: [{
|
|
989
|
+
type: "StringLiteral",
|
|
990
|
+
value: arg.toString(),
|
|
991
|
+
quote: "\""
|
|
992
|
+
}],
|
|
993
|
+
optional: false
|
|
994
|
+
};
|
|
995
|
+
throw new Error(`Unsupported argument type: ${typeof arg}`);
|
|
610
996
|
}
|
|
611
997
|
/**
|
|
612
998
|
* 从参数中收集依赖的 Symbol
|
|
@@ -658,53 +1044,85 @@ function createProxyVariable(id) {
|
|
|
658
1044
|
* @returns Proxy 包装的 Expression
|
|
659
1045
|
*/
|
|
660
1046
|
function createProxyExpression(rootId, path, deps) {
|
|
661
|
-
|
|
1047
|
+
let ast = {
|
|
1048
|
+
type: "Identifier",
|
|
1049
|
+
name: getVariablePlaceholder$1(rootId)
|
|
1050
|
+
};
|
|
1051
|
+
for (const prop of path) ast = {
|
|
1052
|
+
type: "MemberExpr",
|
|
1053
|
+
object: ast,
|
|
1054
|
+
property: {
|
|
1055
|
+
type: "Identifier",
|
|
1056
|
+
name: prop
|
|
1057
|
+
},
|
|
1058
|
+
computed: false,
|
|
1059
|
+
optional: false
|
|
1060
|
+
};
|
|
662
1061
|
const proxy = new Proxy(function() {}, {
|
|
663
1062
|
get(_target, prop) {
|
|
664
1063
|
if (typeof prop === "symbol") return void 0;
|
|
665
1064
|
return createProxyExpression(rootId, [...path, String(prop)], deps);
|
|
666
1065
|
},
|
|
667
1066
|
apply(_target, _thisArg, args) {
|
|
668
|
-
const
|
|
1067
|
+
const callAst = {
|
|
1068
|
+
type: "CallExpr",
|
|
1069
|
+
callee: ast,
|
|
1070
|
+
arguments: args.map(serializeArgumentToAST),
|
|
1071
|
+
optional: false
|
|
1072
|
+
};
|
|
669
1073
|
const newDeps = new Set(deps);
|
|
670
1074
|
collectDepsFromArgs(args, newDeps);
|
|
671
|
-
return
|
|
1075
|
+
return createProxyExpressionWithAST(callAst, newDeps);
|
|
672
1076
|
}
|
|
673
1077
|
});
|
|
674
1078
|
setProxyMetadata(proxy, {
|
|
675
1079
|
type: "expression",
|
|
676
1080
|
path,
|
|
677
1081
|
rootVariable: rootId,
|
|
678
|
-
|
|
1082
|
+
ast,
|
|
679
1083
|
dependencies: deps
|
|
680
1084
|
});
|
|
681
1085
|
return proxy;
|
|
682
1086
|
}
|
|
683
1087
|
/**
|
|
684
|
-
*
|
|
1088
|
+
* 创建带完整 AST 的 Proxy(方法调用后)
|
|
685
1089
|
* 可以继续链式访问和调用
|
|
686
1090
|
*
|
|
687
|
-
* @param
|
|
1091
|
+
* @param ast - 完整的表达式 AST
|
|
688
1092
|
* @param deps - 依赖集合
|
|
689
1093
|
* @returns Proxy 包装的 Expression
|
|
690
1094
|
*/
|
|
691
|
-
function
|
|
1095
|
+
function createProxyExpressionWithAST(ast, deps) {
|
|
692
1096
|
const proxy = new Proxy(function() {}, {
|
|
693
1097
|
get(_target, prop) {
|
|
694
1098
|
if (typeof prop === "symbol") return void 0;
|
|
695
|
-
return
|
|
1099
|
+
return createProxyExpressionWithAST({
|
|
1100
|
+
type: "MemberExpr",
|
|
1101
|
+
object: ast,
|
|
1102
|
+
property: {
|
|
1103
|
+
type: "Identifier",
|
|
1104
|
+
name: String(prop)
|
|
1105
|
+
},
|
|
1106
|
+
computed: false,
|
|
1107
|
+
optional: false
|
|
1108
|
+
}, deps);
|
|
696
1109
|
},
|
|
697
1110
|
apply(_target, _thisArg, args) {
|
|
698
|
-
const
|
|
1111
|
+
const callAst = {
|
|
1112
|
+
type: "CallExpr",
|
|
1113
|
+
callee: ast,
|
|
1114
|
+
arguments: args.map(serializeArgumentToAST),
|
|
1115
|
+
optional: false
|
|
1116
|
+
};
|
|
699
1117
|
const newDeps = new Set(deps);
|
|
700
1118
|
collectDepsFromArgs(args, newDeps);
|
|
701
|
-
return
|
|
1119
|
+
return createProxyExpressionWithAST(callAst, newDeps);
|
|
702
1120
|
}
|
|
703
1121
|
});
|
|
704
1122
|
setProxyMetadata(proxy, {
|
|
705
1123
|
type: "expression",
|
|
706
|
-
path: [
|
|
707
|
-
|
|
1124
|
+
path: [],
|
|
1125
|
+
ast,
|
|
708
1126
|
dependencies: deps
|
|
709
1127
|
});
|
|
710
1128
|
return proxy;
|
|
@@ -770,32 +1188,31 @@ const ALLOWED_GLOBALS = new Set([
|
|
|
770
1188
|
"isNaN",
|
|
771
1189
|
"isFinite",
|
|
772
1190
|
"parseInt",
|
|
773
|
-
"parseFloat"
|
|
1191
|
+
"parseFloat",
|
|
1192
|
+
"BigInt",
|
|
1193
|
+
"URL",
|
|
1194
|
+
"URLSearchParams",
|
|
1195
|
+
"Map",
|
|
1196
|
+
"Set",
|
|
1197
|
+
"Int8Array",
|
|
1198
|
+
"Uint8Array",
|
|
1199
|
+
"Uint8ClampedArray",
|
|
1200
|
+
"Int16Array",
|
|
1201
|
+
"Uint16Array",
|
|
1202
|
+
"Int32Array",
|
|
1203
|
+
"Uint32Array",
|
|
1204
|
+
"Float32Array",
|
|
1205
|
+
"Float64Array",
|
|
1206
|
+
"BigInt64Array",
|
|
1207
|
+
"BigUint64Array",
|
|
1208
|
+
"ArrayBuffer",
|
|
1209
|
+
"DataView"
|
|
774
1210
|
]);
|
|
775
1211
|
/**
|
|
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
1212
|
* 将 Proxy Expression 编译为可序列化的 JSON 结构
|
|
796
1213
|
*
|
|
797
1214
|
* @template TResult - 表达式结果类型
|
|
798
|
-
* @param expression - Proxy Expression
|
|
1215
|
+
* @param expression - Proxy Expression,或包含 Proxy 的对象/数组/原始值
|
|
799
1216
|
* @param variables - 所有使用的变量定义
|
|
800
1217
|
* @param options - 编译选项
|
|
801
1218
|
* @returns 编译后的数据结构 [变量名列表, 表达式1, 表达式2, ...]
|
|
@@ -814,25 +1231,31 @@ function preprocessExpression(source, context) {
|
|
|
814
1231
|
*/
|
|
815
1232
|
function compile(expression, variables, options = {}) {
|
|
816
1233
|
const { shortCircuit = true } = options;
|
|
817
|
-
const
|
|
818
|
-
if (!meta?.source) throw new Error("Invalid expression: expected a Proxy Expression");
|
|
819
|
-
const source = preprocessExpression(meta.source, variables);
|
|
1234
|
+
const ast = serializeArgumentToAST(expression);
|
|
820
1235
|
const variableOrder = [];
|
|
821
1236
|
const variableToIndex = /* @__PURE__ */ new Map();
|
|
822
1237
|
for (const name of Object.keys(variables)) if (!variableToIndex.has(name)) {
|
|
823
1238
|
variableToIndex.set(name, variableOrder.length);
|
|
824
1239
|
variableOrder.push(name);
|
|
825
1240
|
}
|
|
826
|
-
const
|
|
1241
|
+
const descToName = /* @__PURE__ */ new Map();
|
|
1242
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
1243
|
+
const id = getVariableId(value);
|
|
1244
|
+
if (id && id.description) descToName.set(id.description, name);
|
|
1245
|
+
}
|
|
827
1246
|
const undefinedVars = [];
|
|
828
1247
|
const transformed = transformIdentifiers(ast, (name) => {
|
|
1248
|
+
const placeholderMatch = name.match(/^\$\$VAR_(.+)\$\$$/);
|
|
1249
|
+
if (placeholderMatch) {
|
|
1250
|
+
const desc = placeholderMatch[1];
|
|
1251
|
+
const varName = descToName.get(desc);
|
|
1252
|
+
if (!varName) throw new Error(`Unknown variable placeholder: ${name}`);
|
|
1253
|
+
name = varName;
|
|
1254
|
+
}
|
|
829
1255
|
const index = variableToIndex.get(name);
|
|
830
|
-
if (index !== void 0) return {
|
|
831
|
-
type: "Identifier",
|
|
832
|
-
name: `$${index}`
|
|
833
|
-
};
|
|
1256
|
+
if (index !== void 0) return `$${index}`;
|
|
834
1257
|
if (!ALLOWED_GLOBALS.has(name)) undefinedVars.push(name);
|
|
835
|
-
return
|
|
1258
|
+
return name;
|
|
836
1259
|
});
|
|
837
1260
|
if (undefinedVars.length > 0) throw new Error(`Undefined variable(s): ${[...new Set(undefinedVars)].join(", ")}`);
|
|
838
1261
|
const expressions = [];
|
|
@@ -897,56 +1320,6 @@ function compile(expression, variables, options = {}) {
|
|
|
897
1320
|
} else expressions.push(generate(transformed));
|
|
898
1321
|
return [variableOrder, ...expressions];
|
|
899
1322
|
}
|
|
900
|
-
/**
|
|
901
|
-
* 将 AST 中的标识符替换为对应的 AST 节点
|
|
902
|
-
*
|
|
903
|
-
* @param node - 要转换的 AST 节点
|
|
904
|
-
* @param getReplacementAst - 根据标识符名称返回替换的 AST 节点,返回 null 表示不替换
|
|
905
|
-
* @returns 转换后的 AST 节点
|
|
906
|
-
*/
|
|
907
|
-
function transformIdentifiers(node, getReplacementAst) {
|
|
908
|
-
switch (node.type) {
|
|
909
|
-
case "Identifier": return getReplacementAst(node.name) ?? node;
|
|
910
|
-
case "BinaryExpr": return {
|
|
911
|
-
...node,
|
|
912
|
-
left: transformIdentifiers(node.left, getReplacementAst),
|
|
913
|
-
right: transformIdentifiers(node.right, getReplacementAst)
|
|
914
|
-
};
|
|
915
|
-
case "UnaryExpr": return {
|
|
916
|
-
...node,
|
|
917
|
-
argument: transformIdentifiers(node.argument, getReplacementAst)
|
|
918
|
-
};
|
|
919
|
-
case "ConditionalExpr": return {
|
|
920
|
-
...node,
|
|
921
|
-
test: transformIdentifiers(node.test, getReplacementAst),
|
|
922
|
-
consequent: transformIdentifiers(node.consequent, getReplacementAst),
|
|
923
|
-
alternate: transformIdentifiers(node.alternate, getReplacementAst)
|
|
924
|
-
};
|
|
925
|
-
case "MemberExpr": return {
|
|
926
|
-
...node,
|
|
927
|
-
object: transformIdentifiers(node.object, getReplacementAst),
|
|
928
|
-
property: node.computed ? transformIdentifiers(node.property, getReplacementAst) : node.property
|
|
929
|
-
};
|
|
930
|
-
case "CallExpr": return {
|
|
931
|
-
...node,
|
|
932
|
-
callee: transformIdentifiers(node.callee, getReplacementAst),
|
|
933
|
-
arguments: node.arguments.map((arg) => transformIdentifiers(arg, getReplacementAst))
|
|
934
|
-
};
|
|
935
|
-
case "ArrayExpr": return {
|
|
936
|
-
...node,
|
|
937
|
-
elements: node.elements.map((el) => transformIdentifiers(el, getReplacementAst))
|
|
938
|
-
};
|
|
939
|
-
case "ObjectExpr": return {
|
|
940
|
-
...node,
|
|
941
|
-
properties: node.properties.map((prop) => ({
|
|
942
|
-
...prop,
|
|
943
|
-
key: prop.computed ? transformIdentifiers(prop.key, getReplacementAst) : prop.key,
|
|
944
|
-
value: transformIdentifiers(prop.value, getReplacementAst)
|
|
945
|
-
}))
|
|
946
|
-
};
|
|
947
|
-
default: return node;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
1323
|
|
|
951
1324
|
//#endregion
|
|
952
1325
|
//#region src/evaluate.ts
|
|
@@ -1105,7 +1478,11 @@ function expr(context) {
|
|
|
1105
1478
|
const deps = /* @__PURE__ */ new Set();
|
|
1106
1479
|
const nameToId = /* @__PURE__ */ new Map();
|
|
1107
1480
|
for (const [name, value] of Object.entries(context)) {
|
|
1108
|
-
|
|
1481
|
+
let id = getVariableId(value);
|
|
1482
|
+
if (!id && (typeof value === "object" || typeof value === "function") && value !== null) {
|
|
1483
|
+
const meta = getProxyMetadata(value);
|
|
1484
|
+
if (meta?.type === "variable" && meta.rootVariable) id = meta.rootVariable;
|
|
1485
|
+
}
|
|
1109
1486
|
if (id) {
|
|
1110
1487
|
deps.add(id);
|
|
1111
1488
|
nameToId.set(name, id);
|
|
@@ -1114,33 +1491,115 @@ function expr(context) {
|
|
|
1114
1491
|
if (meta?.dependencies) for (const dep of meta.dependencies) deps.add(dep);
|
|
1115
1492
|
}
|
|
1116
1493
|
}
|
|
1117
|
-
|
|
1118
|
-
for (const [name,
|
|
1119
|
-
|
|
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)) {
|
|
1494
|
+
const nameToExprAST = /* @__PURE__ */ new Map();
|
|
1495
|
+
for (const [name, value] of Object.entries(context)) if ((typeof value === "object" || typeof value === "function") && value !== null) {
|
|
1496
|
+
if (nameToId.has(name)) continue;
|
|
1124
1497
|
const meta = getProxyMetadata(value);
|
|
1125
|
-
if (meta?.
|
|
1126
|
-
const regex = new RegExp(`\\b${name}\\b`, "g");
|
|
1127
|
-
const escapedSource = `(${meta.source})`.replace(/\$/g, "$$$$");
|
|
1128
|
-
transformedSource = transformedSource.replace(regex, escapedSource);
|
|
1129
|
-
}
|
|
1498
|
+
if (meta?.ast) nameToExprAST.set(name, meta.ast);
|
|
1130
1499
|
}
|
|
1131
|
-
return
|
|
1500
|
+
return createProxyExpressionWithAST(transformIdentifiers(parse(source), (name) => {
|
|
1501
|
+
const id = nameToId.get(name);
|
|
1502
|
+
if (id) return getVariablePlaceholder(id);
|
|
1503
|
+
const exprAST = nameToExprAST.get(name);
|
|
1504
|
+
if (exprAST) return exprAST;
|
|
1505
|
+
return name;
|
|
1506
|
+
}), deps);
|
|
1132
1507
|
};
|
|
1133
1508
|
}
|
|
1134
1509
|
|
|
1135
1510
|
//#endregion
|
|
1136
|
-
//#region src/
|
|
1511
|
+
//#region src/lambda.ts
|
|
1512
|
+
/**
|
|
1513
|
+
* Lambda 参数计数器,用于生成唯一 ID
|
|
1514
|
+
*/
|
|
1515
|
+
let lambdaParamCounter = 0;
|
|
1137
1516
|
/**
|
|
1138
|
-
*
|
|
1517
|
+
* Lambda 参数到索引的映射
|
|
1518
|
+
* 用于编译时确定参数位置
|
|
1139
1519
|
*/
|
|
1140
|
-
|
|
1141
|
-
|
|
1520
|
+
const lambdaParamIndices = /* @__PURE__ */ new WeakMap();
|
|
1521
|
+
/**
|
|
1522
|
+
* 创建 lambda 参数代理
|
|
1523
|
+
* 生成带特殊标记的 Proxy,用于在表达式中追踪参数
|
|
1524
|
+
*
|
|
1525
|
+
* @param index - 参数索引(0, 1, 2...)
|
|
1526
|
+
* @returns Lambda 参数代理
|
|
1527
|
+
*/
|
|
1528
|
+
function createLambdaParam(index) {
|
|
1529
|
+
const proxy = createProxyVariable(Symbol(`lambda_param_${lambdaParamCounter++}_${index}`));
|
|
1530
|
+
lambdaParamIndices.set(proxy, index);
|
|
1531
|
+
return proxy;
|
|
1142
1532
|
}
|
|
1143
1533
|
/**
|
|
1534
|
+
* 创建类型安全的 lambda 表达式
|
|
1535
|
+
*
|
|
1536
|
+
* @template Args - 参数类型元组
|
|
1537
|
+
* @template R - 返回值类型
|
|
1538
|
+
* @param builder - Lambda 构建函数,接收参数代理,返回函数体表达式
|
|
1539
|
+
* @returns Lambda 表达式代理
|
|
1540
|
+
*
|
|
1541
|
+
* @example
|
|
1542
|
+
* ```ts
|
|
1543
|
+
* const add = lambda<[number, number], number>(
|
|
1544
|
+
* (a, b) => expr({ a, b })("a + b")
|
|
1545
|
+
* );
|
|
1546
|
+
*
|
|
1547
|
+
* const numbers = variable<number[]>();
|
|
1548
|
+
* const sum = numbers.reduce(add, 0);
|
|
1549
|
+
* ```
|
|
1550
|
+
*/
|
|
1551
|
+
function lambda(builder) {
|
|
1552
|
+
const paramCount = builder.length;
|
|
1553
|
+
const params = [];
|
|
1554
|
+
const paramSymbols = [];
|
|
1555
|
+
for (let i = 0; i < paramCount; i++) {
|
|
1556
|
+
const param = createLambdaParam(i);
|
|
1557
|
+
params.push(param);
|
|
1558
|
+
const meta = getProxyMetadata(param);
|
|
1559
|
+
if (meta?.rootVariable) paramSymbols.push(meta.rootVariable);
|
|
1560
|
+
}
|
|
1561
|
+
const bodyExpr = builder(...params);
|
|
1562
|
+
let bodyAst;
|
|
1563
|
+
let bodyDeps;
|
|
1564
|
+
const meta = (typeof bodyExpr === "object" || typeof bodyExpr === "function") && bodyExpr !== null ? getProxyMetadata(bodyExpr) : void 0;
|
|
1565
|
+
if (meta?.ast) {
|
|
1566
|
+
bodyAst = meta.ast;
|
|
1567
|
+
bodyDeps = meta.dependencies ?? /* @__PURE__ */ new Set();
|
|
1568
|
+
} else {
|
|
1569
|
+
bodyAst = serializeArgumentToAST(bodyExpr);
|
|
1570
|
+
bodyDeps = /* @__PURE__ */ new Set();
|
|
1571
|
+
collectDepsFromArgs([bodyExpr], bodyDeps);
|
|
1572
|
+
}
|
|
1573
|
+
const transformedBodyAst = transformIdentifiers(bodyAst, (name) => {
|
|
1574
|
+
for (let i = 0; i < paramSymbols.length; i++) {
|
|
1575
|
+
const sym = paramSymbols[i];
|
|
1576
|
+
if (!sym) continue;
|
|
1577
|
+
if (name === `$$VAR_${sym.description}$$`) return `_${i}`;
|
|
1578
|
+
}
|
|
1579
|
+
return name;
|
|
1580
|
+
});
|
|
1581
|
+
const arrowFunctionAst = {
|
|
1582
|
+
type: "ArrowFunctionExpr",
|
|
1583
|
+
params: params.map((_, i) => ({
|
|
1584
|
+
type: "Identifier",
|
|
1585
|
+
name: `_${i}`
|
|
1586
|
+
})),
|
|
1587
|
+
body: transformedBodyAst
|
|
1588
|
+
};
|
|
1589
|
+
const closureDeps = /* @__PURE__ */ new Set();
|
|
1590
|
+
for (const dep of bodyDeps) if (!paramSymbols.includes(dep)) closureDeps.add(dep);
|
|
1591
|
+
const lambdaProxy = createProxyExpressionWithAST(arrowFunctionAst, closureDeps);
|
|
1592
|
+
const existingMeta = getProxyMetadata(lambdaProxy);
|
|
1593
|
+
if (existingMeta) setProxyMetadata(lambdaProxy, {
|
|
1594
|
+
...existingMeta,
|
|
1595
|
+
type: "expression"
|
|
1596
|
+
});
|
|
1597
|
+
return lambdaProxy;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
//#endregion
|
|
1601
|
+
//#region src/template.ts
|
|
1602
|
+
/**
|
|
1144
1603
|
* Tagged template 函数,用于创建包含变量的字符串表达式
|
|
1145
1604
|
*
|
|
1146
1605
|
* @example
|
|
@@ -1161,17 +1620,62 @@ function t(strings, ...values) {
|
|
|
1161
1620
|
const parts = [];
|
|
1162
1621
|
for (let i = 0; i < strings.length; i++) {
|
|
1163
1622
|
const str = strings[i];
|
|
1164
|
-
if (str.length > 0) parts.push(
|
|
1623
|
+
if (str.length > 0) parts.push({
|
|
1624
|
+
type: "StringLiteral",
|
|
1625
|
+
value: str,
|
|
1626
|
+
quote: "\""
|
|
1627
|
+
});
|
|
1165
1628
|
if (i < values.length) {
|
|
1166
|
-
const
|
|
1167
|
-
parts.push(
|
|
1629
|
+
const ast = serializeArgumentToAST(values[i]);
|
|
1630
|
+
parts.push(ast);
|
|
1168
1631
|
}
|
|
1169
1632
|
}
|
|
1170
|
-
if (parts.length === 0) return
|
|
1171
|
-
|
|
1172
|
-
|
|
1633
|
+
if (parts.length === 0) return createProxyExpressionWithAST({
|
|
1634
|
+
type: "StringLiteral",
|
|
1635
|
+
value: "",
|
|
1636
|
+
quote: "\""
|
|
1637
|
+
}, deps);
|
|
1638
|
+
if (parts.length === 1) return createProxyExpressionWithAST(parts[0], deps);
|
|
1639
|
+
let resultAst = parts[0];
|
|
1640
|
+
for (let i = 1; i < parts.length; i++) resultAst = {
|
|
1641
|
+
type: "BinaryExpr",
|
|
1642
|
+
operator: "+",
|
|
1643
|
+
left: resultAst,
|
|
1644
|
+
right: parts[i]
|
|
1645
|
+
};
|
|
1646
|
+
return createProxyExpressionWithAST(resultAst, deps);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
//#endregion
|
|
1650
|
+
//#region src/wrap.ts
|
|
1651
|
+
/**
|
|
1652
|
+
* 将静态值包装为 Proxy Expression
|
|
1653
|
+
* 返回的 Proxy 可以像 Variable 一样调用方法和访问属性
|
|
1654
|
+
*
|
|
1655
|
+
* @template T - 值的类型
|
|
1656
|
+
* @param value - 要包装的静态值(支持原始值、对象、数组、Date、RegExp 等)
|
|
1657
|
+
* @returns Proxy Expression,可以继续链式调用
|
|
1658
|
+
*
|
|
1659
|
+
* @example
|
|
1660
|
+
* ```ts
|
|
1661
|
+
* // 包装 RegExp
|
|
1662
|
+
* const pattern = wrap(/^[a-z]+$/i);
|
|
1663
|
+
* const input = variable<string>();
|
|
1664
|
+
* const result = pattern.match(input);
|
|
1665
|
+
*
|
|
1666
|
+
* // 包装 Date
|
|
1667
|
+
* const now = wrap(new Date());
|
|
1668
|
+
* const year = now.getFullYear();
|
|
1669
|
+
*
|
|
1670
|
+
* // 包装数组
|
|
1671
|
+
* const numbers = wrap([1, 2, 3, 4, 5]);
|
|
1672
|
+
* const doubled = numbers.map((x) => x * 2);
|
|
1673
|
+
* ```
|
|
1674
|
+
*/
|
|
1675
|
+
function wrap(value) {
|
|
1676
|
+
return createProxyExpressionWithAST(serializeArgumentToAST(value), /* @__PURE__ */ new Set());
|
|
1173
1677
|
}
|
|
1174
1678
|
|
|
1175
1679
|
//#endregion
|
|
1176
|
-
export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, t, variable };
|
|
1680
|
+
export { compile, evaluate, expr, getVariableId, isProxy, isProxyExpression, isProxyVariable, lambda, t, variable, wrap };
|
|
1177
1681
|
//# sourceMappingURL=index.mjs.map
|