@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/CHANGELOG.md +7 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +440 -0
- package/package.json +12 -0
- package/src/__tests__/environment.test.ts +48 -0
- package/src/__tests__/ops.test.ts +109 -0
- package/src/__tests__/type-value-phase2.test.ts +117 -0
- package/src/__tests__/type-value-phase3.test.ts +94 -0
- package/src/__tests__/type-value.test.ts +213 -0
- package/src/environment.ts +69 -0
- package/src/index.ts +22 -0
- package/src/ops.ts +147 -0
- package/src/type-value.ts +332 -0
- package/tsconfig.json +8 -0
package/CHANGELOG.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Node } from '@babel/types';
|
|
2
|
+
|
|
3
|
+
type Environment = {
|
|
4
|
+
lookup(name: string): TypeValue;
|
|
5
|
+
bind(name: string, value: TypeValue): Environment;
|
|
6
|
+
update(name: string, value: TypeValue): boolean;
|
|
7
|
+
extend(bindings: Record<string, TypeValue>): Environment;
|
|
8
|
+
has(name: string): boolean;
|
|
9
|
+
snapshot(): Environment;
|
|
10
|
+
getOwnBindings(): Record<string, TypeValue>;
|
|
11
|
+
};
|
|
12
|
+
declare function createEnvironment(parent?: Environment, bindings?: Map<string, TypeValue>): Environment;
|
|
13
|
+
|
|
14
|
+
type LiteralValue = string | number | boolean | null | undefined;
|
|
15
|
+
type TypeValue = {
|
|
16
|
+
kind: "literal";
|
|
17
|
+
value: LiteralValue;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "primitive";
|
|
20
|
+
type: "number" | "string" | "boolean" | "bigint" | "symbol";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "object";
|
|
23
|
+
properties: Record<string, TypeValue>;
|
|
24
|
+
id: symbol;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "array";
|
|
27
|
+
element: TypeValue;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "tuple";
|
|
30
|
+
elements: TypeValue[];
|
|
31
|
+
} | {
|
|
32
|
+
kind: "function";
|
|
33
|
+
params: string[];
|
|
34
|
+
body: Node;
|
|
35
|
+
closure: Environment;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "promise";
|
|
38
|
+
value: TypeValue;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "instance";
|
|
41
|
+
className: string;
|
|
42
|
+
properties: Record<string, TypeValue>;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "union";
|
|
45
|
+
members: TypeValue[];
|
|
46
|
+
} | {
|
|
47
|
+
kind: "never";
|
|
48
|
+
} | {
|
|
49
|
+
kind: "unknown";
|
|
50
|
+
};
|
|
51
|
+
declare function createUnion(...members: TypeValue[]): TypeValue;
|
|
52
|
+
declare const T: {
|
|
53
|
+
readonly literal: (value: LiteralValue) => TypeValue;
|
|
54
|
+
readonly number: TypeValue;
|
|
55
|
+
readonly string: TypeValue;
|
|
56
|
+
readonly boolean: TypeValue;
|
|
57
|
+
readonly bigint: TypeValue;
|
|
58
|
+
readonly symbol: TypeValue;
|
|
59
|
+
readonly null: TypeValue;
|
|
60
|
+
readonly undefined: TypeValue;
|
|
61
|
+
readonly unknown: TypeValue;
|
|
62
|
+
readonly never: TypeValue;
|
|
63
|
+
readonly object: (properties: Record<string, TypeValue>) => TypeValue;
|
|
64
|
+
readonly array: (element: TypeValue) => TypeValue;
|
|
65
|
+
readonly tuple: (elements: TypeValue[]) => TypeValue;
|
|
66
|
+
readonly promise: (value: TypeValue) => TypeValue;
|
|
67
|
+
readonly instanceOf: (className: string, properties?: Record<string, TypeValue>) => TypeValue;
|
|
68
|
+
readonly union: typeof createUnion;
|
|
69
|
+
readonly fn: (params: string[], body: Node, closure: Environment) => TypeValue;
|
|
70
|
+
};
|
|
71
|
+
declare function typeValueEquals(a: TypeValue, b: TypeValue): boolean;
|
|
72
|
+
declare function simplifyUnion(members: TypeValue[]): TypeValue;
|
|
73
|
+
declare function widenLiteral(tv: TypeValue): TypeValue;
|
|
74
|
+
declare function isSubtypeOf(a: TypeValue, b: TypeValue): boolean;
|
|
75
|
+
declare function deepCloneTypeValue(tv: TypeValue, idMap?: Map<symbol, symbol>): TypeValue;
|
|
76
|
+
declare function mergeObjectProperties(a: TypeValue & {
|
|
77
|
+
kind: "object";
|
|
78
|
+
}, b: TypeValue & {
|
|
79
|
+
kind: "object";
|
|
80
|
+
}): TypeValue;
|
|
81
|
+
declare function typeValueToString(tv: TypeValue): string;
|
|
82
|
+
declare function narrowType(tv: TypeValue, predicate: (member: TypeValue) => boolean): TypeValue;
|
|
83
|
+
declare function subtractType(tv: TypeValue, predicate: (member: TypeValue) => boolean): TypeValue;
|
|
84
|
+
declare function getPrimitiveTypeOf(tv: TypeValue): string | undefined;
|
|
85
|
+
|
|
86
|
+
declare const Ops: {
|
|
87
|
+
readonly add: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
88
|
+
readonly sub: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
89
|
+
readonly mul: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
90
|
+
readonly div: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
91
|
+
readonly mod: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
92
|
+
readonly strictEq: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
93
|
+
readonly strictNeq: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
94
|
+
readonly gt: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
95
|
+
readonly lt: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
96
|
+
readonly gte: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
97
|
+
readonly lte: (left: TypeValue, right: TypeValue) => TypeValue;
|
|
98
|
+
readonly typeof_: (operand: TypeValue) => TypeValue;
|
|
99
|
+
readonly not: (operand: TypeValue) => TypeValue;
|
|
100
|
+
readonly neg: (operand: TypeValue) => TypeValue;
|
|
101
|
+
};
|
|
102
|
+
declare function applyBinaryOp(op: string, left: TypeValue, right: TypeValue): TypeValue;
|
|
103
|
+
|
|
104
|
+
export { type Environment, type LiteralValue, Ops, T, type TypeValue, applyBinaryOp, createEnvironment, deepCloneTypeValue, getPrimitiveTypeOf, isSubtypeOf, mergeObjectProperties, narrowType, simplifyUnion, subtractType, typeValueEquals, typeValueToString, widenLiteral };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// src/type-value.ts
|
|
2
|
+
function createUnion(...members) {
|
|
3
|
+
return simplifyUnion(members);
|
|
4
|
+
}
|
|
5
|
+
var T = {
|
|
6
|
+
literal: (value) => ({ kind: "literal", value }),
|
|
7
|
+
number: { kind: "primitive", type: "number" },
|
|
8
|
+
string: { kind: "primitive", type: "string" },
|
|
9
|
+
boolean: { kind: "primitive", type: "boolean" },
|
|
10
|
+
bigint: { kind: "primitive", type: "bigint" },
|
|
11
|
+
symbol: { kind: "primitive", type: "symbol" },
|
|
12
|
+
null: { kind: "literal", value: null },
|
|
13
|
+
undefined: { kind: "literal", value: void 0 },
|
|
14
|
+
unknown: { kind: "unknown" },
|
|
15
|
+
never: { kind: "never" },
|
|
16
|
+
object: (properties) => ({
|
|
17
|
+
kind: "object",
|
|
18
|
+
properties,
|
|
19
|
+
id: Symbol("object")
|
|
20
|
+
}),
|
|
21
|
+
array: (element) => ({ kind: "array", element }),
|
|
22
|
+
tuple: (elements) => ({ kind: "tuple", elements }),
|
|
23
|
+
promise: (value) => ({ kind: "promise", value }),
|
|
24
|
+
instanceOf: (className, properties = {}) => ({
|
|
25
|
+
kind: "instance",
|
|
26
|
+
className,
|
|
27
|
+
properties
|
|
28
|
+
}),
|
|
29
|
+
union: createUnion,
|
|
30
|
+
fn: (params, body, closure) => ({
|
|
31
|
+
kind: "function",
|
|
32
|
+
params,
|
|
33
|
+
body,
|
|
34
|
+
closure
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
function typeValueEquals(a, b) {
|
|
38
|
+
if (a.kind !== b.kind) return false;
|
|
39
|
+
if (a.kind === "literal" && b.kind === "literal") return a.value === b.value;
|
|
40
|
+
if (a.kind === "primitive" && b.kind === "primitive") return a.type === b.type;
|
|
41
|
+
if (a.kind === "never" && b.kind === "never") return true;
|
|
42
|
+
if (a.kind === "unknown" && b.kind === "unknown") return true;
|
|
43
|
+
if (a.kind === "promise" && b.kind === "promise") {
|
|
44
|
+
return typeValueEquals(a.value, b.value);
|
|
45
|
+
}
|
|
46
|
+
if (a.kind === "instance" && b.kind === "instance") {
|
|
47
|
+
return a.className === b.className;
|
|
48
|
+
}
|
|
49
|
+
if (a.kind === "union" && b.kind === "union") {
|
|
50
|
+
return a.members.length === b.members.length && a.members.every((m, i) => typeValueEquals(m, b.members[i]));
|
|
51
|
+
}
|
|
52
|
+
return a === b;
|
|
53
|
+
}
|
|
54
|
+
function simplifyUnion(members) {
|
|
55
|
+
const flat = [];
|
|
56
|
+
for (const m of members) {
|
|
57
|
+
if (m.kind === "never") continue;
|
|
58
|
+
if (m.kind === "union") {
|
|
59
|
+
flat.push(...m.members);
|
|
60
|
+
} else {
|
|
61
|
+
flat.push(m);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const deduped = [];
|
|
65
|
+
for (const m of flat) {
|
|
66
|
+
if (!deduped.some((d) => typeValueEquals(d, m))) {
|
|
67
|
+
deduped.push(m);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (deduped.length === 0) return T.never;
|
|
71
|
+
if (deduped.length === 1) return deduped[0];
|
|
72
|
+
if (deduped.some((m) => m.kind === "unknown")) return T.unknown;
|
|
73
|
+
return { kind: "union", members: deduped };
|
|
74
|
+
}
|
|
75
|
+
function widenLiteral(tv) {
|
|
76
|
+
if (tv.kind !== "literal") return tv;
|
|
77
|
+
const v = tv.value;
|
|
78
|
+
if (typeof v === "number") return T.number;
|
|
79
|
+
if (typeof v === "string") return T.string;
|
|
80
|
+
if (typeof v === "boolean") return T.boolean;
|
|
81
|
+
if (v === null) return T.null;
|
|
82
|
+
if (v === void 0) return T.undefined;
|
|
83
|
+
return T.unknown;
|
|
84
|
+
}
|
|
85
|
+
function isSubtypeOf(a, b) {
|
|
86
|
+
if (b.kind === "unknown") return true;
|
|
87
|
+
if (a.kind === "never") return true;
|
|
88
|
+
if (typeValueEquals(a, b)) return true;
|
|
89
|
+
if (a.kind === "literal" && b.kind === "primitive") {
|
|
90
|
+
const v = a.value;
|
|
91
|
+
if (b.type === "number" && typeof v === "number") return true;
|
|
92
|
+
if (b.type === "string" && typeof v === "string") return true;
|
|
93
|
+
if (b.type === "boolean" && typeof v === "boolean") return true;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (a.kind === "object" && b.kind === "object") {
|
|
97
|
+
return Object.entries(b.properties).every(
|
|
98
|
+
([k, bv]) => k in a.properties && isSubtypeOf(a.properties[k], bv)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (a.kind === "array" && b.kind === "array") {
|
|
102
|
+
return isSubtypeOf(a.element, b.element);
|
|
103
|
+
}
|
|
104
|
+
if (a.kind === "tuple" && b.kind === "tuple") {
|
|
105
|
+
return a.elements.length === b.elements.length && a.elements.every((el, i) => isSubtypeOf(el, b.elements[i]));
|
|
106
|
+
}
|
|
107
|
+
if (a.kind === "tuple" && b.kind === "array") {
|
|
108
|
+
return a.elements.every((el) => isSubtypeOf(el, b.element));
|
|
109
|
+
}
|
|
110
|
+
if (a.kind === "promise" && b.kind === "promise") {
|
|
111
|
+
return isSubtypeOf(a.value, b.value);
|
|
112
|
+
}
|
|
113
|
+
if (a.kind === "instance" && b.kind === "instance") {
|
|
114
|
+
return a.className === b.className || isErrorSubclass(a.className, b.className);
|
|
115
|
+
}
|
|
116
|
+
if (a.kind === "union") {
|
|
117
|
+
return a.members.every((m) => isSubtypeOf(m, b));
|
|
118
|
+
}
|
|
119
|
+
if (b.kind === "union") {
|
|
120
|
+
return b.members.some((m) => isSubtypeOf(a, m));
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
var errorHierarchy = {
|
|
125
|
+
TypeError: "Error",
|
|
126
|
+
SyntaxError: "Error",
|
|
127
|
+
RangeError: "Error",
|
|
128
|
+
ReferenceError: "Error",
|
|
129
|
+
URIError: "Error",
|
|
130
|
+
EvalError: "Error"
|
|
131
|
+
};
|
|
132
|
+
function isErrorSubclass(child, parent) {
|
|
133
|
+
if (child === parent) return true;
|
|
134
|
+
const sup = errorHierarchy[child];
|
|
135
|
+
return sup ? isErrorSubclass(sup, parent) : false;
|
|
136
|
+
}
|
|
137
|
+
function deepCloneTypeValue(tv, idMap) {
|
|
138
|
+
const map = idMap ?? /* @__PURE__ */ new Map();
|
|
139
|
+
if (tv.kind === "object") {
|
|
140
|
+
let newId = map.get(tv.id);
|
|
141
|
+
if (!newId) {
|
|
142
|
+
newId = Symbol("object");
|
|
143
|
+
map.set(tv.id, newId);
|
|
144
|
+
}
|
|
145
|
+
const newProps = {};
|
|
146
|
+
for (const [k, v] of Object.entries(tv.properties)) {
|
|
147
|
+
newProps[k] = deepCloneTypeValue(v, map);
|
|
148
|
+
}
|
|
149
|
+
return { kind: "object", properties: newProps, id: newId };
|
|
150
|
+
}
|
|
151
|
+
if (tv.kind === "array") {
|
|
152
|
+
return { kind: "array", element: deepCloneTypeValue(tv.element, map) };
|
|
153
|
+
}
|
|
154
|
+
if (tv.kind === "tuple") {
|
|
155
|
+
return { kind: "tuple", elements: tv.elements.map((e) => deepCloneTypeValue(e, map)) };
|
|
156
|
+
}
|
|
157
|
+
if (tv.kind === "promise") {
|
|
158
|
+
return { kind: "promise", value: deepCloneTypeValue(tv.value, map) };
|
|
159
|
+
}
|
|
160
|
+
if (tv.kind === "instance") {
|
|
161
|
+
const newProps = {};
|
|
162
|
+
for (const [k, v] of Object.entries(tv.properties)) {
|
|
163
|
+
newProps[k] = deepCloneTypeValue(v, map);
|
|
164
|
+
}
|
|
165
|
+
return { kind: "instance", className: tv.className, properties: newProps };
|
|
166
|
+
}
|
|
167
|
+
if (tv.kind === "union") {
|
|
168
|
+
return simplifyUnion(tv.members.map((m) => deepCloneTypeValue(m, map)));
|
|
169
|
+
}
|
|
170
|
+
return tv;
|
|
171
|
+
}
|
|
172
|
+
function mergeObjectProperties(a, b) {
|
|
173
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(a.properties), ...Object.keys(b.properties)]);
|
|
174
|
+
const merged = {};
|
|
175
|
+
for (const k of allKeys) {
|
|
176
|
+
const av = a.properties[k];
|
|
177
|
+
const bv = b.properties[k];
|
|
178
|
+
if (av && bv) {
|
|
179
|
+
merged[k] = simplifyUnion([av, bv]);
|
|
180
|
+
} else {
|
|
181
|
+
merged[k] = av ?? bv;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { kind: "object", properties: merged, id: a.id };
|
|
185
|
+
}
|
|
186
|
+
function typeValueToString(tv) {
|
|
187
|
+
switch (tv.kind) {
|
|
188
|
+
case "literal": {
|
|
189
|
+
const v = tv.value;
|
|
190
|
+
if (v === null) return "null";
|
|
191
|
+
if (v === void 0) return "undefined";
|
|
192
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
193
|
+
return String(v);
|
|
194
|
+
}
|
|
195
|
+
case "primitive":
|
|
196
|
+
return tv.type;
|
|
197
|
+
case "object": {
|
|
198
|
+
const entries = Object.entries(tv.properties);
|
|
199
|
+
if (entries.length === 0) return "{}";
|
|
200
|
+
const inner = entries.map(([k, v]) => `${k}: ${typeValueToString(v)}`).join(", ");
|
|
201
|
+
return `{ ${inner} }`;
|
|
202
|
+
}
|
|
203
|
+
case "array":
|
|
204
|
+
return `${typeValueToString(tv.element)}[]`;
|
|
205
|
+
case "tuple": {
|
|
206
|
+
const inner = tv.elements.map(typeValueToString).join(", ");
|
|
207
|
+
return `[${inner}]`;
|
|
208
|
+
}
|
|
209
|
+
case "function": {
|
|
210
|
+
const params = tv.params.join(", ");
|
|
211
|
+
return `(${params}) => ...`;
|
|
212
|
+
}
|
|
213
|
+
case "promise":
|
|
214
|
+
return `Promise<${typeValueToString(tv.value)}>`;
|
|
215
|
+
case "instance": {
|
|
216
|
+
const entries = Object.entries(tv.properties);
|
|
217
|
+
if (entries.length === 0) return tv.className;
|
|
218
|
+
const inner = entries.map(([k, v]) => `${k}: ${typeValueToString(v)}`).join(", ");
|
|
219
|
+
return `${tv.className} { ${inner} }`;
|
|
220
|
+
}
|
|
221
|
+
case "union": {
|
|
222
|
+
return tv.members.map(typeValueToString).join(" | ");
|
|
223
|
+
}
|
|
224
|
+
case "never":
|
|
225
|
+
return "never";
|
|
226
|
+
case "unknown":
|
|
227
|
+
return "unknown";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function narrowType(tv, predicate) {
|
|
231
|
+
if (tv.kind === "union") {
|
|
232
|
+
return simplifyUnion(tv.members.filter(predicate));
|
|
233
|
+
}
|
|
234
|
+
return predicate(tv) ? tv : T.never;
|
|
235
|
+
}
|
|
236
|
+
function subtractType(tv, predicate) {
|
|
237
|
+
return narrowType(tv, (m) => !predicate(m));
|
|
238
|
+
}
|
|
239
|
+
function getPrimitiveTypeOf(tv) {
|
|
240
|
+
if (tv.kind === "literal") {
|
|
241
|
+
const v = tv.value;
|
|
242
|
+
if (v === null) return "object";
|
|
243
|
+
return typeof v;
|
|
244
|
+
}
|
|
245
|
+
if (tv.kind === "primitive") return tv.type;
|
|
246
|
+
if (tv.kind === "object") return "object";
|
|
247
|
+
if (tv.kind === "array" || tv.kind === "tuple") return "object";
|
|
248
|
+
if (tv.kind === "function") return "function";
|
|
249
|
+
if (tv.kind === "promise") return "object";
|
|
250
|
+
if (tv.kind === "instance") return "object";
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/ops.ts
|
|
255
|
+
function bothLiteral(l, r) {
|
|
256
|
+
if (l.kind === "literal" && r.kind === "literal") {
|
|
257
|
+
return { lv: l.value, rv: r.value };
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
var Ops = {
|
|
262
|
+
add(left, right) {
|
|
263
|
+
const lit = bothLiteral(left, right);
|
|
264
|
+
if (lit) {
|
|
265
|
+
return T.literal(lit.lv + lit.rv);
|
|
266
|
+
}
|
|
267
|
+
if (isSubtypeOf(left, T.string) || isSubtypeOf(right, T.string)) {
|
|
268
|
+
return T.string;
|
|
269
|
+
}
|
|
270
|
+
if (isSubtypeOf(left, T.number) && isSubtypeOf(right, T.number)) {
|
|
271
|
+
return T.number;
|
|
272
|
+
}
|
|
273
|
+
return T.union(T.number, T.string);
|
|
274
|
+
},
|
|
275
|
+
sub(left, right) {
|
|
276
|
+
const lit = bothLiteral(left, right);
|
|
277
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
278
|
+
return T.literal(lit.lv - lit.rv);
|
|
279
|
+
}
|
|
280
|
+
return T.number;
|
|
281
|
+
},
|
|
282
|
+
mul(left, right) {
|
|
283
|
+
const lit = bothLiteral(left, right);
|
|
284
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
285
|
+
return T.literal(lit.lv * lit.rv);
|
|
286
|
+
}
|
|
287
|
+
return T.number;
|
|
288
|
+
},
|
|
289
|
+
div(left, right) {
|
|
290
|
+
const lit = bothLiteral(left, right);
|
|
291
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
292
|
+
return T.literal(lit.lv / lit.rv);
|
|
293
|
+
}
|
|
294
|
+
return T.number;
|
|
295
|
+
},
|
|
296
|
+
mod(left, right) {
|
|
297
|
+
const lit = bothLiteral(left, right);
|
|
298
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
299
|
+
return T.literal(lit.lv % lit.rv);
|
|
300
|
+
}
|
|
301
|
+
return T.number;
|
|
302
|
+
},
|
|
303
|
+
strictEq(left, right) {
|
|
304
|
+
const lit = bothLiteral(left, right);
|
|
305
|
+
if (lit) return T.literal(lit.lv === lit.rv);
|
|
306
|
+
return T.boolean;
|
|
307
|
+
},
|
|
308
|
+
strictNeq(left, right) {
|
|
309
|
+
const lit = bothLiteral(left, right);
|
|
310
|
+
if (lit) return T.literal(lit.lv !== lit.rv);
|
|
311
|
+
return T.boolean;
|
|
312
|
+
},
|
|
313
|
+
gt(left, right) {
|
|
314
|
+
const lit = bothLiteral(left, right);
|
|
315
|
+
if (lit) return T.literal(lit.lv > lit.rv);
|
|
316
|
+
return T.boolean;
|
|
317
|
+
},
|
|
318
|
+
lt(left, right) {
|
|
319
|
+
const lit = bothLiteral(left, right);
|
|
320
|
+
if (lit) return T.literal(lit.lv < lit.rv);
|
|
321
|
+
return T.boolean;
|
|
322
|
+
},
|
|
323
|
+
gte(left, right) {
|
|
324
|
+
const lit = bothLiteral(left, right);
|
|
325
|
+
if (lit) return T.literal(lit.lv >= lit.rv);
|
|
326
|
+
return T.boolean;
|
|
327
|
+
},
|
|
328
|
+
lte(left, right) {
|
|
329
|
+
const lit = bothLiteral(left, right);
|
|
330
|
+
if (lit) return T.literal(lit.lv <= lit.rv);
|
|
331
|
+
return T.boolean;
|
|
332
|
+
},
|
|
333
|
+
typeof_(operand) {
|
|
334
|
+
if (operand.kind === "literal") {
|
|
335
|
+
const v = operand.value;
|
|
336
|
+
if (v === null) return T.literal("object");
|
|
337
|
+
return T.literal(typeof v);
|
|
338
|
+
}
|
|
339
|
+
if (operand.kind === "primitive") return T.literal(operand.type);
|
|
340
|
+
if (operand.kind === "object") return T.literal("object");
|
|
341
|
+
if (operand.kind === "array" || operand.kind === "tuple")
|
|
342
|
+
return T.literal("object");
|
|
343
|
+
if (operand.kind === "function") return T.literal("function");
|
|
344
|
+
if (operand.kind === "promise") return T.literal("object");
|
|
345
|
+
if (operand.kind === "instance") return T.literal("object");
|
|
346
|
+
return T.string;
|
|
347
|
+
},
|
|
348
|
+
not(operand) {
|
|
349
|
+
if (operand.kind === "literal") return T.literal(!operand.value);
|
|
350
|
+
return T.boolean;
|
|
351
|
+
},
|
|
352
|
+
neg(operand) {
|
|
353
|
+
if (operand.kind === "literal" && typeof operand.value === "number") {
|
|
354
|
+
return T.literal(-operand.value);
|
|
355
|
+
}
|
|
356
|
+
return T.number;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var binaryOpMap = {
|
|
360
|
+
"+": Ops.add,
|
|
361
|
+
"-": Ops.sub,
|
|
362
|
+
"*": Ops.mul,
|
|
363
|
+
"/": Ops.div,
|
|
364
|
+
"%": Ops.mod,
|
|
365
|
+
"===": Ops.strictEq,
|
|
366
|
+
"!==": Ops.strictNeq,
|
|
367
|
+
">": Ops.gt,
|
|
368
|
+
"<": Ops.lt,
|
|
369
|
+
">=": Ops.gte,
|
|
370
|
+
"<=": Ops.lte
|
|
371
|
+
};
|
|
372
|
+
function applyBinaryOp(op, left, right) {
|
|
373
|
+
const fn = binaryOpMap[op];
|
|
374
|
+
if (!fn) return T.unknown;
|
|
375
|
+
return fn(left, right);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/environment.ts
|
|
379
|
+
function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
|
|
380
|
+
const store = new Map(bindings);
|
|
381
|
+
const env = {
|
|
382
|
+
lookup(name) {
|
|
383
|
+
const val = store.get(name);
|
|
384
|
+
if (val !== void 0) return val;
|
|
385
|
+
if (parent) return parent.lookup(name);
|
|
386
|
+
return T.undefined;
|
|
387
|
+
},
|
|
388
|
+
bind(name, value) {
|
|
389
|
+
store.set(name, value);
|
|
390
|
+
return env;
|
|
391
|
+
},
|
|
392
|
+
update(name, value) {
|
|
393
|
+
if (store.has(name)) {
|
|
394
|
+
store.set(name, value);
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
if (parent) return parent.update(name, value);
|
|
398
|
+
return false;
|
|
399
|
+
},
|
|
400
|
+
extend(newBindings) {
|
|
401
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
402
|
+
for (const [k, v] of Object.entries(newBindings)) {
|
|
403
|
+
childMap.set(k, v);
|
|
404
|
+
}
|
|
405
|
+
return createEnvironment(env, childMap);
|
|
406
|
+
},
|
|
407
|
+
has(name) {
|
|
408
|
+
return store.has(name) || (parent?.has(name) ?? false);
|
|
409
|
+
},
|
|
410
|
+
snapshot() {
|
|
411
|
+
const clonedStore = new Map(store);
|
|
412
|
+
const clonedParent = parent?.snapshot();
|
|
413
|
+
return createEnvironment(clonedParent, clonedStore);
|
|
414
|
+
},
|
|
415
|
+
getOwnBindings() {
|
|
416
|
+
const result = {};
|
|
417
|
+
for (const [k, v] of store) {
|
|
418
|
+
result[k] = v;
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
return env;
|
|
424
|
+
}
|
|
425
|
+
export {
|
|
426
|
+
Ops,
|
|
427
|
+
T,
|
|
428
|
+
applyBinaryOp,
|
|
429
|
+
createEnvironment,
|
|
430
|
+
deepCloneTypeValue,
|
|
431
|
+
getPrimitiveTypeOf,
|
|
432
|
+
isSubtypeOf,
|
|
433
|
+
mergeObjectProperties,
|
|
434
|
+
narrowType,
|
|
435
|
+
simplifyUnion,
|
|
436
|
+
subtractType,
|
|
437
|
+
typeValueEquals,
|
|
438
|
+
typeValueToString,
|
|
439
|
+
widenLiteral
|
|
440
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createEnvironment } from "../environment.ts";
|
|
3
|
+
import { T, typeValueEquals } from "../type-value.ts";
|
|
4
|
+
|
|
5
|
+
describe("Environment", () => {
|
|
6
|
+
it("binds and looks up values", () => {
|
|
7
|
+
const env = createEnvironment();
|
|
8
|
+
env.bind("x", T.literal(42));
|
|
9
|
+
expect(typeValueEquals(env.lookup("x"), T.literal(42))).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns undefined for unbound names", () => {
|
|
13
|
+
const env = createEnvironment();
|
|
14
|
+
expect(typeValueEquals(env.lookup("x"), T.undefined)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("extends with child scope", () => {
|
|
18
|
+
const parent = createEnvironment();
|
|
19
|
+
parent.bind("x", T.literal(1));
|
|
20
|
+
const child = parent.extend({ y: T.literal(2) });
|
|
21
|
+
expect(typeValueEquals(child.lookup("x"), T.literal(1))).toBe(true);
|
|
22
|
+
expect(typeValueEquals(child.lookup("y"), T.literal(2))).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("child binding shadows parent", () => {
|
|
26
|
+
const parent = createEnvironment();
|
|
27
|
+
parent.bind("x", T.literal(1));
|
|
28
|
+
const child = parent.extend({ x: T.literal(99) });
|
|
29
|
+
expect(typeValueEquals(child.lookup("x"), T.literal(99))).toBe(true);
|
|
30
|
+
expect(typeValueEquals(parent.lookup("x"), T.literal(1))).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("has checks existence", () => {
|
|
34
|
+
const env = createEnvironment();
|
|
35
|
+
expect(env.has("x")).toBe(false);
|
|
36
|
+
env.bind("x", T.number);
|
|
37
|
+
expect(env.has("x")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("snapshot creates independent copy", () => {
|
|
41
|
+
const env = createEnvironment();
|
|
42
|
+
env.bind("x", T.literal(1));
|
|
43
|
+
const snap = env.snapshot();
|
|
44
|
+
env.bind("x", T.literal(2));
|
|
45
|
+
expect(typeValueEquals(snap.lookup("x"), T.literal(1))).toBe(true);
|
|
46
|
+
expect(typeValueEquals(env.lookup("x"), T.literal(2))).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|