1 import { Env } from "./env";
3 export type MalType = MalList | MalNumber | MalString | MalNil | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom;
5 export const enum Node {
19 export function equals(a: MalType, b: MalType, strict?: boolean): boolean {
20 if (strict && a.type !== b.type) {
24 if (a.type === Node.Nil && b.type === Node.Nil) {
27 if (isSeq(a) && isSeq(b)) {
28 return listEquals(a.list, b.list);
30 if (a.type === Node.HashMap && b.type === Node.HashMap) {
31 if (a.keywordMap.size !== b.keywordMap.size) {
34 if (Object.keys(a.stringMap).length !== Object.keys(b.stringMap).length) {
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`);
42 if (aV.type === Node.Nil && bV.type === Node.Nil) {
45 if (!equals(aV, bV)) {
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)
64 function listEquals(a: MalType[], b: MalType[]): boolean {
65 if (a.length !== b.length) {
68 for (let i = 0; i < a.length; i++) {
69 if (!equals(a[i], b[i], strict)) {
77 export function isSeq(ast: MalType): ast is MalList | MalVector {
78 return ast.type === Node.List || ast.type === Node.Vector;
81 export function isAST(v: MalType): v is MalType {
85 export class MalList {
86 type: Node.List = Node.List;
89 constructor(public list: MalType[]) {
92 withMeta(meta: MalType) {
93 const v = new MalList(this.list);
99 export class MalNumber {
100 type: Node.Number = Node.Number;
103 constructor(public v: number) {
106 withMeta(meta: MalType) {
107 const v = new MalNumber(this.v);
113 export class MalString {
114 type: Node.String = Node.String;
117 constructor(public v: string) {
120 withMeta(meta: MalType) {
121 const v = new MalString(this.v);
127 export class MalNil {
129 private static _instance?: MalNil;
131 static get instance(): MalNil {
132 if (this._instance) {
133 return this._instance;
135 this._instance = new MalNil();
136 return this._instance;
139 type: Node.Nil = Node.Nil;
142 private constructor() { }
144 withMeta(_meta: MalType): MalNil {
145 throw new Error(`not supported`);
149 export class MalBoolean {
150 type: Node.Boolean = Node.Boolean;
153 constructor(public v: boolean) {
156 withMeta(meta: MalType) {
157 const v = new MalBoolean(this.v);
163 export class MalSymbol {
164 static map = new Map<symbol, MalSymbol>();
166 static get(name: string): MalSymbol {
167 const sym = Symbol.for(name);
168 let token = this.map.get(sym);
172 token = new MalSymbol(name);
173 this.map.set(sym, token);
177 type: Node.Symbol = Node.Symbol;
180 private constructor(public v: string) {
183 withMeta(_meta: MalType): MalSymbol {
184 throw new Error(`not supported`);
188 export class MalKeyword {
189 static map = new Map<symbol, MalKeyword>();
191 static get(name: string): MalKeyword {
192 const sym = Symbol.for(name);
193 let token = this.map.get(sym);
197 token = new MalKeyword(name);
198 this.map.set(sym, token);
202 type: Node.Keyword = Node.Keyword;
205 private constructor(public v: string) {
208 withMeta(_meta: MalType): MalKeyword {
209 throw new Error(`not supported`);
213 export class MalVector {
214 type: Node.Vector = Node.Vector;
217 constructor(public list: MalType[]) {
220 withMeta(meta: MalType) {
221 const v = new MalVector(this.list);
227 export class MalHashMap {
228 type: Node.HashMap = Node.HashMap;
229 stringMap: { [key: string]: MalType } = {};
230 keywordMap = new Map<MalType, MalType>();
233 constructor(list: MalType[]) {
234 while (list.length !== 0) {
235 const key = list.shift()!;
236 const value = list.shift();
238 throw new Error("unexpected hash length");
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;
245 throw new Error(`unexpected key symbol: ${key.type}, expected: keyword or string`);
250 withMeta(meta: MalType) {
251 const v = this.assoc([]);
256 has(key: MalKeyword | MalString) {
257 if (key.type === Node.Keyword) {
258 return !!this.keywordMap.get(key);
260 return !!this.stringMap[key.v];
263 get(key: MalKeyword | MalString) {
264 if (key.type === Node.Keyword) {
265 return this.keywordMap.get(key) || MalNil.instance;
267 return this.stringMap[key.v] || MalNil.instance;
270 entries(): [MalType, MalType][] {
271 const list: [MalType, MalType][] = [];
273 this.keywordMap.forEach((v, k) => {
276 Object.keys(this.stringMap).forEach(v => list.push([new MalString(v), this.stringMap[v]]));
282 const list: MalType[] = [];
283 this.keywordMap.forEach((_v, k) => {
286 Object.keys(this.stringMap).forEach(v => list.push(new MalString(v)));
291 const list: MalType[] = [];
292 this.keywordMap.forEach(v => {
295 Object.keys(this.stringMap).forEach(v => list.push(this.stringMap[v]));
299 assoc(args: MalType[]): MalHashMap {
300 const list: MalType[] = [];
301 this.keywordMap.forEach((value, key) => {
305 Object.keys(this.stringMap).forEach(keyStr => {
306 list.push(new MalString(keyStr));
307 list.push(this.stringMap[keyStr]);
310 return new MalHashMap(list.concat(args));
313 dissoc(args: MalType[]): MalHashMap {
314 const newHashMap = this.assoc([]);
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);
322 throw new Error(`unexpected symbol: ${arg.type}, expected: keyword or string`);
329 type MalF = (...args: (MalType | undefined)[]) => MalType;
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)));
342 function checkUndefined(args: (MalType | undefined)[]): MalType[] {
343 return args.map(arg => {
345 throw new Error(`undefined argument`);
352 static fromBootstrap(func: MalF): MalFunction {
353 const f = new MalFunction();
360 type: Node.Function = Node.Function;
368 private constructor() { }
370 withMeta(meta: MalType) {
371 const f = new MalFunction();
375 f.params = this.params;
376 f.isMacro = this.isMacro;
382 newEnv(args: MalType[]) {
383 return new Env(this.env, this.params, args);
387 export class MalAtom {
388 type: Node.Atom = Node.Atom;
391 constructor(public v: MalType) {
394 withMeta(meta: MalType) {
395 const v = new MalAtom(this.v);