Move implementations into impls/ dir
[jackhill/mal.git] / impls / ts / types.ts
1 import { Env } from "./env";
2
3 export type MalType = MalList | MalNumber | MalString | MalNil | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom;
4
5 export const enum Node {
6 List = 1,
7 Number,
8 String,
9 Nil,
10 Boolean,
11 Symbol,
12 Keyword,
13 Vector,
14 HashMap,
15 Function,
16 Atom,
17 }
18
19 export function equals(a: MalType, b: MalType, strict?: boolean): boolean {
20 if (strict && a.type !== b.type) {
21 return false;
22 }
23
24 if (a.type === Node.Nil && b.type === Node.Nil) {
25 return true;
26 }
27 if (isSeq(a) && isSeq(b)) {
28 return listEquals(a.list, b.list);
29 }
30 if (a.type === Node.HashMap && b.type === Node.HashMap) {
31 if (a.keywordMap.size !== b.keywordMap.size) {
32 return false;
33 }
34 if (Object.keys(a.stringMap).length !== Object.keys(b.stringMap).length) {
35 return false;
36 }
37 for (const [aK, aV] of a.entries()) {
38 if (aK.type !== Node.String && aK.type !== Node.Keyword) {
39 throw new Error(`unexpected symbol: ${aK.type}, expected: string or keyword`);
40 }
41 const bV = b.get(aK);
42 if (aV.type === Node.Nil && bV.type === Node.Nil) {
43 continue;
44 }
45 if (!equals(aV, bV)) {
46 return false;
47 }
48 }
49
50 return true;
51 }
52 if (
53 (a.type === Node.Number && b.type === Node.Number)
54 || (a.type === Node.String && b.type === Node.String)
55 || (a.type === Node.Boolean && b.type === Node.Boolean)
56 || (a.type === Node.Symbol && b.type === Node.Symbol)
57 || (a.type === Node.Keyword && b.type === Node.Keyword)
58 ) {
59 return a.v === b.v;
60 }
61
62 return false;
63
64 function listEquals(a: MalType[], b: MalType[]): boolean {
65 if (a.length !== b.length) {
66 return false;
67 }
68 for (let i = 0; i < a.length; i++) {
69 if (!equals(a[i], b[i], strict)) {
70 return false;
71 }
72 }
73 return true;
74 }
75 }
76
77 export function isSeq(ast: MalType): ast is MalList | MalVector {
78 return ast.type === Node.List || ast.type === Node.Vector;
79 }
80
81 export function isAST(v: MalType): v is MalType {
82 return !!v.type;
83 }
84
85 export class MalList {
86 type: Node.List = Node.List;
87 meta?: MalType;
88
89 constructor(public list: MalType[]) {
90 }
91
92 withMeta(meta: MalType) {
93 const v = new MalList(this.list);
94 v.meta = meta;
95 return v;
96 }
97 }
98
99 export class MalNumber {
100 type: Node.Number = Node.Number;
101 meta?: MalType;
102
103 constructor(public v: number) {
104 }
105
106 withMeta(meta: MalType) {
107 const v = new MalNumber(this.v);
108 v.meta = meta;
109 return v;
110 }
111 }
112
113 export class MalString {
114 type: Node.String = Node.String;
115 meta?: MalType;
116
117 constructor(public v: string) {
118 }
119
120 withMeta(meta: MalType) {
121 const v = new MalString(this.v);
122 v.meta = meta;
123 return v;
124 }
125 }
126
127 export class MalNil {
128
129 private static _instance?: MalNil;
130
131 static get instance(): MalNil {
132 if (this._instance) {
133 return this._instance;
134 }
135 this._instance = new MalNil();
136 return this._instance;
137 }
138
139 type: Node.Nil = Node.Nil;
140 meta?: MalType;
141
142 private constructor() { }
143
144 withMeta(_meta: MalType): MalNil {
145 throw new Error(`not supported`);
146 }
147 }
148
149 export class MalBoolean {
150 type: Node.Boolean = Node.Boolean;
151 meta?: MalType;
152
153 constructor(public v: boolean) {
154 }
155
156 withMeta(meta: MalType) {
157 const v = new MalBoolean(this.v);
158 v.meta = meta;
159 return v;
160 }
161 }
162
163 export class MalSymbol {
164 static map = new Map<symbol, MalSymbol>();
165
166 static get(name: string): MalSymbol {
167 const sym = Symbol.for(name);
168 let token = this.map.get(sym);
169 if (token) {
170 return token;
171 }
172 token = new MalSymbol(name);
173 this.map.set(sym, token);
174 return token;
175 }
176
177 type: Node.Symbol = Node.Symbol;
178 meta?: MalType;
179
180 private constructor(public v: string) {
181 }
182
183 withMeta(_meta: MalType): MalSymbol {
184 throw new Error(`not supported`);
185 }
186 }
187
188 export class MalKeyword {
189 static map = new Map<symbol, MalKeyword>();
190
191 static get(name: string): MalKeyword {
192 const sym = Symbol.for(name);
193 let token = this.map.get(sym);
194 if (token) {
195 return token;
196 }
197 token = new MalKeyword(name);
198 this.map.set(sym, token);
199 return token;
200 }
201
202 type: Node.Keyword = Node.Keyword;
203 meta?: MalType;
204
205 private constructor(public v: string) {
206 }
207
208 withMeta(_meta: MalType): MalKeyword {
209 throw new Error(`not supported`);
210 }
211 }
212
213 export class MalVector {
214 type: Node.Vector = Node.Vector;
215 meta?: MalType;
216
217 constructor(public list: MalType[]) {
218 }
219
220 withMeta(meta: MalType) {
221 const v = new MalVector(this.list);
222 v.meta = meta;
223 return v;
224 }
225 }
226
227 export class MalHashMap {
228 type: Node.HashMap = Node.HashMap;
229 stringMap: { [key: string]: MalType } = {};
230 keywordMap = new Map<MalType, MalType>();
231 meta?: MalType;
232
233 constructor(list: MalType[]) {
234 while (list.length !== 0) {
235 const key = list.shift()!;
236 const value = list.shift();
237 if (value == null) {
238 throw new Error("unexpected hash length");
239 }
240 if (key.type === Node.Keyword) {
241 this.keywordMap.set(key, value);
242 } else if (key.type === Node.String) {
243 this.stringMap[key.v] = value;
244 } else {
245 throw new Error(`unexpected key symbol: ${key.type}, expected: keyword or string`);
246 }
247 }
248 }
249
250 withMeta(meta: MalType) {
251 const v = this.assoc([]);
252 v.meta = meta;
253 return v;
254 }
255
256 has(key: MalKeyword | MalString) {
257 if (key.type === Node.Keyword) {
258 return !!this.keywordMap.get(key);
259 }
260 return !!this.stringMap[key.v];
261 }
262
263 get(key: MalKeyword | MalString) {
264 if (key.type === Node.Keyword) {
265 return this.keywordMap.get(key) || MalNil.instance;
266 }
267 return this.stringMap[key.v] || MalNil.instance;
268 }
269
270 entries(): [MalType, MalType][] {
271 const list: [MalType, MalType][] = [];
272
273 this.keywordMap.forEach((v, k) => {
274 list.push([k, v]);
275 });
276 Object.keys(this.stringMap).forEach(v => list.push([new MalString(v), this.stringMap[v]]));
277
278 return list;
279 }
280
281 keys(): MalType[] {
282 const list: MalType[] = [];
283 this.keywordMap.forEach((_v, k) => {
284 list.push(k);
285 });
286 Object.keys(this.stringMap).forEach(v => list.push(new MalString(v)));
287 return list;
288 }
289
290 vals(): MalType[] {
291 const list: MalType[] = [];
292 this.keywordMap.forEach(v => {
293 list.push(v);
294 });
295 Object.keys(this.stringMap).forEach(v => list.push(this.stringMap[v]));
296 return list;
297 }
298
299 assoc(args: MalType[]): MalHashMap {
300 const list: MalType[] = [];
301 this.keywordMap.forEach((value, key) => {
302 list.push(key);
303 list.push(value);
304 });
305 Object.keys(this.stringMap).forEach(keyStr => {
306 list.push(new MalString(keyStr));
307 list.push(this.stringMap[keyStr]);
308 });
309
310 return new MalHashMap(list.concat(args));
311 }
312
313 dissoc(args: MalType[]): MalHashMap {
314 const newHashMap = this.assoc([]);
315
316 args.forEach(arg => {
317 if (arg.type === Node.String) {
318 delete newHashMap.stringMap[arg.v];
319 } else if (arg.type === Node.Keyword) {
320 newHashMap.keywordMap.delete(arg);
321 } else {
322 throw new Error(`unexpected symbol: ${arg.type}, expected: keyword or string`);
323 }
324 });
325 return newHashMap;
326 }
327 }
328
329 type MalF = (...args: (MalType | undefined)[]) => MalType;
330
331 export class MalFunction {
332 static fromLisp(evalMal: (ast: MalType, env: Env) => MalType, env: Env, params: MalSymbol[], bodyAst: MalType): MalFunction {
333 const f = new MalFunction();
334 f.func = (...args) => evalMal(bodyAst, new Env(env, params, checkUndefined(args)));
335 f.env = env;
336 f.params = params;
337 f.ast = bodyAst;
338 f.isMacro = false;
339
340 return f;
341
342 function checkUndefined(args: (MalType | undefined)[]): MalType[] {
343 return args.map(arg => {
344 if (!arg) {
345 throw new Error(`undefined argument`);
346 }
347 return arg;
348 });
349 }
350 }
351
352 static fromBootstrap(func: MalF): MalFunction {
353 const f = new MalFunction();
354 f.func = func;
355 f.isMacro = false;
356
357 return f;
358 }
359
360 type: Node.Function = Node.Function;
361 func: MalF;
362 ast: MalType;
363 env: Env;
364 params: MalSymbol[];
365 isMacro: boolean;
366 meta?: MalType;
367
368 private constructor() { }
369
370 withMeta(meta: MalType) {
371 const f = new MalFunction();
372 f.func = this.func;
373 f.ast = this.ast;
374 f.env = this.env;
375 f.params = this.params;
376 f.isMacro = this.isMacro;
377 f.meta = meta;
378
379 return f;
380 }
381
382 newEnv(args: MalType[]) {
383 return new Env(this.env, this.params, args);
384 }
385 }
386
387 export class MalAtom {
388 type: Node.Atom = Node.Atom;
389 meta?: MalType;
390
391 constructor(public v: MalType) {
392 }
393
394 withMeta(meta: MalType) {
395 const v = new MalAtom(this.v);
396 v.meta = meta;
397 return v;
398 }
399 }