@nudojs/cli 0.2.0 → 0.3.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 (40) hide show
  1. package/README.md +34 -0
  2. package/package.json +24 -4
  3. package/src/abs-route.ts +273 -0
  4. package/src/builtins/builtin-intl.ts +17 -0
  5. package/src/builtins/builtin-map.ts +174 -0
  6. package/src/builtins/builtin-promise.ts +116 -0
  7. package/src/builtins/builtin-prototype.ts +279 -0
  8. package/src/builtins/builtin-reflect.ts +30 -0
  9. package/src/builtins/builtin-regexp.ts +34 -0
  10. package/src/builtins/builtin-set.ts +131 -0
  11. package/src/builtins/builtin-static.ts +260 -0
  12. package/src/builtins/builtin-symbol.ts +16 -0
  13. package/src/builtins/builtin-url.ts +58 -0
  14. package/src/builtins/builtin-weak.ts +22 -0
  15. package/src/builtins/builtin-web.ts +111 -0
  16. package/src/config.ts +32 -0
  17. package/src/env-loader.ts +174 -0
  18. package/src/eval-binary.ts +113 -0
  19. package/src/evaluator-api.ts +23 -0
  20. package/src/evaluator.ts +3502 -285
  21. package/src/index.ts +922 -180
  22. package/src/narrowing.ts +237 -15
  23. package/src/phi-from-test.ts +42 -0
  24. package/src/resolve-npm.ts +63 -0
  25. package/src/run-test.ts +92 -0
  26. package/src/term-registry.ts +96 -0
  27. package/CHANGELOG.md +0 -29
  28. package/dist/chunk-LGWVGEMB.js +0 -1817
  29. package/dist/evaluator-api.d.ts +0 -59
  30. package/dist/evaluator-api.js +0 -30
  31. package/dist/index.d.ts +0 -2
  32. package/dist/index.js +0 -3570
  33. package/src/__tests__/evaluator-loops.test.ts +0 -163
  34. package/src/__tests__/evaluator-phase2.test.ts +0 -564
  35. package/src/__tests__/evaluator-phase3.test.ts +0 -513
  36. package/src/__tests__/evaluator-refined.test.ts +0 -121
  37. package/src/__tests__/evaluator-string-methods.test.ts +0 -151
  38. package/src/__tests__/evaluator.test.ts +0 -243
  39. package/src/__tests__/narrowing.test.ts +0 -82
  40. package/tsconfig.json +0 -9
@@ -1,513 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import {
3
- T,
4
- typeValueEquals,
5
- typeValueToString,
6
- createEnvironment,
7
- isSubtypeOf,
8
- } from "@nudojs/core";
9
- import type { TypeValue } from "@nudojs/core";
10
- import { parse } from "@nudojs/parser";
11
- import { evaluate, evaluateFunction, evaluateFunctionFull, evaluateProgram, resetMemo, setModuleResolver, setCurrentFileDir } from "../evaluator.ts";
12
-
13
- function evalCode(code: string): TypeValue {
14
- const ast = parse(code);
15
- const env = createEnvironment();
16
- return evaluateProgram(ast, env);
17
- }
18
-
19
- function evalFn(code: string, args: TypeValue[]): TypeValue {
20
- const ast = parse(code);
21
- const env = createEnvironment();
22
- evaluateProgram(ast, env);
23
- const fns = ast.type === "File" ? ast.program.body : [];
24
- const fnNode = fns.find(
25
- (n: any) => n.type === "FunctionDeclaration",
26
- );
27
- if (!fnNode) throw new Error("No function found");
28
- return evaluateFunction(fnNode, args, env);
29
- }
30
-
31
- function evalFnFull(code: string, args: TypeValue[]): { value: TypeValue; throws: TypeValue } {
32
- const ast = parse(code);
33
- const env = createEnvironment();
34
- evaluateProgram(ast, env);
35
- const fns = ast.type === "File" ? ast.program.body : [];
36
- const fnNode = fns.find(
37
- (n: any) => n.type === "FunctionDeclaration",
38
- );
39
- if (!fnNode) throw new Error("No function found");
40
- return evaluateFunctionFull(fnNode, args, env);
41
- }
42
-
43
- describe("ThrowStatement", () => {
44
- it("throw produces ThrowSignal captured by try-catch", () => {
45
- const result = evalFn(
46
- `function test(x) {
47
- try {
48
- throw new Error("oops");
49
- } catch (e) {
50
- return e;
51
- }
52
- }`,
53
- [T.number],
54
- );
55
- expect(result.kind).toBe("instance");
56
- if (result.kind === "instance") {
57
- expect(result.className).toBe("Error");
58
- }
59
- });
60
-
61
- it("uncaught throw in function returns never", () => {
62
- const { value, throws } = evalFnFull(
63
- `function test() {
64
- throw new Error("fail");
65
- }`,
66
- [],
67
- );
68
- expect(value.kind).toBe("never");
69
- expect(throws.kind).toBe("instance");
70
- if (throws.kind === "instance") {
71
- expect(throws.className).toBe("Error");
72
- }
73
- });
74
-
75
- it("try-catch digests throws", () => {
76
- const { value, throws } = evalFnFull(
77
- `function test() {
78
- try {
79
- throw new TypeError("bad type");
80
- } catch (e) {
81
- return "recovered";
82
- }
83
- }`,
84
- [],
85
- );
86
- expect(typeValueEquals(value, T.literal("recovered"))).toBe(true);
87
- expect(throws.kind).toBe("never");
88
- });
89
-
90
- it("finally always executes", () => {
91
- const result = evalFn(
92
- `function test() {
93
- let x = 0;
94
- try {
95
- x = 1;
96
- return x;
97
- } finally {
98
- x = 2;
99
- }
100
- }`,
101
- [],
102
- );
103
- expect(typeValueEquals(result, T.literal(1))).toBe(true);
104
- });
105
-
106
- it("catch receives the thrown type", () => {
107
- const result = evalFn(
108
- `function test() {
109
- try {
110
- throw new TypeError("bad");
111
- } catch (e) {
112
- return e.message;
113
- }
114
- }`,
115
- [],
116
- );
117
- expect(typeValueEquals(result, T.literal("bad"))).toBe(true);
118
- });
119
- });
120
-
121
- describe("NewExpression", () => {
122
- it("creates Error instance", () => {
123
- const result = evalCode(`new Error("something went wrong")`);
124
- expect(result.kind).toBe("instance");
125
- if (result.kind === "instance") {
126
- expect(result.className).toBe("Error");
127
- expect(typeValueEquals(result.properties.message, T.literal("something went wrong"))).toBe(true);
128
- }
129
- });
130
-
131
- it("creates TypeError instance", () => {
132
- const result = evalCode(`new TypeError("bad type")`);
133
- expect(result.kind).toBe("instance");
134
- if (result.kind === "instance") {
135
- expect(result.className).toBe("TypeError");
136
- }
137
- });
138
-
139
- it("creates instance from user-defined class", () => {
140
- const result = evalCode(`
141
- class Point {
142
- constructor(x, y) {
143
- this.x = x;
144
- this.y = y;
145
- }
146
- }
147
- new Point(1, 2)
148
- `);
149
- expect(result.kind).toBe("instance");
150
- if (result.kind === "instance") {
151
- expect(result.className).toBe("Point");
152
- expect(typeValueEquals(result.properties.x, T.literal(1))).toBe(true);
153
- expect(typeValueEquals(result.properties.y, T.literal(2))).toBe(true);
154
- }
155
- });
156
- });
157
-
158
- describe("ClassDeclaration", () => {
159
- it("binds class to environment", () => {
160
- const ast = parse(`
161
- class Foo {
162
- constructor(val) {
163
- this.val = val;
164
- }
165
- getVal() {
166
- return this.val;
167
- }
168
- }
169
- const f = new Foo(42);
170
- `);
171
- const env = createEnvironment();
172
- evaluateProgram(ast, env);
173
- const f = env.lookup("f");
174
- expect(f.kind).toBe("instance");
175
- if (f.kind === "instance") {
176
- expect(f.className).toBe("Foo");
177
- expect(typeValueEquals(f.properties.val, T.literal(42))).toBe(true);
178
- expect(f.properties.getVal?.kind).toBe("function");
179
- }
180
- });
181
- });
182
-
183
- describe("instanceof", () => {
184
- it("checks instanceof for known instance", () => {
185
- const result = evalCode(`
186
- const e = new Error("test");
187
- e instanceof Error
188
- `);
189
- expect(typeValueEquals(result, T.literal(true))).toBe(true);
190
- });
191
-
192
- it("instanceof narrowing in if", () => {
193
- const result = evalFn(
194
- `function test(x) {
195
- if (x instanceof Error) {
196
- return x.message;
197
- }
198
- return "not error";
199
- }`,
200
- [T.instanceOf("Error", { message: T.literal("oops") })],
201
- );
202
- expect(typeValueEquals(result, T.literal("oops"))).toBe(true);
203
- });
204
-
205
- it("instanceof returns boolean for unknown types", () => {
206
- const result = evalCode(`
207
- const x = 42;
208
- x instanceof Error
209
- `);
210
- expect(result).toBe(T.boolean);
211
- });
212
- });
213
-
214
- describe("async/await", () => {
215
- it("async function wraps return in Promise", () => {
216
- const result = evalFn(
217
- `async function fetchData() {
218
- return 42;
219
- }`,
220
- [],
221
- );
222
- expect(result.kind).toBe("promise");
223
- if (result.kind === "promise") {
224
- expect(typeValueEquals(result.value, T.literal(42))).toBe(true);
225
- }
226
- });
227
-
228
- it("await unwraps Promise", () => {
229
- const result = evalFn(
230
- `async function test() {
231
- const p = async function inner() { return 10; };
232
- const val = await p();
233
- return val;
234
- }`,
235
- [],
236
- );
237
- expect(result.kind).toBe("promise");
238
- if (result.kind === "promise") {
239
- expect(typeValueEquals(result.value, T.literal(10))).toBe(true);
240
- }
241
- });
242
-
243
- it("await on non-promise returns value as-is", () => {
244
- const result = evalFn(
245
- `async function test() {
246
- const val = await 42;
247
- return val;
248
- }`,
249
- [],
250
- );
251
- expect(result.kind).toBe("promise");
252
- if (result.kind === "promise") {
253
- expect(typeValueEquals(result.value, T.literal(42))).toBe(true);
254
- }
255
- });
256
- });
257
-
258
- describe("recursion with memoization", () => {
259
- beforeEach(() => {
260
- resetMemo();
261
- });
262
-
263
- it("handles recursive function with memoization", () => {
264
- const ast = parse(`
265
- function factorial(n) {
266
- if (n === 0) return 1;
267
- if (n === 1) return 1;
268
- return n * factorial(n - 1);
269
- }
270
- `);
271
- const env = createEnvironment();
272
- evaluateProgram(ast, env);
273
- const fn = env.lookup("factorial");
274
- expect(fn.kind).toBe("function");
275
- if (fn.kind === "function") {
276
- (fn as any)._memoize = "factorial";
277
- const result = evaluate(
278
- parse(`factorial(5)`).program.body[0],
279
- env,
280
- );
281
- expect(result.kind).toBe("literal");
282
- }
283
- });
284
-
285
- it("recursive call returns unknown for in-progress memo", () => {
286
- const ast = parse(`
287
- function infinite(x) {
288
- return infinite(x);
289
- }
290
- const result = infinite(1);
291
- `);
292
- const env = createEnvironment();
293
- const fn_ast = ast.program.body[0];
294
- evaluate(fn_ast, env);
295
- const fn = env.lookup("infinite");
296
- if (fn.kind === "function") {
297
- (fn as any)._memoize = "infinite";
298
- }
299
- const callAst = ast.program.body[1];
300
- evaluate(callAst, env);
301
- const result = env.lookup("result");
302
- expect(result.kind).toBe("unknown");
303
- });
304
- });
305
-
306
- describe("modules import/export", () => {
307
- beforeEach(() => {
308
- resetMemo();
309
- });
310
-
311
- it("handles export named declaration", () => {
312
- const ast = parse(`
313
- export const x = 42;
314
- export function add(a, b) { return a + b; }
315
- `);
316
- const env = createEnvironment();
317
- evaluateProgram(ast, env);
318
- expect(typeValueEquals(env.lookup("x"), T.literal(42))).toBe(true);
319
- expect(typeValueEquals(env.lookup("__export_x"), T.literal(42))).toBe(true);
320
- expect(env.lookup("add").kind).toBe("function");
321
- expect(env.lookup("__export_add").kind).toBe("function");
322
- });
323
-
324
- it("handles export default declaration", () => {
325
- const ast = parse(`
326
- export default function main() { return "hello"; }
327
- `);
328
- const env = createEnvironment();
329
- evaluateProgram(ast, env);
330
- expect(env.lookup("__export_default").kind).toBe("function");
331
- });
332
-
333
- it("handles import with module resolver", () => {
334
- const moduleSource = `
335
- export const PI = 3.14;
336
- export function double(x) { return x * 2; }
337
- `;
338
- const moduleAst = parse(moduleSource);
339
-
340
- setModuleResolver((source) => {
341
- if (source === "./math") {
342
- return { ast: moduleAst, filePath: "/fake/math.js" };
343
- }
344
- return null;
345
- });
346
- setCurrentFileDir("/fake");
347
-
348
- const ast = parse(`
349
- import { PI, double } from "./math";
350
- const result = double(PI);
351
- `);
352
- const env = createEnvironment();
353
- evaluateProgram(ast, env);
354
-
355
- expect(typeValueEquals(env.lookup("PI"), T.literal(3.14))).toBe(true);
356
- const result = env.lookup("result");
357
- expect(typeValueEquals(result, T.literal(6.28))).toBe(true);
358
-
359
- setModuleResolver(null);
360
- });
361
-
362
- it("handles default import", () => {
363
- const moduleSource = `
364
- export default function greet() { return "hi"; }
365
- `;
366
- const moduleAst = parse(moduleSource);
367
-
368
- setModuleResolver((source) => {
369
- if (source === "./greet") {
370
- return { ast: moduleAst, filePath: "/fake/greet.js" };
371
- }
372
- return null;
373
- });
374
- setCurrentFileDir("/fake");
375
-
376
- const ast = parse(`
377
- import greet from "./greet";
378
- `);
379
- const env = createEnvironment();
380
- evaluateProgram(ast, env);
381
-
382
- expect(env.lookup("greet").kind).toBe("function");
383
-
384
- setModuleResolver(null);
385
- });
386
-
387
- it("handles namespace import", () => {
388
- const moduleSource = `
389
- export const a = 1;
390
- export const b = 2;
391
- `;
392
- const moduleAst = parse(moduleSource);
393
-
394
- setModuleResolver((source) => {
395
- if (source === "./vals") {
396
- return { ast: moduleAst, filePath: "/fake/vals.js" };
397
- }
398
- return null;
399
- });
400
- setCurrentFileDir("/fake");
401
-
402
- const ast = parse(`
403
- import * as vals from "./vals";
404
- `);
405
- const env = createEnvironment();
406
- evaluateProgram(ast, env);
407
-
408
- const vals = env.lookup("vals");
409
- expect(vals.kind).toBe("object");
410
- if (vals.kind === "object") {
411
- expect(typeValueEquals(vals.properties.a, T.literal(1))).toBe(true);
412
- expect(typeValueEquals(vals.properties.b, T.literal(2))).toBe(true);
413
- }
414
-
415
- setModuleResolver(null);
416
- });
417
-
418
- it("caches modules - same file evaluated once", () => {
419
- setModuleResolver((source) => {
420
- if (source === "./cached") {
421
- return { ast: parse(`export const x = 1;`), filePath: "/fake/cached.js" };
422
- }
423
- return null;
424
- });
425
- setCurrentFileDir("/fake");
426
-
427
- const ast = parse(`
428
- import { x } from "./cached";
429
- import { x as x2 } from "./cached";
430
- `);
431
- const env = createEnvironment();
432
- evaluateProgram(ast, env);
433
-
434
- expect(typeValueEquals(env.lookup("x"), T.literal(1))).toBe(true);
435
- expect(typeValueEquals(env.lookup("x2"), T.literal(1))).toBe(true);
436
-
437
- setModuleResolver(null);
438
- });
439
- });
440
-
441
- describe("SwitchStatement", () => {
442
- it("evaluates matching case", () => {
443
- const result = evalFn(
444
- `function test(x) {
445
- switch (x) {
446
- case 1: return "one";
447
- case 2: return "two";
448
- default: return "other";
449
- }
450
- }`,
451
- [T.literal(1)],
452
- );
453
- expect(typeValueEquals(result, T.literal("one"))).toBe(true);
454
- });
455
-
456
- it("evaluates default case", () => {
457
- const result = evalFn(
458
- `function test(x) {
459
- switch (x) {
460
- case 1: return "one";
461
- default: return "other";
462
- }
463
- }`,
464
- [T.literal(99)],
465
- );
466
- expect(typeValueEquals(result, T.literal("other"))).toBe(true);
467
- });
468
-
469
- it("returns union for abstract discriminant", () => {
470
- const result = evalFn(
471
- `function test(x) {
472
- switch (x) {
473
- case 1: return "one";
474
- case 2: return "two";
475
- default: return "other";
476
- }
477
- }`,
478
- [T.number],
479
- );
480
- expect(result.kind).toBe("union");
481
- });
482
- });
483
-
484
- describe("PromiseType in evaluator", () => {
485
- it("typeof promise is object", () => {
486
- const result = evalCode(`typeof (async function() { return 1; })()`);
487
- expect(typeValueEquals(result, T.literal("object"))).toBe(true);
488
- });
489
- });
490
-
491
- describe("instance property access", () => {
492
- it("accesses instance properties", () => {
493
- const result = evalCode(`
494
- const e = new Error("test");
495
- e.message
496
- `);
497
- expect(typeValueEquals(result, T.literal("test"))).toBe(true);
498
- });
499
- });
500
-
501
- describe("export specifiers", () => {
502
- it("handles export { x, y }", () => {
503
- const ast = parse(`
504
- const x = 1;
505
- const y = 2;
506
- export { x, y };
507
- `);
508
- const env = createEnvironment();
509
- evaluateProgram(ast, env);
510
- expect(typeValueEquals(env.lookup("__export_x"), T.literal(1))).toBe(true);
511
- expect(typeValueEquals(env.lookup("__export_y"), T.literal(2))).toBe(true);
512
- });
513
- });
@@ -1,121 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { T, typeValueEquals, typeValueToString, isSubtypeOf } from "@nudojs/core";
3
- import { isTemplate, getTemplateParts, isRange, getRangeMeta } from "@nudojs/core";
4
- import { evaluate, evaluateProgram } from "../evaluator.ts";
5
- import { createEnvironment } from "@nudojs/core";
6
- import { parse } from "@nudojs/parser";
7
-
8
- function evalExpr(code: string): import("@nudojs/core").TypeValue {
9
- const ast = parse(code);
10
- const env = createEnvironment();
11
- return evaluateProgram(ast, env);
12
- }
13
-
14
- function evalWithBindings(code: string, bindings: Record<string, import("@nudojs/core").TypeValue>): import("@nudojs/core").TypeValue {
15
- const ast = parse(code);
16
- const env = createEnvironment();
17
- for (const [k, v] of Object.entries(bindings)) {
18
- env.bind(k, v);
19
- }
20
- return evaluateProgram(ast, env);
21
- }
22
-
23
- describe("Template Literal Evaluation", () => {
24
- it("all-literal template produces literal", () => {
25
- const result = evalExpr("`hello world`");
26
- expect(typeValueEquals(result, T.literal("hello world"))).toBe(true);
27
- });
28
-
29
- it("template with abstract expression produces template type", () => {
30
- const result = evalWithBindings("`xy${x}`", { x: T.string });
31
- expect(isTemplate(result)).toBe(true);
32
- expect(typeValueToString(result)).toBe("`xy${string}`");
33
- });
34
-
35
- it("template with multiple parts", () => {
36
- const result = evalWithBindings("`${x}!`", { x: T.string });
37
- expect(isTemplate(result)).toBe(true);
38
- expect(typeValueToString(result)).toBe("`${string}!`");
39
- });
40
-
41
- it("string concatenation with + produces template", () => {
42
- const result = evalWithBindings('"xy" + x', { x: T.string });
43
- expect(isTemplate(result)).toBe(true);
44
- expect(typeValueToString(result)).toBe("`xy${string}`");
45
- });
46
-
47
- it("chained concatenation produces template", () => {
48
- const result = evalWithBindings('"x" + x + "!"', { x: T.string });
49
- expect(isTemplate(result)).toBe(true);
50
- expect(typeValueToString(result)).toBe("`x${string}!`");
51
- });
52
- });
53
-
54
- describe("Template Method Dispatch", () => {
55
- it("startsWith on template with known prefix returns true", () => {
56
- const result = evalWithBindings(
57
- 'const s = "xy" + x; s.startsWith("x")',
58
- { x: T.string },
59
- );
60
- // The evaluateMethodCall should dispatch to template's startsWith
61
- // and return T.literal(true)
62
- // Note: this requires string method support in evaluateMethodCall
63
- // which we add in 6.1. For now, refined dispatch handles it.
64
- if (result.kind === "literal") {
65
- expect(result.value).toBe(true);
66
- }
67
- });
68
- });
69
-
70
- describe("Range Narrowing", () => {
71
- it("x >= 0 narrows to range in true branch", () => {
72
- const code = `
73
- function f(x) {
74
- if (x >= 0) {
75
- return x;
76
- }
77
- return x;
78
- }
79
- `;
80
- const ast = parse(code);
81
- const env = createEnvironment();
82
- evaluateProgram(ast, env);
83
- const fn = env.lookup("f");
84
- expect(fn.kind).toBe("function");
85
- });
86
-
87
- it("range comparison produces correct results", () => {
88
- const result = evalWithBindings(
89
- 'const r = x >= 0 ? x : 0; r',
90
- { x: T.number },
91
- );
92
- // In the true branch, x is narrowed to range(min:0)
93
- // In the false branch, result is T.literal(0)
94
- // Union of both
95
- expect(result).toBeDefined();
96
- });
97
- });
98
-
99
- describe("User-defined Refined Types", () => {
100
- it("refined type with custom ops", () => {
101
- const odd = T.refine(T.number, {
102
- name: "odd",
103
- meta: {},
104
- ops: {
105
- "%"(self, other) {
106
- if (other.kind === "literal" && other.value === 2) return T.literal(1);
107
- return undefined;
108
- },
109
- },
110
- });
111
-
112
- const result = evalWithBindings("x % 2", { x: odd });
113
- expect(typeValueEquals(result, T.literal(1))).toBe(true);
114
- });
115
-
116
- it("refined type falls back to base for undefined ops", () => {
117
- const odd = T.refine(T.number, { name: "odd", meta: {} });
118
- const result = evalWithBindings("x + 1", { x: odd });
119
- expect(typeValueEquals(result, T.number)).toBe(true);
120
- });
121
- });