@nudojs/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/chunk-N7E2A7IK.js +7216 -0
  2. package/dist/exec.d.ts +2 -0
  3. package/dist/exec.js +174 -0
  4. package/dist/index-DgyudAkw.d.ts +911 -0
  5. package/dist/index.d.ts +64 -710
  6. package/dist/index.js +3269 -8426
  7. package/package.json +18 -5
  8. package/CHANGELOG.md +0 -34
  9. package/src/__tests__/environment.test.ts +0 -48
  10. package/src/__tests__/fn-sig-impl.test.ts +0 -57
  11. package/src/__tests__/ops-refined.test.ts +0 -196
  12. package/src/__tests__/ops.test.ts +0 -84
  13. package/src/__tests__/type-value-phase2.test.ts +0 -117
  14. package/src/__tests__/type-value-phase3.test.ts +0 -94
  15. package/src/__tests__/type-value-refined.test.ts +0 -210
  16. package/src/__tests__/type-value.test.ts +0 -319
  17. package/src/algebra/__tests__/abs-native.test.ts +0 -176
  18. package/src/algebra/__tests__/arithmetic.test.ts +0 -170
  19. package/src/algebra/__tests__/ast-eval.test.ts +0 -154
  20. package/src/algebra/__tests__/bridge.test.ts +0 -160
  21. package/src/algebra/__tests__/call-budget.test.ts +0 -117
  22. package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
  23. package/src/algebra/__tests__/check-gold.test.ts +0 -290
  24. package/src/algebra/__tests__/check-json.test.ts +0 -57
  25. package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
  26. package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
  27. package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
  28. package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
  29. package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
  30. package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
  31. package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
  32. package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
  33. package/src/algebra/__tests__/check.test.ts +0 -69
  34. package/src/algebra/__tests__/denote.test.ts +0 -77
  35. package/src/algebra/__tests__/diagnostics.test.ts +0 -64
  36. package/src/algebra/__tests__/differential.test.ts +0 -124
  37. package/src/algebra/__tests__/div-mod.test.ts +0 -38
  38. package/src/algebra/__tests__/example0.test.ts +0 -103
  39. package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
  40. package/src/algebra/__tests__/exec-async.test.ts +0 -46
  41. package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
  42. package/src/algebra/__tests__/exec-class.test.ts +0 -56
  43. package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
  44. package/src/algebra/__tests__/exec-generator.test.ts +0 -53
  45. package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
  46. package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
  47. package/src/algebra/__tests__/exec-rest.test.ts +0 -55
  48. package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
  49. package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
  50. package/src/algebra/__tests__/exec-switch.test.ts +0 -74
  51. package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
  52. package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
  53. package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
  54. package/src/algebra/__tests__/format.test.ts +0 -34
  55. package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
  56. package/src/algebra/__tests__/generalize.test.ts +0 -113
  57. package/src/algebra/__tests__/hash-source.test.ts +0 -19
  58. package/src/algebra/__tests__/hof.test.ts +0 -80
  59. package/src/algebra/__tests__/inlay.test.ts +0 -30
  60. package/src/algebra/__tests__/language.test.ts +0 -77
  61. package/src/algebra/__tests__/leq.test.ts +0 -109
  62. package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
  63. package/src/algebra/__tests__/methods.test.ts +0 -37
  64. package/src/algebra/__tests__/node-types.test.ts +0 -26
  65. package/src/algebra/__tests__/nudo-constraints.ts +0 -46
  66. package/src/algebra/__tests__/objects.test.ts +0 -117
  67. package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
  68. package/src/algebra/__tests__/refine-import.test.ts +0 -109
  69. package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
  70. package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
  71. package/src/algebra/__tests__/surface.test.ts +0 -41
  72. package/src/algebra/__tests__/this-inherit.test.ts +0 -130
  73. package/src/algebra/abs-fn.ts +0 -49
  74. package/src/algebra/abs-modules.ts +0 -139
  75. package/src/algebra/abs.ts +0 -206
  76. package/src/algebra/arithmetic.ts +0 -691
  77. package/src/algebra/ast-eval.ts +0 -1763
  78. package/src/algebra/bridge.ts +0 -350
  79. package/src/algebra/builtins.ts +0 -373
  80. package/src/algebra/check-report.ts +0 -150
  81. package/src/algebra/check.ts +0 -1867
  82. package/src/algebra/constraint.ts +0 -304
  83. package/src/algebra/denote.ts +0 -166
  84. package/src/algebra/diagnostics.ts +0 -210
  85. package/src/algebra/exec/call.ts +0 -29
  86. package/src/algebra/exec/calls.ts +0 -96
  87. package/src/algebra/exec/class-registry.ts +0 -29
  88. package/src/algebra/exec/class.ts +0 -329
  89. package/src/algebra/exec/index.ts +0 -6
  90. package/src/algebra/exec/member-diag.ts +0 -141
  91. package/src/algebra/exec/run.ts +0 -341
  92. package/src/algebra/exec/runtime.ts +0 -583
  93. package/src/algebra/exec/transpile.ts +0 -993
  94. package/src/algebra/fn-fp.ts +0 -294
  95. package/src/algebra/format.ts +0 -91
  96. package/src/algebra/generalize.ts +0 -776
  97. package/src/algebra/hash-source.ts +0 -27
  98. package/src/algebra/index.ts +0 -34
  99. package/src/algebra/inlay.ts +0 -198
  100. package/src/algebra/language.ts +0 -289
  101. package/src/algebra/leak.ts +0 -68
  102. package/src/algebra/leq.ts +0 -301
  103. package/src/algebra/load-deps-fp.ts +0 -97
  104. package/src/algebra/methods.ts +0 -128
  105. package/src/algebra/objects.ts +0 -251
  106. package/src/algebra/parse-source.ts +0 -64
  107. package/src/algebra/phi.ts +0 -42
  108. package/src/algebra/pred.ts +0 -395
  109. package/src/algebra/refine.ts +0 -227
  110. package/src/algebra/stable-source-key.ts +0 -81
  111. package/src/algebra/surface.ts +0 -206
  112. package/src/algebra/template.ts +0 -143
  113. package/src/algebra/term.ts +0 -119
  114. package/src/environment.ts +0 -74
  115. package/src/index.ts +0 -73
  116. package/src/mock-helpers.ts +0 -166
  117. package/src/ops.ts +0 -191
  118. package/src/refinements/range.ts +0 -86
  119. package/src/refinements/template-predicates.ts +0 -11
  120. package/src/refinements/template.ts +0 -176
  121. package/src/strip-types.ts +0 -236
  122. package/src/type-value.ts +0 -513
  123. package/tsconfig.json +0 -8
@@ -1,395 +0,0 @@
1
- /** 约束(Pred):附着在项上的事实,可传播。 */
2
-
3
- import type { Term } from "./term.ts";
4
- import { termEquals, termToString, lit } from "./term.ts";
5
-
6
- export type PrimName = "number" | "string" | "boolean" | "bigint" | "symbol";
7
-
8
- export type Pred =
9
- | { op: "true" }
10
- | { op: "false" }
11
- | { op: "eq"; a: Term; b: Term }
12
- | { op: "ne"; a: Term; b: Term }
13
- | { op: "lt"; a: Term; b: Term }
14
- | { op: "le"; a: Term; b: Term }
15
- | { op: "gt"; a: Term; b: Term }
16
- | { op: "ge"; a: Term; b: Term }
17
- | { op: "and"; args: Pred[] }
18
- | { op: "or"; args: Pred[] }
19
- | { op: "not"; arg: Pred }
20
- | { op: "typeof"; t: Term; type: PrimName };
21
-
22
- export const pTrue: Pred = { op: "true" };
23
- export const pFalse: Pred = { op: "false" };
24
-
25
- export const eq = (a: Term, b: Term): Pred => ({ op: "eq", a, b });
26
- export const ne = (a: Term, b: Term): Pred => ({ op: "ne", a, b });
27
- export const lt = (a: Term, b: Term): Pred => ({ op: "lt", a, b });
28
- export const le = (a: Term, b: Term): Pred => ({ op: "le", a, b });
29
- export const gt = (a: Term, b: Term): Pred => ({ op: "gt", a, b });
30
- export const ge = (a: Term, b: Term): Pred => ({ op: "ge", a, b });
31
- export const ptypeof = (t: Term, type: PrimName): Pred => ({
32
- op: "typeof",
33
- t,
34
- type,
35
- });
36
-
37
- export function and(...preds: Pred[]): Pred {
38
- const flat: Pred[] = [];
39
- for (const p of preds) {
40
- if (p.op === "true") continue;
41
- if (p.op === "false") return pFalse;
42
- if (p.op === "and") flat.push(...p.args);
43
- else flat.push(p);
44
- }
45
- if (flat.length === 0) return pTrue;
46
- if (flat.length === 1) return flat[0]!;
47
- return { op: "and", args: flat };
48
- }
49
-
50
- export function or(...preds: Pred[]): Pred {
51
- const flat: Pred[] = [];
52
- for (const p of preds) {
53
- if (p.op === "false") continue;
54
- if (p.op === "true") return pTrue;
55
- if (p.op === "or") flat.push(...p.args);
56
- else flat.push(p);
57
- }
58
- if (flat.length === 0) return pFalse;
59
- if (flat.length === 1) return flat[0]!;
60
- return { op: "or", args: flat };
61
- }
62
-
63
- export function not(p: Pred): Pred {
64
- if (p.op === "true") return pFalse;
65
- if (p.op === "false") return pTrue;
66
- if (p.op === "not") return p.arg;
67
- return { op: "not", arg: p };
68
- }
69
-
70
- export function predEquals(a: Pred, b: Pred): boolean {
71
- if (a.op !== b.op) return false;
72
- switch (a.op) {
73
- case "true":
74
- case "false":
75
- return true;
76
- case "eq":
77
- case "ne":
78
- case "lt":
79
- case "le":
80
- case "gt":
81
- case "ge":
82
- return (
83
- b.op === a.op &&
84
- termEquals(a.a, (b as typeof a).a) &&
85
- termEquals(a.b, (b as typeof a).b)
86
- );
87
- case "and":
88
- case "or":
89
- return (
90
- (b.op === "and" || b.op === "or") &&
91
- a.args.length === (b as typeof a).args.length &&
92
- a.args.every((x, i) => predEquals(x, (b as typeof a).args[i]!))
93
- );
94
- case "not":
95
- return b.op === "not" && predEquals(a.arg, b.arg);
96
- case "typeof":
97
- return (
98
- b.op === "typeof" &&
99
- a.type === b.type &&
100
- termEquals(a.t, b.t)
101
- );
102
- }
103
- }
104
-
105
- export function predToString(p: Pred): string {
106
- switch (p.op) {
107
- case "true":
108
- return "true";
109
- case "false":
110
- return "false";
111
- case "eq":
112
- return `${termToString(p.a)} = ${termToString(p.b)}`;
113
- case "ne":
114
- return `${termToString(p.a)} ≠ ${termToString(p.b)}`;
115
- case "lt":
116
- return `${termToString(p.a)} < ${termToString(p.b)}`;
117
- case "le":
118
- return `${termToString(p.a)} ≤ ${termToString(p.b)}`;
119
- case "gt":
120
- return `${termToString(p.a)} > ${termToString(p.b)}`;
121
- case "ge":
122
- return `${termToString(p.a)} ≥ ${termToString(p.b)}`;
123
- case "and":
124
- return p.args.map(predToString).join(" ∧ ");
125
- case "or":
126
- return `(${p.args.map(predToString).join(" ∨ ")})`;
127
- case "not":
128
- return `¬(${predToString(p.arg)})`;
129
- case "typeof":
130
- return `typeof ${termToString(p.t)} = "${p.type}"`;
131
- }
132
- }
133
-
134
- /** 把 pred 里的 Term 用 subst 替换(用于 bound 变量重命名等) */
135
- export function substPred(p: Pred, subst: (t: Term) => Term): Pred {
136
- switch (p.op) {
137
- case "true":
138
- case "false":
139
- return p;
140
- case "eq":
141
- case "ne":
142
- case "lt":
143
- case "le":
144
- case "gt":
145
- case "ge":
146
- return { ...p, a: subst(p.a), b: subst(p.b) } as Pred;
147
- case "and":
148
- case "or":
149
- return { ...p, args: p.args.map((x) => substPred(x, subst)) };
150
- case "not":
151
- return { op: "not", arg: substPred(p.arg, subst) };
152
- case "typeof":
153
- return { op: "typeof", t: subst(p.t), type: p.type };
154
- }
155
- }
156
-
157
- /** 收集 pred 中出现的自由变量 */
158
- export function predVars(p: Pred): Set<string> {
159
- const acc = new Set<string>();
160
- const walkTerm = (t: Term): void => {
161
- if (t.op === "var") acc.add(t.id);
162
- if (t.op === "app") t.args.forEach(walkTerm);
163
- };
164
- const walk = (p: Pred): void => {
165
- switch (p.op) {
166
- case "true":
167
- case "false":
168
- return;
169
- case "eq":
170
- case "ne":
171
- case "lt":
172
- case "le":
173
- case "gt":
174
- case "ge":
175
- walkTerm(p.a);
176
- walkTerm(p.b);
177
- return;
178
- case "and":
179
- case "or":
180
- p.args.forEach(walk);
181
- return;
182
- case "not":
183
- walk(p.arg);
184
- return;
185
- case "typeof":
186
- walkTerm(p.t);
187
- return;
188
- }
189
- };
190
- walk(p);
191
- return acc;
192
- }
193
-
194
- /** 约束环境 Φ:Pred 的合取 */
195
- export type Phi = Pred;
196
-
197
- export const emptyPhi: Phi = pTrue;
198
- export const phiAnd = and;
199
-
200
- /** 简单蕴含:在区间/字面量可判定范围内判断 Φ ⊢ pred */
201
- export function implies(phi: Phi, pred: Pred): boolean {
202
- if (pred.op === "true") return true;
203
- if (pred.op === "false") return false;
204
- if (phi.op === "true") {
205
- // 无约束时,纯字面量比较可判定
206
- return decideLiteralPred(pred) === true;
207
- }
208
- if (predEquals(phi, pred)) return true;
209
- if (phi.op === "and" && phi.args.some((c) => predEquals(c, pred))) return true;
210
-
211
- // 尝试从 Φ 提取同一 term 的界,做区间蕴含
212
- const bounds = extractBounds(phi);
213
- const implied = impliesViaBounds(pred, bounds);
214
- if (implied !== undefined) return implied;
215
-
216
- // 字面量可判定
217
- const litAns = decideLiteralPred(pred);
218
- if (litAns !== undefined) return litAns;
219
- return false;
220
- }
221
-
222
- type Bounds = {
223
- /** term key → { lo 独占? hi 独占? } */
224
- lo: Map<string, { bound: number; strict: boolean }>;
225
- hi: Map<string, { bound: number; strict: boolean }>;
226
- };
227
-
228
- function termKey(t: Term): string {
229
- return termToString(t);
230
- }
231
-
232
- function extractBounds(phi: Phi): Bounds {
233
- const lo = new Map<string, { bound: number; strict: boolean }>();
234
- const hi = new Map<string, { bound: number; strict: boolean }>();
235
- const conjs: Pred[] = phi.op === "and" ? phi.args : [phi];
236
- for (const c of conjs) {
237
- if (c.op === "gt" || c.op === "ge") {
238
- // a > b or a ≥ b,且 b 是字面量
239
- if (c.b.op === "lit" && typeof c.b.value === "number") {
240
- const k = termKey(c.a);
241
- const cur = lo.get(k);
242
- const next = { bound: c.b.value, strict: c.op === "gt" };
243
- if (!cur || next.bound > cur.bound || (next.bound === cur.bound && next.strict)) {
244
- lo.set(k, next);
245
- }
246
- }
247
- // 反向:字面量 > term ⇒ term < 字面量
248
- if (c.a.op === "lit" && typeof c.a.value === "number") {
249
- const k = termKey(c.b);
250
- const cur = hi.get(k);
251
- const next = { bound: c.a.value, strict: c.op === "gt" };
252
- if (!cur || next.bound < cur.bound || (next.bound === cur.bound && next.strict)) {
253
- hi.set(k, next);
254
- }
255
- }
256
- }
257
- if (c.op === "lt" || c.op === "le") {
258
- if (c.b.op === "lit" && typeof c.b.value === "number") {
259
- const k = termKey(c.a);
260
- const cur = hi.get(k);
261
- const next = { bound: c.b.value, strict: c.op === "lt" };
262
- if (!cur || next.bound < cur.bound || (next.bound === cur.bound && next.strict)) {
263
- hi.set(k, next);
264
- }
265
- }
266
- if (c.a.op === "lit" && typeof c.a.value === "number") {
267
- const k = termKey(c.b);
268
- const cur = lo.get(k);
269
- const next = { bound: c.a.value, strict: c.op === "lt" };
270
- if (!cur || next.bound > cur.bound || (next.bound === cur.bound && next.strict)) {
271
- lo.set(k, next);
272
- }
273
- }
274
- }
275
- if (c.op === "eq" && c.a.op === "var" && c.b.op === "lit" && typeof c.b.value === "number") {
276
- lo.set(c.a.id, { bound: c.b.value, strict: false });
277
- hi.set(c.a.id, { bound: c.b.value, strict: false });
278
- }
279
- }
280
- return { lo, hi };
281
- }
282
-
283
- /**
284
- * 用区间界蕴含原子比较。
285
- * 支持:term > n / ≥ n / < n / ≤ n,以及 term+lit 形式的简单平移。
286
- * 返回 undefined 表示无法判定。
287
- */
288
- function impliesViaBounds(pred: Pred, bounds: Bounds): boolean | undefined {
289
- if (
290
- pred.op !== "gt" &&
291
- pred.op !== "ge" &&
292
- pred.op !== "lt" &&
293
- pred.op !== "le"
294
- ) {
295
- return undefined;
296
- }
297
- // 归一:只处理 term ⊕ lit 比较 lit 的情况
298
- const left = pred.a;
299
- const right = pred.b;
300
- if (right.op !== "lit" || typeof right.value !== "number") return undefined;
301
- const n = right.value;
302
-
303
- // 直接 term
304
- if (left.op === "var") {
305
- return cmpVar(left.id, pred.op, n, bounds);
306
- }
307
- // term = x + k
308
- if (left.op === "app" && left.fn === "+" && left.args.length === 2) {
309
- const [a, b] = left.args as [Term, Term];
310
- if (a.op === "var" && b.op === "lit" && typeof b.value === "number") {
311
- // x + k op n ⟺ x op (n - k)
312
- return cmpVar(a.id, pred.op, n - b.value, bounds);
313
- }
314
- if (b.op === "var" && a.op === "lit" && typeof a.value === "number") {
315
- return cmpVar(b.id, pred.op, n - a.value, bounds);
316
- }
317
- }
318
- // 纯字面量已在外层处理
319
- return undefined;
320
- }
321
-
322
- function cmpVar(
323
- id: string,
324
- op: "gt" | "ge" | "lt" | "le",
325
- n: number,
326
- bounds: Bounds,
327
- ): boolean | undefined {
328
- const lo = bounds.lo.get(id);
329
- const hi = bounds.hi.get(id);
330
- // x > n:需要 lo ≥ n(若 strict lo 则 lo ≥ n;若 lo = n 且 non-strict 则 x≥n 不能推出 x>n)
331
- if (op === "gt") {
332
- if (!lo) return undefined;
333
- if (lo.strict) return lo.bound >= n;
334
- return lo.bound > n;
335
- }
336
- if (op === "ge") {
337
- if (!lo) return undefined;
338
- return lo.bound >= n;
339
- }
340
- if (op === "lt") {
341
- if (!hi) return undefined;
342
- if (hi.strict) return hi.bound <= n;
343
- return hi.bound < n;
344
- }
345
- if (op === "le") {
346
- if (!hi) return undefined;
347
- return hi.bound <= n;
348
- }
349
- return undefined;
350
- }
351
-
352
- function decideLiteralPred(pred: Pred): boolean | undefined {
353
- if (
354
- pred.op === "eq" ||
355
- pred.op === "ne" ||
356
- pred.op === "lt" ||
357
- pred.op === "le" ||
358
- pred.op === "gt" ||
359
- pred.op === "ge"
360
- ) {
361
- if (pred.a.op === "lit" && pred.b.op === "lit") {
362
- const a = pred.a.value;
363
- const b = pred.b.value;
364
- switch (pred.op) {
365
- case "eq":
366
- return a === b;
367
- case "ne":
368
- return a !== b;
369
- case "lt":
370
- return typeof a === "number" && typeof b === "number" && a < b;
371
- case "le":
372
- return typeof a === "number" && typeof b === "number" && a <= b;
373
- case "gt":
374
- return typeof a === "number" && typeof b === "number" && a > b;
375
- case "ge":
376
- return typeof a === "number" && typeof b === "number" && a >= b;
377
- }
378
- }
379
- }
380
- return undefined;
381
- }
382
-
383
- /** 便捷:数字下界 */
384
- export function gtNum(term: Term, n: number): Pred {
385
- return gt(term, lit(n));
386
- }
387
- export function geNum(term: Term, n: number): Pred {
388
- return ge(term, lit(n));
389
- }
390
- export function ltNum(term: Term, n: number): Pred {
391
- return lt(term, lit(n));
392
- }
393
- export function leNum(term: Term, n: number): Pred {
394
- return le(term, lit(n));
395
- }
@@ -1,227 +0,0 @@
1
- /**
2
- * 精化契约解析:唯一形态
3
- *
4
- * @nudo:refine <param> <constraint> 参数精化(挂入口 Abs,参与运算)
5
- * @nudo:refine return <constraint> 返回精化(推断返回值 ⊭ 时红)
6
- *
7
- * 不叫 requires:那只是「校验挡板」。
8
- * refine 表示约束是类型的一部分——Abs = shape × term × **pred** × conf,
9
- * pred 会流入代数(x>0 ⇒ x+1>1),不只是调用点挡一下。
10
- *
11
- * constraint 来自 *.nudo.js 导出的模板(number().gt(0) 等),
12
- * 由 /// @nudo:import { delay } from "./delay.nudo.js" 引入。
13
- * 不支持写 `x > 0`(绑死参数名)。
14
- * 不用 JSDoc @param/@return:那是类型注解语法。
15
- */
16
-
17
- import type { Pred } from "./pred.ts";
18
- import { v as termVar } from "./term.ts";
19
- import { parseSource } from "./parse-source.ts";
20
- import {
21
- type NudoConstraint,
22
- isNudoConstraint,
23
- instantiateConstraint,
24
- number,
25
- string as stringC,
26
- boolean as booleanC,
27
- shape,
28
- array,
29
- } from "./constraint.ts";
30
-
31
- /** `/// @nudo:import { delay, percent } from "./delay.nudo.js"` */
32
- export type NamedImport = { names: string[]; spec: string };
33
-
34
- export function extractNudoImports(source: string): NamedImport[] {
35
- const out: NamedImport[] = [];
36
- // named: import { a, b as c } from "..."
37
- const named = /@nudo:import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
38
- let m: RegExpExecArray | null;
39
- while ((m = named.exec(source))) {
40
- const names = m[1]!
41
- .split(",")
42
- .map((s) => s.trim())
43
- .filter(Boolean)
44
- .map((s) => s.split(/\s+as\s+/).pop()!.trim());
45
- out.push({ names, spec: m[2]! });
46
- }
47
- // 兼容 namespace(仍支持)
48
- const ns = /@nudo:import\s+\*\s+as\s+(\w+)\s+from\s*["']([^"']+)["']/g;
49
- while ((m = ns.exec(source))) {
50
- out.push({ names: [`*${m[1]}`], spec: m[2]! });
51
- }
52
- return out;
53
- }
54
-
55
- /**
56
- * 执行 *.nudo.js(真实 JS + 我们的构建器)。
57
- * 把 export const 改写成 const,末尾 return { names }。
58
- */
59
- export function execNudoModule(src: string): Record<string, unknown> {
60
- const names = [...src.matchAll(/export\s+const\s+(\w+)/g)].map((m) => m[1]!);
61
- let body = src.replace(/export\s+const\b/g, "const");
62
- // 去掉 import { number } from "..." —— 用注入参数
63
- body = body.replace(/^\s*import\s*\{[^}]*\}\s*from\s*["'][^"']+["'];?\s*$/gm, "");
64
- body = body.replace(/^\s*import\s+\*\s+as\s+\w+\s+from\s*["'][^"']+["'];?\s*$/gm, "");
65
- body += `\nreturn { ${names.join(", ")} };`;
66
- const fn = new Function("number", "string", "boolean", "shape", "array", body);
67
- return fn(number, stringC, booleanC, shape, array) as Record<string, unknown>;
68
- }
69
-
70
- export type RefineResolveOpts = {
71
- loadModule?: (spec: string, fromFile: string) => string | undefined;
72
- fromFile?: string;
73
- };
74
-
75
- /** *.nudo.js 构建器执行结果(同内容只 new Function 一次) */
76
- const nudoModuleExecCache = new Map<string, Record<string, unknown>>();
77
- const MAX_NUDO_MODULE_EXEC = 64;
78
-
79
- function execNudoModuleCached(src: string): Record<string, unknown> {
80
- const hit = nudoModuleExecCache.get(src);
81
- if (hit !== undefined) {
82
- nudoModuleExecCache.delete(src);
83
- nudoModuleExecCache.set(src, hit);
84
- return hit;
85
- }
86
- const out = execNudoModule(src);
87
- if (nudoModuleExecCache.size >= MAX_NUDO_MODULE_EXEC) {
88
- const oldest = nudoModuleExecCache.keys().next().value;
89
- if (oldest !== undefined) nudoModuleExecCache.delete(oldest);
90
- }
91
- nudoModuleExecCache.set(src, out);
92
- return out;
93
- }
94
-
95
- export function resetNudoModuleExecCache(): void {
96
- nudoModuleExecCache.clear();
97
- }
98
-
99
- /** 从导入收集 name → NudoConstraint */
100
- function collectConstraints(
101
- source: string,
102
- opts: RefineResolveOpts,
103
- ): Map<string, NudoConstraint> {
104
- const map = new Map<string, NudoConstraint>();
105
- const imports = extractNudoImports(source);
106
- for (const imp of imports) {
107
- if (!opts.loadModule || !opts.fromFile) continue;
108
- const src = opts.loadModule(imp.spec, opts.fromFile);
109
- if (!src) continue;
110
- let exports: Record<string, unknown>;
111
- try {
112
- exports = execNudoModuleCached(src);
113
- } catch {
114
- continue;
115
- }
116
- for (const name of imp.names) {
117
- if (name.startsWith("*")) continue; // namespace 暂不展开
118
- const v = exports[name];
119
- if (isNudoConstraint(v)) map.set(name, v);
120
- }
121
- }
122
- return map;
123
- }
124
-
125
- /** 从源码抽函数上的 @nudo:refine 行 */
126
- function extractRefineLines(source: string, fnName: string): string[] {
127
- const fnRe = new RegExp(
128
- `(?:export\\s+default\\s+)?(?:function\\s+${fnName}\\b|const\\s+${fnName}\\s*=)`,
129
- );
130
- const m = source.match(fnRe);
131
- if (!m || m.index === undefined) return [];
132
- const before = source.slice(0, m.index);
133
- const lines = before.split("\n");
134
- const reqs: string[] = [];
135
- for (let i = lines.length - 1; i >= 0; i--) {
136
- const line = lines[i]!.trim();
137
- if (line === "" || line === "*/") continue;
138
- if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
139
- const rm = line.match(/@nudo:refine\s+(.+)$/);
140
- if (rm) reqs.unshift(rm[1]!.trim().replace(/\*\/$/, "").trim());
141
- continue;
142
- }
143
- break;
144
- }
145
- return reqs;
146
- }
147
-
148
- /**
149
- * 解析 `ms delay` / `n percent` → [param, Pred]
150
- * 多条用 && 或换行连接。
151
- * 同时保留原始 NudoConstraint(shape 字段检查用)。
152
- */
153
- export type RefineEntry = {
154
- param: string;
155
- pred: Pred;
156
- constraint: NudoConstraint;
157
- };
158
-
159
- export function extractRefinesFromSource(
160
- source: string,
161
- fnName: string,
162
- opts: RefineResolveOpts = {},
163
- ): RefineEntry[] {
164
- // 快路径:整文件无 @nudo:refine 时免 regex 扫全文(after-edit 批量 check)
165
- if (!source.includes("@nudo:refine")) return [];
166
- const constraints = collectConstraints(source, opts);
167
- const out: RefineEntry[] = [];
168
- for (const line of extractRefineLines(source, fnName)) {
169
- const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
170
- for (const part of parts) {
171
- const m = part.match(/^(\w+)\s+(\w+)$/);
172
- if (!m) continue;
173
- const [, param, cName] = m;
174
- // return 是后置目标,不进参数精化
175
- if (param === "return") continue;
176
- const c = constraints.get(cName!);
177
- if (!c) continue;
178
- out.push({
179
- param: param!,
180
- pred: instantiateConstraint(c, param!),
181
- constraint: c,
182
- });
183
- }
184
- }
185
- return out;
186
- }
187
-
188
- /** refine 参数 → 带约束模板的下标表(shape 检查用) */
189
- export function refineToIndexedFull(
190
- source: string,
191
- fnName: string,
192
- paramNames: string[],
193
- opts: RefineResolveOpts = {},
194
- ): Array<[number, RefineEntry]> {
195
- const raw = extractRefinesFromSource(source, fnName, opts);
196
- const out: Array<[number, RefineEntry]> = [];
197
- for (const item of raw) {
198
- const idx = paramNames.indexOf(item.param);
199
- if (idx >= 0) out.push([idx, item]);
200
- }
201
- return out;
202
- }
203
-
204
- /**
205
- * 解析 `@nudo:refine return positive` → 返回精化。
206
- * 返回 undefined = 无声明(不猜后置)。
207
- */
208
- export function extractRefineReturnFromSource(
209
- source: string,
210
- fnName: string,
211
- opts: RefineResolveOpts = {},
212
- ): { name: string; constraint: NudoConstraint } | undefined {
213
- if (!source.includes("@nudo:refine")) return undefined;
214
- const constraints = collectConstraints(source, opts);
215
- for (const line of extractRefineLines(source, fnName)) {
216
- const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
217
- for (const part of parts) {
218
- const m = part.match(/^return\s+(\w+)$/);
219
- if (!m) continue;
220
- const cName = m[1]!;
221
- const c = constraints.get(cName);
222
- if (!c) continue;
223
- return { name: cName, constraint: c };
224
- }
225
- }
226
- return undefined;
227
- }
@@ -1,81 +0,0 @@
1
- /**
2
- * Analysis-stable source key: strip trailing noise that cannot affect types.
3
- * Keeps every line that may carry @nudo directives.
4
- *
5
- * Fast path: already-stable sources return the same string identity (O(1)
6
- * compare in B-path / analysis caches when the caller reuses the buffer).
7
- */
8
- export function stableAnalyzeKeySource(source: string): string {
9
- // Scan back over trailing whitespace/newlines (usually a few chars).
10
- let end = source.length;
11
- while (end > 0) {
12
- const ch = source.charCodeAt(end - 1);
13
- if (ch === 10 || ch === 13 || ch === 32 || ch === 9) {
14
- end--;
15
- continue;
16
- }
17
- break;
18
- }
19
- const lineStart = source.lastIndexOf("\n", end - 1) + 1;
20
- const lastLine = source.slice(lineStart, end);
21
- const trimmedLast = lastLine.trim();
22
- // Last non-empty line is real code → normalize trailing whitespace only,
23
- // then still try stripping a trailing block comment below.
24
- if (trimmedLast !== "" && !trimmedLast.startsWith("//") && !trimmedLast.startsWith("/*")) {
25
- let body = source.slice(0, end);
26
- if (body.endsWith("*/")) {
27
- body = stripTrailingBlockComments(body);
28
- return body + "\n";
29
- }
30
- const after = source.slice(end);
31
- if (after === "" || after === "\n") return source;
32
- return source.slice(0, end) + "\n";
33
- }
34
-
35
- const lines = source.split("\n");
36
- while (lines.length > 0) {
37
- const last = lines[lines.length - 1]!;
38
- if (last.trim() === "") {
39
- lines.pop();
40
- continue;
41
- }
42
- if (last.trimStart().startsWith("//") && !last.includes("@nudo")) {
43
- lines.pop();
44
- continue;
45
- }
46
- // Multi-line trailing block comment (/* ... */) without @nudo.
47
- if (last.trim() === "*/" || last.trimStart().startsWith("/*")) {
48
- const joined = stripTrailingBlockComments(lines.join("\n"));
49
- const stripped = joined.split("\n");
50
- if (stripped.length < lines.length) {
51
- lines.length = 0;
52
- lines.push(...stripped);
53
- continue;
54
- }
55
- }
56
- break;
57
- }
58
- let body = lines.join("\n").replace(/\s+$/, "");
59
- body = stripTrailingBlockComments(body);
60
- return body + "\n";
61
- }
62
-
63
- /**
64
- * Strip trailing `/* ... *\/` comments that sit alone on their line(s) and
65
- * carry no @nudo directive. Heuristic: `/*` must start a line (after indent)
66
- * so we do not cut inside strings. Returns body without a trailing newline.
67
- */
68
- function stripTrailingBlockComments(body: string): string {
69
- let t = body.replace(/\s+$/, "");
70
- while (t.endsWith("*/")) {
71
- const start = t.lastIndexOf("/*");
72
- if (start < 0) break;
73
- const block = t.slice(start);
74
- if (block.includes("@nudo")) break;
75
- const before = t.slice(0, start);
76
- const lineStart = before.lastIndexOf("\n") + 1;
77
- if (before.slice(lineStart).trim() !== "") break;
78
- t = before.replace(/\s+$/, "");
79
- }
80
- return t;
81
- }