Change quasiquote algorithm
[jackhill/mal.git] / impls / ts / step8_macros.ts
index 537c419..e319ca5 100644 (file)
@@ -11,43 +11,49 @@ function read(str: string): MalType {
     return readStr(str);
 }
 
-function quasiquote(ast: MalType): MalType {
-    if (!isPair(ast)) {
-        return new MalList([MalSymbol.get("quote"), ast]);
-    }
-    if (!isSeq(ast)) {
-        throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`);
-    }
-    const [arg1, arg2] = ast.list;
-    if (arg1.type === Node.Symbol && arg1.v === "unquote") {
-        return arg2;
-    }
-    if (isPair(arg1)) {
-        if (!isSeq(arg1)) {
-            throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`);
-        }
-        const [arg11, arg12] = arg1.list;
-        if (arg11.type === Node.Symbol && arg11.v === "splice-unquote") {
-            return new MalList([
-                MalSymbol.get("concat"),
-                arg12,
-                quasiquote(new MalList(ast.list.slice(1))),
-            ]);
+function starts_with(lst: MalType[], sym: string): boolean {
+    if (lst.length == 2) {
+        let a0 = lst[0]
+        switch (a0.type) {
+            case Node.Symbol:
+                return a0.v === sym;
         }
     }
+    return false;
+}
 
-    return new MalList([
-        MalSymbol.get("cons"),
-        quasiquote(arg1),
-        quasiquote(new MalList(ast.list.slice(1))),
-    ]);
+function qq_loop(elt: MalType, acc: MalList): MalList {
+    if (elt.type == Node.List && starts_with(elt.list, "splice-unquote")) {
+        return new MalList([MalSymbol.get("concat"), elt.list[1], acc]);
+    } else {
+        return new MalList([MalSymbol.get("cons"), quasiquote(elt), acc]);
+    }
+}
 
-    function isPair(ast: MalType) {
-        if (!isSeq(ast)) {
-            return false;
-        }
+function qq_foldr(xs : MalType[]): MalList {
+    let acc = new MalList([])
+    for (let i=xs.length-1; 0<=i; i-=1) {
+         acc = qq_loop(xs[i], acc)
+    }
+    return acc;
+}
 
-        return 0 < ast.list.length;
+function quasiquote(ast: MalType): MalType {
+    switch (ast.type) {
+        case Node.Symbol:
+            return new MalList([MalSymbol.get("quote"), ast]);
+        case Node.HashMap:
+            return new MalList([MalSymbol.get("quote"), ast]);
+        case Node.List:
+            if (starts_with(ast.list, "unquote")) {
+                return ast.list[1];
+            } else {
+                return qq_foldr(ast.list);
+            }
+        case Node.Vector:
+            return new MalList([MalSymbol.get("vec"), qq_foldr(ast.list)]);
+        default:
+            return ast;
     }
 }
 
@@ -171,6 +177,9 @@ function evalMal(ast: MalType, env: Env): MalType {
                     case "quote": {
                         return ast.list[1];
                     }
+                    case "quasiquoteexpand": {
+                        return quasiquote(ast.list[1]);
+                    }
                     case "quasiquote": {
                         ast = quasiquote(ast.list[1]);
                         continue loop;