Change quasiquote algorithm
[jackhill/mal.git] / impls / swift5 / Sources / step8_macros / main.swift
index a05f324..9998ab6 100644 (file)
@@ -5,56 +5,38 @@ func read(_ s: String) throws -> Expr {
     return try Reader.read(s)
 }
 
-private func isPair(_ expr: Expr) -> Bool {
-    switch expr {
-    case let .list(values, _), let .vector(values, _):
-        return !values.isEmpty
-    default:
-        return false
+private func qq_loop(_ elt: Expr, acc: Expr) throws -> Expr {
+    if case let .list(xs, _) = elt {
+        if 0 < xs.count && xs[0] == .symbol("splice-unquote") {
+            guard xs.count == 2 else { throw MalError.invalidArguments("splice-unquote") }
+            return .list([.symbol("concat"), xs[1], acc])
+        }
     }
+    return .list([.symbol("cons"), try quasiquote(elt), acc])
 }
-
-private func asListOrVector(_ expr: Expr) -> [Expr]? {
-    switch expr {
-    case let .list(values, _), let .vector(values, _):
-        return values
-    default:
-        return nil
+private func qq_foldr(_ xs: [Expr]) throws -> Expr {
+    var acc : Expr = .list([])
+    for i in stride(from: xs.count-1, through: 0, by: -1) {
+        acc = try qq_loop(xs[i], acc:acc)
     }
+    return acc
 }
-
 private func quasiquote(_ expr: Expr) throws -> Expr {
-    if !isPair(expr) {
-        return .list([.symbol("quote"), expr])
-    }
-    guard let ast = asListOrVector(expr), !ast.isEmpty else {
-        throw MalError.invalidArguments("quasiquote")
-    }
-
-    if case .symbol("unquote") = ast[0] {
-        guard ast.count > 1 else { throw MalError.invalidArguments("unquote") }
-        return ast[1]
-    }
-
-    if isPair(ast[0]), let ast0 = asListOrVector(ast[0]) {
-        if case .symbol("splice-unquote") = ast0.first {
-            guard ast0.count > 1 else { throw MalError.invalidArguments("splice-unquote") }
-            let rest = try quasiquote(.list(Array(ast[1...])))
-            return .list([.symbol("concat"), ast0[1], rest])
+    switch expr {
+    case let .list(xs, _):
+        if 0 < xs.count && xs[0] == .symbol("unquote") {
+            guard xs.count == 2 else { throw MalError.invalidArguments("unquote") }
+            return xs[1]
+        } else {
+            return try qq_foldr(xs)
         }
+    case let .vector(xs, _):
+        return .list([.symbol("vec"), try qq_foldr(xs)])
+    case .symbol(_), .hashmap(_):
+        return .list([.symbol("quote"), expr])
+    default:
+        return expr
     }
-
-    let rest = try quasiquote(.list(Array(ast[1...])))
-    return .list([.symbol("cons"), try quasiquote(ast[0]), rest])
-}
-
-private func isMacroCall(_ expr: Expr, env: Env) -> Bool {
-    if case let .list(ast, _) = expr,
-        case let .symbol(name) = ast.first,
-        case let .function(fn) = try? env.get(name) {
-            return fn.isMacro
-    }
-    return false
 }
 
 private func macroExpand(_ expr: Expr, env: Env) throws -> Expr {
@@ -137,6 +119,10 @@ func eval(_ expr: Expr, env: Env) throws -> Expr {
             guard ast.count == 2 else { throw MalError.invalidArguments("quote") }
             return ast[1]
 
+        case .symbol("quasiquoteexpand"):
+            guard ast.count == 2 else { throw MalError.invalidArguments("quasiquoteexpand") }
+            return try quasiquote(ast[1])
+
         case .symbol("quasiquote"):
             guard ast.count == 2 else { throw MalError.invalidArguments("quasiquote") }
             expr = try quasiquote(ast[1])