@nudojs/core 0.1.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/src/ops.ts ADDED
@@ -0,0 +1,147 @@
1
+ import { type TypeValue, T, isSubtypeOf } from "./type-value.ts";
2
+
3
+ function bothLiteral(
4
+ l: TypeValue,
5
+ r: TypeValue,
6
+ ): { lv: string | number | boolean | null | undefined; rv: string | number | boolean | null | undefined } | null {
7
+ if (l.kind === "literal" && r.kind === "literal") {
8
+ return { lv: l.value, rv: r.value };
9
+ }
10
+ return null;
11
+ }
12
+
13
+ export const Ops = {
14
+ add(left: TypeValue, right: TypeValue): TypeValue {
15
+ const lit = bothLiteral(left, right);
16
+ if (lit) {
17
+ return T.literal((lit.lv as any) + (lit.rv as any));
18
+ }
19
+ if (isSubtypeOf(left, T.string) || isSubtypeOf(right, T.string)) {
20
+ return T.string;
21
+ }
22
+ if (isSubtypeOf(left, T.number) && isSubtypeOf(right, T.number)) {
23
+ return T.number;
24
+ }
25
+ return T.union(T.number, T.string);
26
+ },
27
+
28
+ sub(left: TypeValue, right: TypeValue): TypeValue {
29
+ const lit = bothLiteral(left, right);
30
+ if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
31
+ return T.literal(lit.lv - lit.rv);
32
+ }
33
+ return T.number;
34
+ },
35
+
36
+ mul(left: TypeValue, right: TypeValue): TypeValue {
37
+ const lit = bothLiteral(left, right);
38
+ if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
39
+ return T.literal(lit.lv * lit.rv);
40
+ }
41
+ return T.number;
42
+ },
43
+
44
+ div(left: TypeValue, right: TypeValue): TypeValue {
45
+ const lit = bothLiteral(left, right);
46
+ if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
47
+ return T.literal(lit.lv / lit.rv);
48
+ }
49
+ return T.number;
50
+ },
51
+
52
+ mod(left: TypeValue, right: TypeValue): TypeValue {
53
+ const lit = bothLiteral(left, right);
54
+ if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
55
+ return T.literal(lit.lv % lit.rv);
56
+ }
57
+ return T.number;
58
+ },
59
+
60
+ strictEq(left: TypeValue, right: TypeValue): TypeValue {
61
+ const lit = bothLiteral(left, right);
62
+ if (lit) return T.literal(lit.lv === lit.rv);
63
+ return T.boolean;
64
+ },
65
+
66
+ strictNeq(left: TypeValue, right: TypeValue): TypeValue {
67
+ const lit = bothLiteral(left, right);
68
+ if (lit) return T.literal(lit.lv !== lit.rv);
69
+ return T.boolean;
70
+ },
71
+
72
+ gt(left: TypeValue, right: TypeValue): TypeValue {
73
+ const lit = bothLiteral(left, right);
74
+ if (lit) return T.literal((lit.lv as any) > (lit.rv as any));
75
+ return T.boolean;
76
+ },
77
+
78
+ lt(left: TypeValue, right: TypeValue): TypeValue {
79
+ const lit = bothLiteral(left, right);
80
+ if (lit) return T.literal((lit.lv as any) < (lit.rv as any));
81
+ return T.boolean;
82
+ },
83
+
84
+ gte(left: TypeValue, right: TypeValue): TypeValue {
85
+ const lit = bothLiteral(left, right);
86
+ if (lit) return T.literal((lit.lv as any) >= (lit.rv as any));
87
+ return T.boolean;
88
+ },
89
+
90
+ lte(left: TypeValue, right: TypeValue): TypeValue {
91
+ const lit = bothLiteral(left, right);
92
+ if (lit) return T.literal((lit.lv as any) <= (lit.rv as any));
93
+ return T.boolean;
94
+ },
95
+
96
+ typeof_(operand: TypeValue): TypeValue {
97
+ if (operand.kind === "literal") {
98
+ const v = operand.value;
99
+ if (v === null) return T.literal("object");
100
+ return T.literal(typeof v);
101
+ }
102
+ if (operand.kind === "primitive") return T.literal(operand.type);
103
+ if (operand.kind === "object") return T.literal("object");
104
+ if (operand.kind === "array" || operand.kind === "tuple")
105
+ return T.literal("object");
106
+ if (operand.kind === "function") return T.literal("function");
107
+ if (operand.kind === "promise") return T.literal("object");
108
+ if (operand.kind === "instance") return T.literal("object");
109
+ return T.string;
110
+ },
111
+
112
+ not(operand: TypeValue): TypeValue {
113
+ if (operand.kind === "literal") return T.literal(!operand.value);
114
+ return T.boolean;
115
+ },
116
+
117
+ neg(operand: TypeValue): TypeValue {
118
+ if (operand.kind === "literal" && typeof operand.value === "number") {
119
+ return T.literal(-operand.value);
120
+ }
121
+ return T.number;
122
+ },
123
+ } as const;
124
+
125
+ const binaryOpMap: Record<string, (l: TypeValue, r: TypeValue) => TypeValue> = {
126
+ "+": Ops.add,
127
+ "-": Ops.sub,
128
+ "*": Ops.mul,
129
+ "/": Ops.div,
130
+ "%": Ops.mod,
131
+ "===": Ops.strictEq,
132
+ "!==": Ops.strictNeq,
133
+ ">": Ops.gt,
134
+ "<": Ops.lt,
135
+ ">=": Ops.gte,
136
+ "<=": Ops.lte,
137
+ };
138
+
139
+ export function applyBinaryOp(
140
+ op: string,
141
+ left: TypeValue,
142
+ right: TypeValue,
143
+ ): TypeValue {
144
+ const fn = binaryOpMap[op];
145
+ if (!fn) return T.unknown;
146
+ return fn(left, right);
147
+ }
@@ -0,0 +1,332 @@
1
+ import type { Node } from "@babel/types";
2
+ import type { Environment } from "./environment.ts";
3
+
4
+ // --- TypeValue discriminated union ---
5
+
6
+ export type LiteralValue = string | number | boolean | null | undefined;
7
+
8
+ export type TypeValue =
9
+ | { kind: "literal"; value: LiteralValue }
10
+ | {
11
+ kind: "primitive";
12
+ type: "number" | "string" | "boolean" | "bigint" | "symbol";
13
+ }
14
+ | { kind: "object"; properties: Record<string, TypeValue>; id: symbol }
15
+ | { kind: "array"; element: TypeValue }
16
+ | { kind: "tuple"; elements: TypeValue[] }
17
+ | {
18
+ kind: "function";
19
+ params: string[];
20
+ body: Node;
21
+ closure: Environment;
22
+ }
23
+ | { kind: "promise"; value: TypeValue }
24
+ | { kind: "instance"; className: string; properties: Record<string, TypeValue> }
25
+ | { kind: "union"; members: TypeValue[] }
26
+ | { kind: "never" }
27
+ | { kind: "unknown" };
28
+
29
+ // --- T: static factory ---
30
+
31
+ function createUnion(...members: TypeValue[]): TypeValue {
32
+ return simplifyUnion(members);
33
+ }
34
+
35
+ export const T = {
36
+ literal: (value: LiteralValue): TypeValue => ({ kind: "literal", value }),
37
+ number: { kind: "primitive", type: "number" } as TypeValue,
38
+ string: { kind: "primitive", type: "string" } as TypeValue,
39
+ boolean: { kind: "primitive", type: "boolean" } as TypeValue,
40
+ bigint: { kind: "primitive", type: "bigint" } as TypeValue,
41
+ symbol: { kind: "primitive", type: "symbol" } as TypeValue,
42
+ null: { kind: "literal", value: null } as TypeValue,
43
+ undefined: { kind: "literal", value: undefined } as TypeValue,
44
+ unknown: { kind: "unknown" } as TypeValue,
45
+ never: { kind: "never" } as TypeValue,
46
+
47
+ object: (properties: Record<string, TypeValue>): TypeValue => ({
48
+ kind: "object",
49
+ properties,
50
+ id: Symbol("object"),
51
+ }),
52
+ array: (element: TypeValue): TypeValue => ({ kind: "array", element }),
53
+ tuple: (elements: TypeValue[]): TypeValue => ({ kind: "tuple", elements }),
54
+ promise: (value: TypeValue): TypeValue => ({ kind: "promise", value }),
55
+ instanceOf: (className: string, properties: Record<string, TypeValue> = {}): TypeValue => ({
56
+ kind: "instance",
57
+ className,
58
+ properties,
59
+ }),
60
+ union: createUnion,
61
+ fn: (
62
+ params: string[],
63
+ body: Node,
64
+ closure: Environment,
65
+ ): TypeValue => ({
66
+ kind: "function",
67
+ params,
68
+ body,
69
+ closure,
70
+ }),
71
+ } as const;
72
+
73
+ // --- Helpers ---
74
+
75
+ export function typeValueEquals(a: TypeValue, b: TypeValue): boolean {
76
+ if (a.kind !== b.kind) return false;
77
+ if (a.kind === "literal" && b.kind === "literal") return a.value === b.value;
78
+ if (a.kind === "primitive" && b.kind === "primitive") return a.type === b.type;
79
+ if (a.kind === "never" && b.kind === "never") return true;
80
+ if (a.kind === "unknown" && b.kind === "unknown") return true;
81
+ if (a.kind === "promise" && b.kind === "promise") {
82
+ return typeValueEquals(a.value, b.value);
83
+ }
84
+ if (a.kind === "instance" && b.kind === "instance") {
85
+ return a.className === b.className;
86
+ }
87
+ if (a.kind === "union" && b.kind === "union") {
88
+ return (
89
+ a.members.length === b.members.length &&
90
+ a.members.every((m, i) => typeValueEquals(m, b.members[i]))
91
+ );
92
+ }
93
+ return a === b;
94
+ }
95
+
96
+ export function simplifyUnion(members: TypeValue[]): TypeValue {
97
+ const flat: TypeValue[] = [];
98
+ for (const m of members) {
99
+ if (m.kind === "never") continue;
100
+ if (m.kind === "union") {
101
+ flat.push(...m.members);
102
+ } else {
103
+ flat.push(m);
104
+ }
105
+ }
106
+
107
+ const deduped: TypeValue[] = [];
108
+ for (const m of flat) {
109
+ if (!deduped.some((d) => typeValueEquals(d, m))) {
110
+ deduped.push(m);
111
+ }
112
+ }
113
+
114
+ if (deduped.length === 0) return T.never;
115
+ if (deduped.length === 1) return deduped[0];
116
+ if (deduped.some((m) => m.kind === "unknown")) return T.unknown;
117
+ return { kind: "union", members: deduped };
118
+ }
119
+
120
+ export function widenLiteral(tv: TypeValue): TypeValue {
121
+ if (tv.kind !== "literal") return tv;
122
+ const v = tv.value;
123
+ if (typeof v === "number") return T.number;
124
+ if (typeof v === "string") return T.string;
125
+ if (typeof v === "boolean") return T.boolean;
126
+ if (v === null) return T.null;
127
+ if (v === undefined) return T.undefined;
128
+ return T.unknown;
129
+ }
130
+
131
+ export function isSubtypeOf(a: TypeValue, b: TypeValue): boolean {
132
+ if (b.kind === "unknown") return true;
133
+ if (a.kind === "never") return true;
134
+ if (typeValueEquals(a, b)) return true;
135
+
136
+ if (a.kind === "literal" && b.kind === "primitive") {
137
+ const v = a.value;
138
+ if (b.type === "number" && typeof v === "number") return true;
139
+ if (b.type === "string" && typeof v === "string") return true;
140
+ if (b.type === "boolean" && typeof v === "boolean") return true;
141
+ return false;
142
+ }
143
+
144
+ if (a.kind === "object" && b.kind === "object") {
145
+ return Object.entries(b.properties).every(
146
+ ([k, bv]) => k in a.properties && isSubtypeOf(a.properties[k], bv),
147
+ );
148
+ }
149
+
150
+ if (a.kind === "array" && b.kind === "array") {
151
+ return isSubtypeOf(a.element, b.element);
152
+ }
153
+
154
+ if (a.kind === "tuple" && b.kind === "tuple") {
155
+ return (
156
+ a.elements.length === b.elements.length &&
157
+ a.elements.every((el, i) => isSubtypeOf(el, b.elements[i]))
158
+ );
159
+ }
160
+
161
+ if (a.kind === "tuple" && b.kind === "array") {
162
+ return a.elements.every((el) => isSubtypeOf(el, b.element));
163
+ }
164
+
165
+ if (a.kind === "promise" && b.kind === "promise") {
166
+ return isSubtypeOf(a.value, b.value);
167
+ }
168
+
169
+ if (a.kind === "instance" && b.kind === "instance") {
170
+ return a.className === b.className || isErrorSubclass(a.className, b.className);
171
+ }
172
+
173
+ if (a.kind === "union") {
174
+ return a.members.every((m) => isSubtypeOf(m, b));
175
+ }
176
+
177
+ if (b.kind === "union") {
178
+ return b.members.some((m) => isSubtypeOf(a, m));
179
+ }
180
+
181
+ return false;
182
+ }
183
+
184
+ const errorHierarchy: Record<string, string> = {
185
+ TypeError: "Error",
186
+ SyntaxError: "Error",
187
+ RangeError: "Error",
188
+ ReferenceError: "Error",
189
+ URIError: "Error",
190
+ EvalError: "Error",
191
+ };
192
+
193
+ function isErrorSubclass(child: string, parent: string): boolean {
194
+ if (child === parent) return true;
195
+ const sup = errorHierarchy[child];
196
+ return sup ? isErrorSubclass(sup, parent) : false;
197
+ }
198
+
199
+ export function deepCloneTypeValue(tv: TypeValue, idMap?: Map<symbol, symbol>): TypeValue {
200
+ const map = idMap ?? new Map<symbol, symbol>();
201
+ if (tv.kind === "object") {
202
+ let newId = map.get(tv.id);
203
+ if (!newId) {
204
+ newId = Symbol("object");
205
+ map.set(tv.id, newId);
206
+ }
207
+ const newProps: Record<string, TypeValue> = {};
208
+ for (const [k, v] of Object.entries(tv.properties)) {
209
+ newProps[k] = deepCloneTypeValue(v, map);
210
+ }
211
+ return { kind: "object", properties: newProps, id: newId };
212
+ }
213
+ if (tv.kind === "array") {
214
+ return { kind: "array", element: deepCloneTypeValue(tv.element, map) };
215
+ }
216
+ if (tv.kind === "tuple") {
217
+ return { kind: "tuple", elements: tv.elements.map((e) => deepCloneTypeValue(e, map)) };
218
+ }
219
+ if (tv.kind === "promise") {
220
+ return { kind: "promise", value: deepCloneTypeValue(tv.value, map) };
221
+ }
222
+ if (tv.kind === "instance") {
223
+ const newProps: Record<string, TypeValue> = {};
224
+ for (const [k, v] of Object.entries(tv.properties)) {
225
+ newProps[k] = deepCloneTypeValue(v, map);
226
+ }
227
+ return { kind: "instance", className: tv.className, properties: newProps };
228
+ }
229
+ if (tv.kind === "union") {
230
+ return simplifyUnion(tv.members.map((m) => deepCloneTypeValue(m, map)));
231
+ }
232
+ return tv;
233
+ }
234
+
235
+ export function mergeObjectProperties(
236
+ a: TypeValue & { kind: "object" },
237
+ b: TypeValue & { kind: "object" },
238
+ ): TypeValue {
239
+ const allKeys = new Set([...Object.keys(a.properties), ...Object.keys(b.properties)]);
240
+ const merged: Record<string, TypeValue> = {};
241
+ for (const k of allKeys) {
242
+ const av = a.properties[k];
243
+ const bv = b.properties[k];
244
+ if (av && bv) {
245
+ merged[k] = simplifyUnion([av, bv]);
246
+ } else {
247
+ merged[k] = av ?? bv;
248
+ }
249
+ }
250
+ return { kind: "object", properties: merged, id: a.id };
251
+ }
252
+
253
+ export function typeValueToString(tv: TypeValue): string {
254
+ switch (tv.kind) {
255
+ case "literal": {
256
+ const v = tv.value;
257
+ if (v === null) return "null";
258
+ if (v === undefined) return "undefined";
259
+ if (typeof v === "string") return JSON.stringify(v);
260
+ return String(v);
261
+ }
262
+ case "primitive":
263
+ return tv.type;
264
+ case "object": {
265
+ const entries = Object.entries(tv.properties);
266
+ if (entries.length === 0) return "{}";
267
+ const inner = entries
268
+ .map(([k, v]) => `${k}: ${typeValueToString(v)}`)
269
+ .join(", ");
270
+ return `{ ${inner} }`;
271
+ }
272
+ case "array":
273
+ return `${typeValueToString(tv.element)}[]`;
274
+ case "tuple": {
275
+ const inner = tv.elements.map(typeValueToString).join(", ");
276
+ return `[${inner}]`;
277
+ }
278
+ case "function": {
279
+ const params = tv.params.join(", ");
280
+ return `(${params}) => ...`;
281
+ }
282
+ case "promise":
283
+ return `Promise<${typeValueToString(tv.value)}>`;
284
+ case "instance": {
285
+ const entries = Object.entries(tv.properties);
286
+ if (entries.length === 0) return tv.className;
287
+ const inner = entries
288
+ .map(([k, v]) => `${k}: ${typeValueToString(v)}`)
289
+ .join(", ");
290
+ return `${tv.className} { ${inner} }`;
291
+ }
292
+ case "union": {
293
+ return tv.members.map(typeValueToString).join(" | ");
294
+ }
295
+ case "never":
296
+ return "never";
297
+ case "unknown":
298
+ return "unknown";
299
+ }
300
+ }
301
+
302
+ export function narrowType(
303
+ tv: TypeValue,
304
+ predicate: (member: TypeValue) => boolean,
305
+ ): TypeValue {
306
+ if (tv.kind === "union") {
307
+ return simplifyUnion(tv.members.filter(predicate));
308
+ }
309
+ return predicate(tv) ? tv : T.never;
310
+ }
311
+
312
+ export function subtractType(
313
+ tv: TypeValue,
314
+ predicate: (member: TypeValue) => boolean,
315
+ ): TypeValue {
316
+ return narrowType(tv, (m) => !predicate(m));
317
+ }
318
+
319
+ export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
320
+ if (tv.kind === "literal") {
321
+ const v = tv.value;
322
+ if (v === null) return "object";
323
+ return typeof v;
324
+ }
325
+ if (tv.kind === "primitive") return tv.type;
326
+ if (tv.kind === "object") return "object";
327
+ if (tv.kind === "array" || tv.kind === "tuple") return "object";
328
+ if (tv.kind === "function") return "function";
329
+ if (tv.kind === "promise") return "object";
330
+ if (tv.kind === "instance") return "object";
331
+ return undefined;
332
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }