@codehz/json-expr 0.0.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/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/index.d.mts +365 -0
- package/dist/index.mjs +981 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/parser.ts
|
|
4
|
+
const PRECEDENCE = {
|
|
5
|
+
"||": 1,
|
|
6
|
+
"??": 1,
|
|
7
|
+
"&&": 2,
|
|
8
|
+
"|": 3,
|
|
9
|
+
"^": 4,
|
|
10
|
+
"&": 5,
|
|
11
|
+
"==": 6,
|
|
12
|
+
"!=": 6,
|
|
13
|
+
"===": 6,
|
|
14
|
+
"!==": 6,
|
|
15
|
+
"<": 7,
|
|
16
|
+
">": 7,
|
|
17
|
+
"<=": 7,
|
|
18
|
+
">=": 7,
|
|
19
|
+
in: 7,
|
|
20
|
+
instanceof: 7,
|
|
21
|
+
"<<": 8,
|
|
22
|
+
">>": 8,
|
|
23
|
+
">>>": 8,
|
|
24
|
+
"+": 9,
|
|
25
|
+
"-": 9,
|
|
26
|
+
"*": 10,
|
|
27
|
+
"/": 10,
|
|
28
|
+
"%": 10,
|
|
29
|
+
"**": 11
|
|
30
|
+
};
|
|
31
|
+
const RIGHT_ASSOCIATIVE = new Set(["**"]);
|
|
32
|
+
var Parser = class {
|
|
33
|
+
pos = 0;
|
|
34
|
+
source;
|
|
35
|
+
constructor(source) {
|
|
36
|
+
this.source = source;
|
|
37
|
+
}
|
|
38
|
+
parse() {
|
|
39
|
+
this.skipWhitespace();
|
|
40
|
+
const node = this.parseExpression();
|
|
41
|
+
this.skipWhitespace();
|
|
42
|
+
if (this.pos < this.source.length) throw new Error(`Unexpected token at position ${this.pos}: ${this.source.slice(this.pos, this.pos + 10)}`);
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
parseExpression() {
|
|
46
|
+
return this.parseConditional();
|
|
47
|
+
}
|
|
48
|
+
parseConditional() {
|
|
49
|
+
let node = this.parseBinary(0);
|
|
50
|
+
this.skipWhitespace();
|
|
51
|
+
if (this.peek() === "?") {
|
|
52
|
+
this.advance();
|
|
53
|
+
this.skipWhitespace();
|
|
54
|
+
const consequent = this.parseExpression();
|
|
55
|
+
this.skipWhitespace();
|
|
56
|
+
this.expect(":");
|
|
57
|
+
this.skipWhitespace();
|
|
58
|
+
const alternate = this.parseExpression();
|
|
59
|
+
node = {
|
|
60
|
+
type: "ConditionalExpr",
|
|
61
|
+
test: node,
|
|
62
|
+
consequent,
|
|
63
|
+
alternate
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
parseBinary(minPrec) {
|
|
69
|
+
let left = this.parseUnary();
|
|
70
|
+
while (true) {
|
|
71
|
+
this.skipWhitespace();
|
|
72
|
+
const op = this.peekOperator();
|
|
73
|
+
if (!op || PRECEDENCE[op] === void 0 || PRECEDENCE[op] < minPrec) break;
|
|
74
|
+
this.pos += op.length;
|
|
75
|
+
this.skipWhitespace();
|
|
76
|
+
const nextMinPrec = RIGHT_ASSOCIATIVE.has(op) ? PRECEDENCE[op] : PRECEDENCE[op] + 1;
|
|
77
|
+
const right = this.parseBinary(nextMinPrec);
|
|
78
|
+
left = {
|
|
79
|
+
type: "BinaryExpr",
|
|
80
|
+
operator: op,
|
|
81
|
+
left,
|
|
82
|
+
right
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return left;
|
|
86
|
+
}
|
|
87
|
+
parseUnary() {
|
|
88
|
+
this.skipWhitespace();
|
|
89
|
+
const ch = this.peek();
|
|
90
|
+
if (ch === "!" || ch === "~" || ch === "+" || ch === "-") {
|
|
91
|
+
if (ch === "+" || ch === "-") {
|
|
92
|
+
this.advance();
|
|
93
|
+
this.skipWhitespace();
|
|
94
|
+
return {
|
|
95
|
+
type: "UnaryExpr",
|
|
96
|
+
operator: ch,
|
|
97
|
+
argument: this.parseUnary(),
|
|
98
|
+
prefix: true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
this.advance();
|
|
102
|
+
this.skipWhitespace();
|
|
103
|
+
return {
|
|
104
|
+
type: "UnaryExpr",
|
|
105
|
+
operator: ch,
|
|
106
|
+
argument: this.parseUnary(),
|
|
107
|
+
prefix: true
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (this.matchKeyword("typeof")) {
|
|
111
|
+
this.skipWhitespace();
|
|
112
|
+
return {
|
|
113
|
+
type: "UnaryExpr",
|
|
114
|
+
operator: "typeof",
|
|
115
|
+
argument: this.parseUnary(),
|
|
116
|
+
prefix: true
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (this.matchKeyword("void")) {
|
|
120
|
+
this.skipWhitespace();
|
|
121
|
+
return {
|
|
122
|
+
type: "UnaryExpr",
|
|
123
|
+
operator: "void",
|
|
124
|
+
argument: this.parseUnary(),
|
|
125
|
+
prefix: true
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return this.parsePostfix();
|
|
129
|
+
}
|
|
130
|
+
parsePostfix() {
|
|
131
|
+
let node = this.parsePrimary();
|
|
132
|
+
while (true) {
|
|
133
|
+
this.skipWhitespace();
|
|
134
|
+
const ch = this.peek();
|
|
135
|
+
if (ch === ".") {
|
|
136
|
+
this.advance();
|
|
137
|
+
this.skipWhitespace();
|
|
138
|
+
const property = this.parseIdentifier();
|
|
139
|
+
node = {
|
|
140
|
+
type: "MemberExpr",
|
|
141
|
+
object: node,
|
|
142
|
+
property,
|
|
143
|
+
computed: false,
|
|
144
|
+
optional: false
|
|
145
|
+
};
|
|
146
|
+
} else if (ch === "[") {
|
|
147
|
+
this.advance();
|
|
148
|
+
this.skipWhitespace();
|
|
149
|
+
const property = this.parseExpression();
|
|
150
|
+
this.skipWhitespace();
|
|
151
|
+
this.expect("]");
|
|
152
|
+
node = {
|
|
153
|
+
type: "MemberExpr",
|
|
154
|
+
object: node,
|
|
155
|
+
property,
|
|
156
|
+
computed: true,
|
|
157
|
+
optional: false
|
|
158
|
+
};
|
|
159
|
+
} else if (ch === "(") {
|
|
160
|
+
this.advance();
|
|
161
|
+
const args = this.parseArguments();
|
|
162
|
+
this.expect(")");
|
|
163
|
+
node = {
|
|
164
|
+
type: "CallExpr",
|
|
165
|
+
callee: node,
|
|
166
|
+
arguments: args,
|
|
167
|
+
optional: false
|
|
168
|
+
};
|
|
169
|
+
} else if (ch === "?" && this.peekAt(1) === ".") {
|
|
170
|
+
this.advance();
|
|
171
|
+
this.advance();
|
|
172
|
+
this.skipWhitespace();
|
|
173
|
+
if (this.peek() === "[") {
|
|
174
|
+
this.advance();
|
|
175
|
+
this.skipWhitespace();
|
|
176
|
+
const property = this.parseExpression();
|
|
177
|
+
this.skipWhitespace();
|
|
178
|
+
this.expect("]");
|
|
179
|
+
node = {
|
|
180
|
+
type: "MemberExpr",
|
|
181
|
+
object: node,
|
|
182
|
+
property,
|
|
183
|
+
computed: true,
|
|
184
|
+
optional: true
|
|
185
|
+
};
|
|
186
|
+
} else if (this.peek() === "(") {
|
|
187
|
+
this.advance();
|
|
188
|
+
const args = this.parseArguments();
|
|
189
|
+
this.expect(")");
|
|
190
|
+
node = {
|
|
191
|
+
type: "CallExpr",
|
|
192
|
+
callee: node,
|
|
193
|
+
arguments: args,
|
|
194
|
+
optional: true
|
|
195
|
+
};
|
|
196
|
+
} else {
|
|
197
|
+
const property = this.parseIdentifier();
|
|
198
|
+
node = {
|
|
199
|
+
type: "MemberExpr",
|
|
200
|
+
object: node,
|
|
201
|
+
property,
|
|
202
|
+
computed: false,
|
|
203
|
+
optional: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
} else break;
|
|
207
|
+
}
|
|
208
|
+
return node;
|
|
209
|
+
}
|
|
210
|
+
parsePrimary() {
|
|
211
|
+
this.skipWhitespace();
|
|
212
|
+
const ch = this.peek();
|
|
213
|
+
if (this.isDigit(ch) || ch === "." && this.isDigit(this.peekAt(1))) return this.parseNumber();
|
|
214
|
+
if (ch === "\"" || ch === "'" || ch === "`") return this.parseString();
|
|
215
|
+
if (ch === "[") return this.parseArray();
|
|
216
|
+
if (ch === "{") return this.parseObject();
|
|
217
|
+
if (ch === "(") {
|
|
218
|
+
this.advance();
|
|
219
|
+
this.skipWhitespace();
|
|
220
|
+
const expr = this.parseExpression();
|
|
221
|
+
this.skipWhitespace();
|
|
222
|
+
this.expect(")");
|
|
223
|
+
return expr;
|
|
224
|
+
}
|
|
225
|
+
if (this.matchKeyword("true")) return {
|
|
226
|
+
type: "BooleanLiteral",
|
|
227
|
+
value: true
|
|
228
|
+
};
|
|
229
|
+
if (this.matchKeyword("false")) return {
|
|
230
|
+
type: "BooleanLiteral",
|
|
231
|
+
value: false
|
|
232
|
+
};
|
|
233
|
+
if (this.matchKeyword("null")) return { type: "NullLiteral" };
|
|
234
|
+
if (this.matchKeyword("undefined")) return {
|
|
235
|
+
type: "Identifier",
|
|
236
|
+
name: "undefined"
|
|
237
|
+
};
|
|
238
|
+
if (this.isIdentifierStart(ch)) return this.parseIdentifier();
|
|
239
|
+
throw new Error(`Unexpected character at position ${this.pos}: ${ch}`);
|
|
240
|
+
}
|
|
241
|
+
parseNumber() {
|
|
242
|
+
const start = this.pos;
|
|
243
|
+
if (this.peek() === "0") {
|
|
244
|
+
const next = this.peekAt(1)?.toLowerCase();
|
|
245
|
+
if (next === "x" || next === "o" || next === "b") {
|
|
246
|
+
this.advance();
|
|
247
|
+
this.advance();
|
|
248
|
+
while (this.isHexDigit(this.peek())) this.advance();
|
|
249
|
+
const raw = this.source.slice(start, this.pos);
|
|
250
|
+
return {
|
|
251
|
+
type: "NumberLiteral",
|
|
252
|
+
value: Number(raw),
|
|
253
|
+
raw
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
while (this.isDigit(this.peek())) this.advance();
|
|
258
|
+
if (this.peek() === "." && this.isDigit(this.peekAt(1))) {
|
|
259
|
+
this.advance();
|
|
260
|
+
while (this.isDigit(this.peek())) this.advance();
|
|
261
|
+
}
|
|
262
|
+
if (this.peek()?.toLowerCase() === "e") {
|
|
263
|
+
this.advance();
|
|
264
|
+
if (this.peek() === "+" || this.peek() === "-") this.advance();
|
|
265
|
+
while (this.isDigit(this.peek())) this.advance();
|
|
266
|
+
}
|
|
267
|
+
const raw = this.source.slice(start, this.pos);
|
|
268
|
+
return {
|
|
269
|
+
type: "NumberLiteral",
|
|
270
|
+
value: Number(raw),
|
|
271
|
+
raw
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
parseString() {
|
|
275
|
+
const quote = this.peek();
|
|
276
|
+
this.advance();
|
|
277
|
+
let value = "";
|
|
278
|
+
while (this.pos < this.source.length && this.peek() !== quote) if (this.peek() === "\\") {
|
|
279
|
+
this.advance();
|
|
280
|
+
const escaped = this.peek();
|
|
281
|
+
switch (escaped) {
|
|
282
|
+
case "n":
|
|
283
|
+
value += "\n";
|
|
284
|
+
break;
|
|
285
|
+
case "r":
|
|
286
|
+
value += "\r";
|
|
287
|
+
break;
|
|
288
|
+
case "t":
|
|
289
|
+
value += " ";
|
|
290
|
+
break;
|
|
291
|
+
case "\\":
|
|
292
|
+
value += "\\";
|
|
293
|
+
break;
|
|
294
|
+
case "'":
|
|
295
|
+
value += "'";
|
|
296
|
+
break;
|
|
297
|
+
case "\"":
|
|
298
|
+
value += "\"";
|
|
299
|
+
break;
|
|
300
|
+
case "`":
|
|
301
|
+
value += "`";
|
|
302
|
+
break;
|
|
303
|
+
default: value += escaped;
|
|
304
|
+
}
|
|
305
|
+
this.advance();
|
|
306
|
+
} else {
|
|
307
|
+
value += this.peek();
|
|
308
|
+
this.advance();
|
|
309
|
+
}
|
|
310
|
+
this.expect(quote);
|
|
311
|
+
return {
|
|
312
|
+
type: "StringLiteral",
|
|
313
|
+
value,
|
|
314
|
+
quote
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
parseArray() {
|
|
318
|
+
this.expect("[");
|
|
319
|
+
const elements = [];
|
|
320
|
+
this.skipWhitespace();
|
|
321
|
+
while (this.peek() !== "]") {
|
|
322
|
+
elements.push(this.parseExpression());
|
|
323
|
+
this.skipWhitespace();
|
|
324
|
+
if (this.peek() === ",") {
|
|
325
|
+
this.advance();
|
|
326
|
+
this.skipWhitespace();
|
|
327
|
+
} else break;
|
|
328
|
+
}
|
|
329
|
+
this.expect("]");
|
|
330
|
+
return {
|
|
331
|
+
type: "ArrayExpr",
|
|
332
|
+
elements
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
parseObject() {
|
|
336
|
+
this.expect("{");
|
|
337
|
+
const properties = [];
|
|
338
|
+
this.skipWhitespace();
|
|
339
|
+
while (this.peek() !== "}") {
|
|
340
|
+
const prop = this.parseObjectProperty();
|
|
341
|
+
properties.push(prop);
|
|
342
|
+
this.skipWhitespace();
|
|
343
|
+
if (this.peek() === ",") {
|
|
344
|
+
this.advance();
|
|
345
|
+
this.skipWhitespace();
|
|
346
|
+
} else break;
|
|
347
|
+
}
|
|
348
|
+
this.expect("}");
|
|
349
|
+
return {
|
|
350
|
+
type: "ObjectExpr",
|
|
351
|
+
properties
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
parseObjectProperty() {
|
|
355
|
+
this.skipWhitespace();
|
|
356
|
+
let key;
|
|
357
|
+
let computed = false;
|
|
358
|
+
if (this.peek() === "[") {
|
|
359
|
+
this.advance();
|
|
360
|
+
this.skipWhitespace();
|
|
361
|
+
key = this.parseExpression();
|
|
362
|
+
this.skipWhitespace();
|
|
363
|
+
this.expect("]");
|
|
364
|
+
computed = true;
|
|
365
|
+
} else if (this.peek() === "\"" || this.peek() === "'") key = this.parseString();
|
|
366
|
+
else key = this.parseIdentifier();
|
|
367
|
+
this.skipWhitespace();
|
|
368
|
+
if (this.peek() === ":") {
|
|
369
|
+
this.advance();
|
|
370
|
+
this.skipWhitespace();
|
|
371
|
+
const value = this.parseExpression();
|
|
372
|
+
return {
|
|
373
|
+
key,
|
|
374
|
+
value,
|
|
375
|
+
computed,
|
|
376
|
+
shorthand: false
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (key.type !== "Identifier") throw new Error("Shorthand property must be an identifier");
|
|
380
|
+
return {
|
|
381
|
+
key,
|
|
382
|
+
value: key,
|
|
383
|
+
computed: false,
|
|
384
|
+
shorthand: true
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
parseIdentifier() {
|
|
388
|
+
const start = this.pos;
|
|
389
|
+
while (this.isIdentifierPart(this.peek())) this.advance();
|
|
390
|
+
const name = this.source.slice(start, this.pos);
|
|
391
|
+
if (!name) throw new Error(`Expected identifier at position ${this.pos}`);
|
|
392
|
+
return {
|
|
393
|
+
type: "Identifier",
|
|
394
|
+
name
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
parseArguments() {
|
|
398
|
+
const args = [];
|
|
399
|
+
this.skipWhitespace();
|
|
400
|
+
while (this.peek() !== ")") {
|
|
401
|
+
args.push(this.parseExpression());
|
|
402
|
+
this.skipWhitespace();
|
|
403
|
+
if (this.peek() === ",") {
|
|
404
|
+
this.advance();
|
|
405
|
+
this.skipWhitespace();
|
|
406
|
+
} else break;
|
|
407
|
+
}
|
|
408
|
+
return args;
|
|
409
|
+
}
|
|
410
|
+
peekOperator() {
|
|
411
|
+
for (const op of [
|
|
412
|
+
">>>",
|
|
413
|
+
"===",
|
|
414
|
+
"!==",
|
|
415
|
+
"instanceof",
|
|
416
|
+
"&&",
|
|
417
|
+
"||",
|
|
418
|
+
"??",
|
|
419
|
+
"==",
|
|
420
|
+
"!=",
|
|
421
|
+
"<=",
|
|
422
|
+
">=",
|
|
423
|
+
"<<",
|
|
424
|
+
">>",
|
|
425
|
+
"**",
|
|
426
|
+
"in",
|
|
427
|
+
"+",
|
|
428
|
+
"-",
|
|
429
|
+
"*",
|
|
430
|
+
"/",
|
|
431
|
+
"%",
|
|
432
|
+
"<",
|
|
433
|
+
">",
|
|
434
|
+
"&",
|
|
435
|
+
"|",
|
|
436
|
+
"^"
|
|
437
|
+
]) if (this.source.startsWith(op, this.pos)) {
|
|
438
|
+
if (op === "in" || op === "instanceof") {
|
|
439
|
+
const nextChar = this.source[this.pos + op.length];
|
|
440
|
+
if (nextChar && this.isIdentifierPart(nextChar)) continue;
|
|
441
|
+
}
|
|
442
|
+
return op;
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
matchKeyword(keyword) {
|
|
447
|
+
if (this.source.startsWith(keyword, this.pos)) {
|
|
448
|
+
const nextChar = this.source[this.pos + keyword.length];
|
|
449
|
+
if (!nextChar || !this.isIdentifierPart(nextChar)) {
|
|
450
|
+
this.pos += keyword.length;
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
peek() {
|
|
457
|
+
return this.source[this.pos] || "";
|
|
458
|
+
}
|
|
459
|
+
peekAt(offset) {
|
|
460
|
+
return this.source[this.pos + offset] || "";
|
|
461
|
+
}
|
|
462
|
+
advance() {
|
|
463
|
+
return this.source[this.pos++] || "";
|
|
464
|
+
}
|
|
465
|
+
expect(ch) {
|
|
466
|
+
if (this.peek() !== ch) throw new Error(`Expected '${ch}' at position ${this.pos}, got '${this.peek()}'`);
|
|
467
|
+
this.advance();
|
|
468
|
+
}
|
|
469
|
+
skipWhitespace() {
|
|
470
|
+
while (/\s/.test(this.peek())) this.advance();
|
|
471
|
+
}
|
|
472
|
+
isDigit(ch) {
|
|
473
|
+
return /[0-9]/.test(ch);
|
|
474
|
+
}
|
|
475
|
+
isHexDigit(ch) {
|
|
476
|
+
return /[0-9a-fA-F]/.test(ch);
|
|
477
|
+
}
|
|
478
|
+
isIdentifierStart(ch) {
|
|
479
|
+
return /[a-zA-Z_$]/.test(ch);
|
|
480
|
+
}
|
|
481
|
+
isIdentifierPart(ch) {
|
|
482
|
+
return /[a-zA-Z0-9_$]/.test(ch);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* 解析 JavaScript 表达式为 AST
|
|
487
|
+
*/
|
|
488
|
+
function parse(source) {
|
|
489
|
+
return new Parser(source).parse();
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* 从 AST 生成规范化的代码
|
|
493
|
+
*/
|
|
494
|
+
function generate(node) {
|
|
495
|
+
switch (node.type) {
|
|
496
|
+
case "NumberLiteral": return node.raw;
|
|
497
|
+
case "StringLiteral": return JSON.stringify(node.value);
|
|
498
|
+
case "BooleanLiteral": return node.value ? "true" : "false";
|
|
499
|
+
case "NullLiteral": return "null";
|
|
500
|
+
case "Identifier": return node.name;
|
|
501
|
+
case "BinaryExpr": {
|
|
502
|
+
const left = wrapIfNeeded(node.left, node, "left");
|
|
503
|
+
const right = wrapIfNeeded(node.right, node, "right");
|
|
504
|
+
return `${left}${node.operator}${right}`;
|
|
505
|
+
}
|
|
506
|
+
case "UnaryExpr":
|
|
507
|
+
if (node.prefix) {
|
|
508
|
+
const arg = wrapIfNeeded(node.argument, node, "argument");
|
|
509
|
+
if (node.operator === "typeof" || node.operator === "void") return `${node.operator} ${arg}`;
|
|
510
|
+
return `${node.operator}${arg}`;
|
|
511
|
+
}
|
|
512
|
+
return generate(node.argument) + node.operator;
|
|
513
|
+
case "ConditionalExpr": return `${generate(node.test)}?${generate(node.consequent)}:${generate(node.alternate)}`;
|
|
514
|
+
case "MemberExpr": {
|
|
515
|
+
const object = wrapIfNeeded(node.object, node, "object");
|
|
516
|
+
if (node.computed) {
|
|
517
|
+
const property = generate(node.property);
|
|
518
|
+
return node.optional ? `${object}?.[${property}]` : `${object}[${property}]`;
|
|
519
|
+
}
|
|
520
|
+
const property = generate(node.property);
|
|
521
|
+
return node.optional ? `${object}?.${property}` : `${object}.${property}`;
|
|
522
|
+
}
|
|
523
|
+
case "CallExpr": {
|
|
524
|
+
const callee = wrapIfNeeded(node.callee, node, "callee");
|
|
525
|
+
const args = node.arguments.map(generate).join(",");
|
|
526
|
+
return node.optional ? `${callee}?.(${args})` : `${callee}(${args})`;
|
|
527
|
+
}
|
|
528
|
+
case "ArrayExpr": return `[${node.elements.map(generate).join(",")}]`;
|
|
529
|
+
case "ObjectExpr": return `{${node.properties.map((prop) => {
|
|
530
|
+
if (prop.shorthand) return generate(prop.key);
|
|
531
|
+
return `${prop.computed ? `[${generate(prop.key)}]` : generate(prop.key)}:${generate(prop.value)}`;
|
|
532
|
+
}).join(",")}}`;
|
|
533
|
+
default: {
|
|
534
|
+
const nodeType = node.type ?? "unknown";
|
|
535
|
+
throw new Error(`Unknown node type: ${nodeType}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* 判断是否需要括号包裹,并生成代码
|
|
541
|
+
*/
|
|
542
|
+
function wrapIfNeeded(child, parent, position) {
|
|
543
|
+
const code = generate(child);
|
|
544
|
+
if (needsParens(child, parent, position)) return `(${code})`;
|
|
545
|
+
return code;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 判断子节点是否需要括号
|
|
549
|
+
*/
|
|
550
|
+
function needsParens(child, parent, position) {
|
|
551
|
+
if (child.type === "ConditionalExpr" && parent.type === "BinaryExpr") return true;
|
|
552
|
+
if (child.type === "BinaryExpr" && parent.type === "BinaryExpr") {
|
|
553
|
+
const childPrec = PRECEDENCE[child.operator] || 0;
|
|
554
|
+
const parentPrec = PRECEDENCE[parent.operator] || 0;
|
|
555
|
+
if (childPrec < parentPrec) return true;
|
|
556
|
+
if (childPrec === parentPrec && position === "right") {
|
|
557
|
+
if (!RIGHT_ASSOCIATIVE.has(parent.operator)) return true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (child.type === "UnaryExpr" && parent.type === "BinaryExpr") {
|
|
561
|
+
if (parent.operator === "**" && position === "left") return true;
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* 收集 AST 中所有使用的标识符名称
|
|
567
|
+
*/
|
|
568
|
+
function collectIdentifiers(node) {
|
|
569
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
570
|
+
function visit(n) {
|
|
571
|
+
switch (n.type) {
|
|
572
|
+
case "Identifier":
|
|
573
|
+
identifiers.add(n.name);
|
|
574
|
+
break;
|
|
575
|
+
case "BinaryExpr":
|
|
576
|
+
visit(n.left);
|
|
577
|
+
visit(n.right);
|
|
578
|
+
break;
|
|
579
|
+
case "UnaryExpr":
|
|
580
|
+
visit(n.argument);
|
|
581
|
+
break;
|
|
582
|
+
case "ConditionalExpr":
|
|
583
|
+
visit(n.test);
|
|
584
|
+
visit(n.consequent);
|
|
585
|
+
visit(n.alternate);
|
|
586
|
+
break;
|
|
587
|
+
case "MemberExpr":
|
|
588
|
+
visit(n.object);
|
|
589
|
+
if (n.computed) visit(n.property);
|
|
590
|
+
break;
|
|
591
|
+
case "CallExpr":
|
|
592
|
+
visit(n.callee);
|
|
593
|
+
n.arguments.forEach(visit);
|
|
594
|
+
break;
|
|
595
|
+
case "ArrayExpr":
|
|
596
|
+
n.elements.forEach(visit);
|
|
597
|
+
break;
|
|
598
|
+
case "ObjectExpr":
|
|
599
|
+
n.properties.forEach((prop) => {
|
|
600
|
+
if (prop.computed) visit(prop.key);
|
|
601
|
+
visit(prop.value);
|
|
602
|
+
});
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
visit(node);
|
|
607
|
+
return identifiers;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region src/compile.ts
|
|
612
|
+
/**
|
|
613
|
+
* 将表达式树编译为可序列化的 JSON 结构
|
|
614
|
+
*
|
|
615
|
+
* @template TResult - 表达式结果类型
|
|
616
|
+
* @param expression - 根表达式
|
|
617
|
+
* @param variables - 所有使用的变量定义
|
|
618
|
+
* @param options - 编译选项
|
|
619
|
+
* @returns 编译后的数据结构 [变量名列表, 表达式1, 表达式2, ...]
|
|
620
|
+
*
|
|
621
|
+
* @throws 如果检测到循环依赖或未定义的变量引用
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```ts
|
|
625
|
+
* const x = variable(z.number())
|
|
626
|
+
* const y = variable(z.number())
|
|
627
|
+
* const sum = expr({ x, y })<number>("x + y")
|
|
628
|
+
* const result = expr({ sum })<number>("sum * 2")
|
|
629
|
+
* const compiled = compile(result, { x, y })
|
|
630
|
+
* // => [["x", "y"], "($0+$1)*2"] // 内联优化后
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
function compile(expression, variables, options = {}) {
|
|
634
|
+
const { inline = true } = options;
|
|
635
|
+
const context = {
|
|
636
|
+
variableOrder: [],
|
|
637
|
+
nodeToIndex: /* @__PURE__ */ new Map(),
|
|
638
|
+
expressions: []
|
|
639
|
+
};
|
|
640
|
+
const exprIdMap = /* @__PURE__ */ new WeakMap();
|
|
641
|
+
const getExprId = (expr) => {
|
|
642
|
+
if (!exprIdMap.has(expr)) exprIdMap.set(expr, Symbol("expr"));
|
|
643
|
+
const id = exprIdMap.get(expr);
|
|
644
|
+
if (id === void 0) throw new Error("Expression ID not found");
|
|
645
|
+
return id;
|
|
646
|
+
};
|
|
647
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
648
|
+
const variableNodes = /* @__PURE__ */ new Map();
|
|
649
|
+
const visited = /* @__PURE__ */ new Set();
|
|
650
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
651
|
+
for (const [name, variable] of Object.entries(variables)) {
|
|
652
|
+
const id = Symbol(`var:${name}`);
|
|
653
|
+
const node = {
|
|
654
|
+
id,
|
|
655
|
+
tag: "variable",
|
|
656
|
+
schema: variable.schema
|
|
657
|
+
};
|
|
658
|
+
nodeMap.set(id, node);
|
|
659
|
+
variableNodes.set(name, node);
|
|
660
|
+
}
|
|
661
|
+
const exprNodes = /* @__PURE__ */ new Map();
|
|
662
|
+
const collectNodes = (expr) => {
|
|
663
|
+
const exprId = getExprId(expr);
|
|
664
|
+
if (visited.has(exprId)) return nodeMap.get(exprId);
|
|
665
|
+
if (visiting.has(exprId)) throw new Error("Circular dependency detected in expressions");
|
|
666
|
+
visiting.add(exprId);
|
|
667
|
+
const contextNodes = {};
|
|
668
|
+
for (const [key, contextItem] of Object.entries(expr.context)) {
|
|
669
|
+
const item = contextItem;
|
|
670
|
+
if (item._tag === "variable") {
|
|
671
|
+
const varNode = variableNodes.get(key);
|
|
672
|
+
if (!varNode) throw new Error(`Undefined variable reference: ${key}`);
|
|
673
|
+
contextNodes[key] = varNode;
|
|
674
|
+
} else if (item._tag === "expression") contextNodes[key] = collectNodes(item);
|
|
675
|
+
}
|
|
676
|
+
const node = {
|
|
677
|
+
id: exprId,
|
|
678
|
+
tag: "expression",
|
|
679
|
+
context: contextNodes,
|
|
680
|
+
source: expr.source
|
|
681
|
+
};
|
|
682
|
+
nodeMap.set(exprId, node);
|
|
683
|
+
exprNodes.set(exprId, node);
|
|
684
|
+
visited.add(exprId);
|
|
685
|
+
visiting.delete(exprId);
|
|
686
|
+
return node;
|
|
687
|
+
};
|
|
688
|
+
const rootNode = collectNodes(expression);
|
|
689
|
+
const sortedExprNodes = [];
|
|
690
|
+
const exprVisited = /* @__PURE__ */ new Set();
|
|
691
|
+
const topologicalSort = (node) => {
|
|
692
|
+
if (exprVisited.has(node.id)) return;
|
|
693
|
+
exprVisited.add(node.id);
|
|
694
|
+
if (node.tag === "expression" && node.context) for (const contextNode of Object.values(node.context)) topologicalSort(contextNode);
|
|
695
|
+
if (node.tag === "expression") sortedExprNodes.push(node);
|
|
696
|
+
};
|
|
697
|
+
topologicalSort(rootNode);
|
|
698
|
+
for (const [name, varNode] of variableNodes.entries()) if (!context.nodeToIndex.has(varNode.id)) {
|
|
699
|
+
context.nodeToIndex.set(varNode.id, context.variableOrder.length);
|
|
700
|
+
context.variableOrder.push(name);
|
|
701
|
+
}
|
|
702
|
+
const refCount = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const exprNode of sortedExprNodes) refCount.set(exprNode.id, 0);
|
|
704
|
+
for (const exprNode of sortedExprNodes) if (exprNode.context) {
|
|
705
|
+
for (const contextNode of Object.values(exprNode.context)) if (contextNode.tag === "expression") refCount.set(contextNode.id, (refCount.get(contextNode.id) ?? 0) + 1);
|
|
706
|
+
}
|
|
707
|
+
const canInline = (node) => {
|
|
708
|
+
if (!inline) return false;
|
|
709
|
+
if (node.id === rootNode.id) return false;
|
|
710
|
+
return (refCount.get(node.id) ?? 0) === 1;
|
|
711
|
+
};
|
|
712
|
+
let exprIndex = 0;
|
|
713
|
+
for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
|
|
714
|
+
const index = context.variableOrder.length + exprIndex;
|
|
715
|
+
context.nodeToIndex.set(exprNode.id, index);
|
|
716
|
+
exprIndex++;
|
|
717
|
+
}
|
|
718
|
+
const nodeAstMap = /* @__PURE__ */ new Map();
|
|
719
|
+
for (const [, varNode] of variableNodes.entries()) {
|
|
720
|
+
const index = context.nodeToIndex.get(varNode.id);
|
|
721
|
+
nodeAstMap.set(varNode.id, {
|
|
722
|
+
type: "Identifier",
|
|
723
|
+
name: `$${index}`
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
for (const exprNode of sortedExprNodes) {
|
|
727
|
+
if (!exprNode.context || !exprNode.source) throw new Error("Invalid expression node");
|
|
728
|
+
const usedVariables = extractVariableNames(exprNode.source);
|
|
729
|
+
for (const varName of usedVariables) if (!(varName in exprNode.context)) throw new Error(`Undefined variable reference: ${varName} (available: ${Object.keys(exprNode.context).join(", ")})`);
|
|
730
|
+
const transformed = inlineTransform(parse(exprNode.source), (name) => {
|
|
731
|
+
const contextNode = exprNode.context[name];
|
|
732
|
+
if (!contextNode) return null;
|
|
733
|
+
if (contextNode.tag === "variable") return nodeAstMap.get(contextNode.id) ?? null;
|
|
734
|
+
else if (canInline(contextNode)) return nodeAstMap.get(contextNode.id) ?? null;
|
|
735
|
+
else return {
|
|
736
|
+
type: "Identifier",
|
|
737
|
+
name: `$${context.nodeToIndex.get(contextNode.id)}`
|
|
738
|
+
};
|
|
739
|
+
});
|
|
740
|
+
nodeAstMap.set(exprNode.id, transformed);
|
|
741
|
+
}
|
|
742
|
+
for (const exprNode of sortedExprNodes) if (!canInline(exprNode)) {
|
|
743
|
+
const ast = nodeAstMap.get(exprNode.id);
|
|
744
|
+
context.expressions.push(generate(ast));
|
|
745
|
+
}
|
|
746
|
+
return [context.variableOrder, ...context.expressions];
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* 将 AST 中的标识符替换为对应的 AST 节点(用于内联优化)
|
|
750
|
+
*
|
|
751
|
+
* @param node - 要转换的 AST 节点
|
|
752
|
+
* @param getReplacementAst - 根据标识符名称返回替换的 AST 节点,返回 null 表示不替换
|
|
753
|
+
* @returns 转换后的 AST 节点
|
|
754
|
+
*/
|
|
755
|
+
function inlineTransform(node, getReplacementAst) {
|
|
756
|
+
switch (node.type) {
|
|
757
|
+
case "Identifier": return getReplacementAst(node.name) ?? node;
|
|
758
|
+
case "BinaryExpr": return {
|
|
759
|
+
...node,
|
|
760
|
+
left: inlineTransform(node.left, getReplacementAst),
|
|
761
|
+
right: inlineTransform(node.right, getReplacementAst)
|
|
762
|
+
};
|
|
763
|
+
case "UnaryExpr": return {
|
|
764
|
+
...node,
|
|
765
|
+
argument: inlineTransform(node.argument, getReplacementAst)
|
|
766
|
+
};
|
|
767
|
+
case "ConditionalExpr": return {
|
|
768
|
+
...node,
|
|
769
|
+
test: inlineTransform(node.test, getReplacementAst),
|
|
770
|
+
consequent: inlineTransform(node.consequent, getReplacementAst),
|
|
771
|
+
alternate: inlineTransform(node.alternate, getReplacementAst)
|
|
772
|
+
};
|
|
773
|
+
case "MemberExpr": return {
|
|
774
|
+
...node,
|
|
775
|
+
object: inlineTransform(node.object, getReplacementAst),
|
|
776
|
+
property: node.computed ? inlineTransform(node.property, getReplacementAst) : node.property
|
|
777
|
+
};
|
|
778
|
+
case "CallExpr": return {
|
|
779
|
+
...node,
|
|
780
|
+
callee: inlineTransform(node.callee, getReplacementAst),
|
|
781
|
+
arguments: node.arguments.map((arg) => inlineTransform(arg, getReplacementAst))
|
|
782
|
+
};
|
|
783
|
+
case "ArrayExpr": return {
|
|
784
|
+
...node,
|
|
785
|
+
elements: node.elements.map((el) => inlineTransform(el, getReplacementAst))
|
|
786
|
+
};
|
|
787
|
+
case "ObjectExpr": return {
|
|
788
|
+
...node,
|
|
789
|
+
properties: node.properties.map((prop) => ({
|
|
790
|
+
...prop,
|
|
791
|
+
key: prop.computed ? inlineTransform(prop.key, getReplacementAst) : prop.key,
|
|
792
|
+
value: inlineTransform(prop.value, getReplacementAst)
|
|
793
|
+
}))
|
|
794
|
+
};
|
|
795
|
+
default: return node;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 允许在表达式中直接使用的全局对象
|
|
800
|
+
* 这些对象不需要在上下文中定义
|
|
801
|
+
*/
|
|
802
|
+
const ALLOWED_GLOBALS = new Set([
|
|
803
|
+
"Math",
|
|
804
|
+
"JSON",
|
|
805
|
+
"Number",
|
|
806
|
+
"String",
|
|
807
|
+
"Boolean",
|
|
808
|
+
"Array",
|
|
809
|
+
"Object",
|
|
810
|
+
"Date",
|
|
811
|
+
"RegExp",
|
|
812
|
+
"undefined",
|
|
813
|
+
"NaN",
|
|
814
|
+
"Infinity",
|
|
815
|
+
"isNaN",
|
|
816
|
+
"isFinite",
|
|
817
|
+
"parseInt",
|
|
818
|
+
"parseFloat"
|
|
819
|
+
]);
|
|
820
|
+
/**
|
|
821
|
+
* 从表达式源码中提取所有使用的变量名
|
|
822
|
+
* 通过 AST 解析实现精确提取
|
|
823
|
+
* 排除允许的全局对象
|
|
824
|
+
*
|
|
825
|
+
* @param source - 表达式源码字符串
|
|
826
|
+
* @returns 使用的变量名列表(去重,不含全局对象)
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* ```ts
|
|
830
|
+
* extractVariableNames("x + y * Math.PI")
|
|
831
|
+
* // => ["x", "y"] // Math 被排除
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
function extractVariableNames(source) {
|
|
835
|
+
const identifiers = collectIdentifiers(parse(source));
|
|
836
|
+
return Array.from(identifiers).filter((name) => !ALLOWED_GLOBALS.has(name));
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region src/evaluate.ts
|
|
841
|
+
/**
|
|
842
|
+
* 缓存已构造的求值函数,以提升重复执行性能
|
|
843
|
+
*/
|
|
844
|
+
const evaluatorCache = /* @__PURE__ */ new Map();
|
|
845
|
+
/**
|
|
846
|
+
* 执行编译后的表达式
|
|
847
|
+
*
|
|
848
|
+
* @template TResult - 表达式结果类型
|
|
849
|
+
* @param data - 编译后的数据结构 [变量名列表, 表达式1, 表达式2, ...]
|
|
850
|
+
* @param values - 变量值映射,按变量名提供值
|
|
851
|
+
* @returns 最后一个表达式的求值结果
|
|
852
|
+
*
|
|
853
|
+
* @throws 如果运行时类型验证失败或表达式执行出错
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* ```ts
|
|
857
|
+
* const compiled = [["x", "y"], "$0+$1", "$1*2"]
|
|
858
|
+
* const result = evaluate<number>(compiled, { x: 2, y: 3 })
|
|
859
|
+
* // => 6 (3 * 2)
|
|
860
|
+
* ```
|
|
861
|
+
*/
|
|
862
|
+
function evaluate(data, values) {
|
|
863
|
+
if (data.length < 1) throw new Error("Invalid compiled data: must have at least variable names");
|
|
864
|
+
const [variableNames, ...expressions] = data;
|
|
865
|
+
if (!Array.isArray(variableNames)) throw new Error("Invalid compiled data: first element must be variable names array");
|
|
866
|
+
for (const varName of variableNames) {
|
|
867
|
+
if (typeof varName !== "string") throw new Error("Invalid compiled data: variable names must be strings");
|
|
868
|
+
if (!(varName in values)) throw new Error(`Missing required variable: ${varName}`);
|
|
869
|
+
}
|
|
870
|
+
const valueArray = [];
|
|
871
|
+
for (const varName of variableNames) valueArray.push(values[varName]);
|
|
872
|
+
const cacheKey = JSON.stringify(data);
|
|
873
|
+
let evaluator = evaluatorCache.get(cacheKey);
|
|
874
|
+
if (!evaluator) {
|
|
875
|
+
const functionBody = buildEvaluatorFunctionBody(expressions, variableNames.length);
|
|
876
|
+
evaluator = new Function("$values", functionBody);
|
|
877
|
+
evaluatorCache.set(cacheKey, evaluator);
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
return evaluator(valueArray);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
throw new Error(`Failed to evaluate expression: ${error instanceof Error ? error.message : String(error)}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* 构造求值函数体
|
|
887
|
+
*
|
|
888
|
+
* @param expressions - 表达式列表
|
|
889
|
+
* @param variableCount - 变量数量
|
|
890
|
+
* @returns 函数体字符串
|
|
891
|
+
*
|
|
892
|
+
* @example
|
|
893
|
+
* ```ts
|
|
894
|
+
* buildEvaluatorFunctionBody(["$0+$1", "$2*2"], 2)
|
|
895
|
+
* // 返回执行 $0+$1 并存储到 $values[2],然后执行 $2*2 的函数体
|
|
896
|
+
* ```
|
|
897
|
+
*/
|
|
898
|
+
function buildEvaluatorFunctionBody(expressions, variableCount) {
|
|
899
|
+
if (expressions.length === 0) throw new Error("No expressions to evaluate");
|
|
900
|
+
const lines = [];
|
|
901
|
+
lines.push("const $0 = $values[0];");
|
|
902
|
+
for (let i = 1; i < variableCount; i++) lines.push(`const $${i} = $values[${i}];`);
|
|
903
|
+
for (let i = 0; i < expressions.length; i++) {
|
|
904
|
+
const exprSource = expressions[i];
|
|
905
|
+
const resultIndex = variableCount + i;
|
|
906
|
+
lines.push(`const $${resultIndex} = ${exprSource};`);
|
|
907
|
+
lines.push(`$values[${resultIndex}] = $${resultIndex};`);
|
|
908
|
+
}
|
|
909
|
+
lines.push(`return $values[$values.length - 1];`);
|
|
910
|
+
return lines.join("\n");
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/expr.ts
|
|
915
|
+
/**
|
|
916
|
+
* 创建一个表达式,支持编译时类型检查和返回类型自动推导
|
|
917
|
+
*
|
|
918
|
+
* @template TContext - 表达式上下文类型(Variable 或 Expression 的映射)
|
|
919
|
+
* @param context - 包含 Variable 或 Expression 的上下文对象
|
|
920
|
+
* @returns 返回一个函数,该函数接收表达式源码字符串并返回 Expression 对象
|
|
921
|
+
*
|
|
922
|
+
* 类型系统会:
|
|
923
|
+
* 1. 验证表达式中使用的所有标识符都在 context 中定义
|
|
924
|
+
* 2. 根据表达式和操作数类型自动推导返回类型
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* ```ts
|
|
928
|
+
* const x = variable(z.number())
|
|
929
|
+
* const y = variable(z.number())
|
|
930
|
+
*
|
|
931
|
+
* // 自动推导返回类型为 number
|
|
932
|
+
* const sum = expr({ x, y })("x + y")
|
|
933
|
+
*
|
|
934
|
+
* // 自动推导返回类型为 boolean
|
|
935
|
+
* const isPositive = expr({ sum })("sum > 0")
|
|
936
|
+
*
|
|
937
|
+
* // 编译错误:z 未在 context 中定义
|
|
938
|
+
* // const invalid = expr({ x, y })("x + z")
|
|
939
|
+
* ```
|
|
940
|
+
*/
|
|
941
|
+
function expr(context) {
|
|
942
|
+
return (source) => {
|
|
943
|
+
return {
|
|
944
|
+
_tag: "expression",
|
|
945
|
+
context,
|
|
946
|
+
source,
|
|
947
|
+
_type: void 0
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
//#endregion
|
|
953
|
+
//#region src/variable.ts
|
|
954
|
+
/**
|
|
955
|
+
* 创建一个类型化变量
|
|
956
|
+
*
|
|
957
|
+
* @template T - Zod schema 类型
|
|
958
|
+
* @param schema - Zod schema 对象,定义变量的类型和验证规则
|
|
959
|
+
* @returns 返回 Variable 对象,包含 _tag 标记和 schema
|
|
960
|
+
*
|
|
961
|
+
* @example
|
|
962
|
+
* ```ts
|
|
963
|
+
* const x = variable(z.number())
|
|
964
|
+
* const name = variable(z.string())
|
|
965
|
+
* const config = variable(z.object({
|
|
966
|
+
* count: z.number(),
|
|
967
|
+
* enabled: z.boolean()
|
|
968
|
+
* }))
|
|
969
|
+
* ```
|
|
970
|
+
*/
|
|
971
|
+
function variable(schema) {
|
|
972
|
+
return {
|
|
973
|
+
_tag: "variable",
|
|
974
|
+
schema,
|
|
975
|
+
_type: void 0
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
//#endregion
|
|
980
|
+
export { compile, evaluate, expr, variable };
|
|
981
|
+
//# sourceMappingURL=index.mjs.map
|