Merge pull request #378 from asarhaddon/test-macro-not-changing-function
[jackhill/mal.git] / ts / step4_if_fn_do.ts
index 5863e71..fd42ed7 100644 (file)
@@ -1,32 +1,33 @@
 import { readline } from "./node_readline";
 
-import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types";
+import { Node, MalType, MalNil, MalList, MalVector, MalHashMap, MalFunction, isAST, isSeq } from "./types";
 import { Env } from "./env";
 import * as core from "./core";
 import { readStr } from "./reader";
 import { prStr } from "./printer";
 
+// READ
 function read(str: string): MalType {
     return readStr(str);
 }
 
 function evalAST(ast: MalType, env: Env): MalType {
     switch (ast.type) {
-        case "symbol":
+        case Node.Symbol:
             const f = env.get(ast);
             if (!f) {
                 throw new Error(`unknown symbol: ${ast.v}`);
             }
             return f;
-        case "list":
-            return new MalList(ast.list.map(ast => evalSexp(ast, env)));
-        case "vector":
-            return new MalVector(ast.list.map(ast => evalSexp(ast, env)));
-        case "hash-map":
+        case Node.List:
+            return new MalList(ast.list.map(ast => evalMal(ast, env)));
+        case Node.Vector:
+            return new MalVector(ast.list.map(ast => evalMal(ast, env)));
+        case Node.HashMap:
             const list: MalType[] = [];
             for (const [key, value] of ast.entries()) {
                 list.push(key);
-                list.push(evalSexp(value, env));
+                list.push(evalMal(value, env));
             }
             return new MalHashMap(list);
         default:
@@ -34,8 +35,9 @@ function evalAST(ast: MalType, env: Env): MalType {
     }
 }
 
-function evalSexp(ast: MalType, env: Env): MalType {
-    if (ast.type !== "list") {
+// EVAL
+function evalMal(ast: MalType, env: Env): MalType {
+    if (ast.type !== Node.List) {
         return evalAST(ast, env);
     }
     if (ast.list.length === 0) {
@@ -43,107 +45,109 @@ function evalSexp(ast: MalType, env: Env): MalType {
     }
     const first = ast.list[0];
     switch (first.type) {
-        case "symbol":
+        case Node.Symbol:
             switch (first.v) {
                 case "def!": {
                     const [, key, value] = ast.list;
-                    if (!MalSymbol.is(key)) {
+                    if (key.type !== Node.Symbol) {
                         throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
                     }
                     if (!value) {
                         throw new Error(`unexpected syntax`);
                     }
-                    return env.set(key, evalSexp(value, env))
+                    return env.set(key, evalMal(value, env));
                 }
                 case "let*": {
                     let letEnv = new Env(env);
                     const pairs = ast.list[1];
-                    if (!MalList.is(pairs) && !MalVector.is(pairs)) {
+                    if (!isSeq(pairs)) {
                         throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`);
                     }
                     for (let i = 0; i < pairs.list.length; i += 2) {
                         const key = pairs.list[i];
                         const value = pairs.list[i + 1];
-                        if (!MalSymbol.is(key)) {
+                        if (key.type !== Node.Symbol) {
                             throw new Error(`unexpected token type: ${key.type}, expected: symbol`);
                         }
                         if (!key || !value) {
                             throw new Error(`unexpected syntax`);
                         }
 
-                        letEnv.set(key, evalSexp(value, letEnv));
+                        letEnv.set(key, evalMal(value, letEnv));
                     }
-                    return evalSexp(ast.list[2], letEnv);
+                    return evalMal(ast.list[2], letEnv);
                 }
                 case "do": {
                     const [, ...list] = ast.list;
                     const ret = evalAST(new MalList(list), env);
-                    if (!MalList.is(ret) && !MalVector.is(ret)) {
+                    if (!isSeq(ret)) {
                         throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`);
                     }
                     return ret.list[ret.list.length - 1];
                 }
                 case "if": {
                     const [, cond, thenExpr, elseExrp] = ast.list;
-                    const ret = evalSexp(cond, env);
+                    const ret = evalMal(cond, env);
                     let b = true;
-                    if (MalBoolean.is(ret) && !ret.v) {
+                    if (ret.type === Node.Boolean && !ret.v) {
                         b = false;
-                    } else if (MalNull.is(ret)) {
+                    } else if (ret.type === Node.Nil) {
                         b = false;
                     }
                     if (b) {
-                        return evalSexp(thenExpr, env);
+                        return evalMal(thenExpr, env);
                     } else if (elseExrp) {
-                        return evalSexp(elseExrp, env);
+                        return evalMal(elseExrp, env);
                     } else {
-                        return MalNull.instance;
+                        return MalNil.instance;
                     }
                 }
                 case "fn*": {
                     const [, args, binds] = ast.list;
-                    if (!MalList.is(args) && !MalVector.is(args)) {
+                    if (!isSeq(args)) {
                         throw new Error(`unexpected return type: ${args.type}, expected: list or vector`);
                     }
-                    const symbols = args.list.map(arg => {
-                        if (!MalSymbol.is(arg)) {
-                            throw new Error(`unexpected return type: ${arg.type}, expected: symbol`);
+                    const symbols = args.list.map(param => {
+                        if (param.type !== Node.Symbol) {
+                            throw new Error(`unexpected return type: ${param.type}, expected: symbol`);
                         }
-                        return arg;
+                        return param;
                     });
                     return MalFunction.fromBootstrap((...fnArgs: MalType[]) => {
-                        return evalSexp(binds, new Env(env, symbols, fnArgs));
+                        return evalMal(binds, new Env(env, symbols, fnArgs));
                     });
                 }
             }
     }
     const result = evalAST(ast, env);
-    if (!MalList.is(result) && !MalVector.is(result)) {
+    if (!isSeq(result)) {
         throw new Error(`unexpected return type: ${result.type}, expected: list or vector`);
     }
     const [f, ...args] = result.list;
-    if (!MalFunction.is(f)) {
+    if (f.type !== Node.Function) {
         throw new Error(`unexpected token: ${f.type}, expected: function`);
     }
     return f.func(...args);
 }
 
+// PRINT
 function print(exp: MalType): string {
     return prStr(exp);
 }
 
 const replEnv = new Env();
-for (const [key, value] of core.ns) {
-    replEnv.set(key, value);
+function rep(str: string): string {
+    return print(evalMal(read(str), replEnv));
 }
 
+// core.EXT: defined using Racket
+core.ns.forEach((value, key) => {
+    replEnv.set(key, value);
+});
+
 // core.mal: defined using the language itself
 rep("(def! not (fn* (a) (if a false true)))");
 
-function rep(str: string): string {
-    return print(evalSexp(read(str), replEnv));
-}
-
 while (true) {
     const line = readline("user> ");
     if (line == null) {
@@ -155,7 +159,11 @@ while (true) {
     try {
         console.log(rep(line));
     } catch (e) {
-        const err: Error = e;
-        console.error(err.message);
+        if (isAST(e)) {
+            console.error("Error:", prStr(e));
+        } else {
+            const err: Error = e;
+            console.error("Error:", err.message);
+        }
     }
 }