swift5: step5_tco
authorOleg Montak <montak.o@ya.ru>
Sun, 3 Nov 2019 13:14:05 +0000 (16:14 +0300)
committerOleg Montak <montak.o@ya.ru>
Fri, 8 Nov 2019 18:46:18 +0000 (21:46 +0300)
swift5/Package.swift
swift5/Sources/core/Errors.swift
swift5/Sources/core/Types.swift
swift5/Sources/step4_if_fn_do/main.swift
swift5/Sources/step5_tco/main.swift [new file with mode: 0644]

index fcd7e3a..00656ed 100644 (file)
@@ -11,7 +11,8 @@ let package = Package(
         .executable(name: "step1_read_print", targets: ["step1_read_print"]),
         .executable(name: "step2_eval", targets: ["step2_eval"]),
         .executable(name: "step3_env", targets: ["step3_env"]),
-        .executable(name: "step4_if_fn_do", targets: ["step4_if_fn_do"])
+        .executable(name: "step4_if_fn_do", targets: ["step4_if_fn_do"]),
+        .executable(name: "step5_tco", targets: ["step5_tco"])
     ],
     dependencies: [
         // Dependencies declare other packages that this package depends on.
@@ -25,6 +26,7 @@ let package = Package(
         .target(name: "step1_read_print", dependencies: ["core"]),
         .target(name: "step2_eval", dependencies: ["core"]),
         .target(name: "step3_env", dependencies: ["core"]),
-        .target(name: "step4_if_fn_do", dependencies: ["core"])
+        .target(name: "step4_if_fn_do", dependencies: ["core"]),
+        .target(name: "step5_tco", dependencies: ["core"])
     ]
 )
index bf63224..750cb2f 100644 (file)
@@ -29,4 +29,8 @@ extension MalError {
     public static func unbalanced(unexpected: String) -> MalError {
         return MalError("unbalanced: unexpected \(unexpected)")
     }
+
+    public static func invalidArguments(_ name: String) -> MalError {
+        return MalError("\(name): invalid arguments")
+    }
 }
index ccb32ae..09a0bf5 100644 (file)
@@ -43,16 +43,27 @@ extension Expr: Equatable {
     }
 }
 
-public struct Func {
+public class Func {
     public let run: ([Expr]) throws -> Expr
+    public let ast: Expr?
+    public let params: [String]
+    public let env: Env?
 
-    public init(run: @escaping ([Expr]) throws -> Expr) {
+    public init(
+        ast: Expr? = nil,
+        params: [String] = [],
+        env: Env? = nil,
+        run: @escaping ([Expr]) throws -> Expr
+    ) {
         self.run = run
+        self.ast = ast
+        self.params = params
+        self.env = env
     }
 }
 
 extension Func: Equatable {
-    public static func == (lhs: Self, rhs: Self) -> Bool {
-        return false
+    public static func == (lhs: Func, rhs: Func) -> Bool {
+        return lhs === rhs
     }
 }
dissimilarity index 77%
index 005e955..2fac69a 100644 (file)
-import Foundation
-import core
-
-func read(_ s: String) throws -> Expr {
-    return try Reader.read(s)
-}
-
-func eval(_ expr: Expr, env: Env) throws -> Expr {
-    switch expr {
-    case let .symbol(name):
-        let value = try env.get(name)
-        return value
-    case let .vector(values):
-        return .vector(try values.map { try eval($0, env: env) })
-    case let .hashmap(values):
-        return .hashmap(try values.mapValues { try eval($0, env: env) })
-    case .list:
-        return try eval_list(expr, env: env)
-    default:
-        return expr
-    }
-}
-
-private func isTaggedList(_ ast: [Expr], tagName: String) -> Bool {
-    if case let .symbol(name) = ast.first, name == tagName {
-        return true
-    }
-    return false
-}
-
-private func isDefinition(_ ast: [Expr]) -> Bool {
-    return isTaggedList(ast, tagName: "def!")
-}
-
-private func evalDefinition(_ ast: [Expr], env: Env) throws -> Expr {
-    guard ast.count == 3 else { throw MalError("def!: invalid arguments") }
-    guard case let .symbol(name) = ast[1] else { throw MalError("def!: invalid arguments") }
-
-    let expToEval = ast[2]
-    let val = try eval(expToEval, env: env)
-    env.set(forKey: name, val: val)
-    return val
-}
-
-private func isLetForm(_ ast: [Expr]) -> Bool {
-    return isTaggedList(ast, tagName: "let*")
-}
-
-private func evalLetForm(_ ast: [Expr], env: Env) throws -> Expr {
-    guard ast.count == 3 else { throw MalError("let*: invalid arguments") }
-
-    switch ast[1] {
-    case let .list(bindable), let .vector(bindable):
-        let letEnv = Env(outer: env)
-
-        for i in stride(from: 0, to: bindable.count - 1, by: 2) {
-            guard case let .symbol(key) = bindable[i] else { throw MalError("let*: invalid arguments") }
-            let value = bindable[i + 1]
-            letEnv.set(forKey: key, val: try eval(value, env: letEnv))
-        }
-
-        let expToEval = ast[2]
-        return try eval(expToEval, env: letEnv)
-    default:
-        throw MalError("let*: invalid arguments")
-    }
-}
-
-private func isDoForm(_ ast: [Expr]) -> Bool {
-    return isTaggedList(ast, tagName: "do")
-}
-
-private func evalDoForm(_ ast: [Expr], env: Env) throws -> Expr {
-    let exprsToEval = ast.dropFirst()
-    if exprsToEval.isEmpty { throw MalError("do: invalid arguments") }
-
-    return try exprsToEval.map { try eval($0, env: env) }.last!
-}
-
-private func isIfForm(_ ast: [Expr]) -> Bool {
-    return isTaggedList(ast, tagName: "if")
-}
-
-private func evalIfForm(_ ast: [Expr], env: Env) throws -> Expr {
-    guard 3...4 ~= ast.count else { throw MalError("if: invalid arguments") }
-
-    let condExpr = ast[1]
-    switch try eval(condExpr, env: env) {
-    case .bool(false), .null:
-        if let falseExpr = ast[safe: 3] {
-            return try eval(falseExpr, env: env)
-        }
-        return .null
-    default:
-        return try eval(ast[2], env: env)
-    }
-}
-
-private func isFnForm(_ values: [Expr]) -> Bool {
-    return isTaggedList(values, tagName: "fn*")
-}
-
-private func evalFnForm(_ ast: [Expr], env: Env) throws -> Expr {
-    guard ast.count == 3 else { throw MalError("fn*: invalid arguments") }
-    let binds: [String]
-    switch ast[1] {
-    case let .list(xs), let .vector(xs):
-        binds = try xs.map {
-            guard case let .symbol(name) = $0 else { throw MalError("fn*: invalid arguments") }
-            return name
-        }
-    default:
-        throw MalError("fn*: invalid arguments")
-    }
-
-    let f = Func { args in
-        let fEnv = try Env(binds: binds, exprs: args, outer: env)
-        return try eval(ast[2], env: fEnv)
-    }
-    return .function(f)
-}
-
-func eval_list(_ expr: Expr, env: Env) throws -> Expr {
-    guard case let .list(ast) = expr else { fatalError() }
-
-    if ast.isEmpty {
-        return expr
-    }
-
-    if isDefinition(ast) {
-        return try evalDefinition(ast, env: env)
-    }
-
-    if isLetForm(ast) {
-        return try evalLetForm(ast, env: env)
-    }
-
-    if isDoForm(ast) {
-        return try evalDoForm(ast, env: env)
-    }
-
-    if isIfForm(ast) {
-        return try evalIfForm(ast, env: env)
-    }
-
-    if isFnForm(ast) {
-        return try evalFnForm(ast, env: env)
-    }
-
-    let evaluated = try ast.map { try eval($0, env: env) }
-    guard case let .function(fn) = evaluated.first else { throw MalError("not a function: \(evaluated.first!)") }
-    return try fn.run(Array(evaluated.dropFirst()))
-}
-
-func print(_ expr: Expr) -> String {
-    return Expr.print(expr)
-}
-
-func rep(_ s: String, env: Env) -> String {
-    do {
-        let expr = try read(s)
-        let resExpr = try eval(expr, env: env)
-        let resultStr = print(resExpr)
-        return resultStr
-    } catch {
-        return error.localizedDescription
-    }
-}
-
-let replEnv: Env = Env(data: Core.ns.data)
-
-_ = rep("(def! not (fn* (a) (if a false true)))", env: replEnv)
-
-while true {
-    print("user> ", terminator: "")
-    guard let s = readLine() else { break }
-    print(rep(s, env: replEnv))
-}
+import Foundation
+import core
+
+func read(_ s: String) throws -> Expr {
+    return try Reader.read(s)
+}
+
+private func evalAst(_ expr: Expr, env: Env) throws -> Expr {
+    switch expr {
+    case let .symbol(name):
+        return try env.get(name)
+    case let .vector(values):
+        return .vector(try values.map { try eval($0, env: env) })
+    case let .hashmap(values):
+        return .hashmap(try values.mapValues { try eval($0, env: env) })
+    case let .list(ast):
+        return .list(try ast.map { try eval($0, env: env) })
+    default:
+        return expr
+    }
+}
+
+func eval(_ expr: Expr, env: Env) throws -> Expr {
+
+    guard case let .list(ast) = expr else {
+        return try evalAst(expr, env: env)
+    }
+    if ast.isEmpty {
+        return expr
+    }
+
+    switch ast[0] {
+
+    case .symbol("def!"):
+        guard ast.count == 3 else { throw MalError.invalidArguments("def!") }
+        guard case let .symbol(name) = ast[1] else { throw MalError.invalidArguments("def!") }
+
+        let val = try eval(ast[2], env: env)
+        env.set(forKey: name, val: val)
+        return val
+
+    case .symbol("let*"):
+        guard ast.count == 3 else { throw MalError.invalidArguments("let*") }
+
+        switch ast[1] {
+        case let .list(bindable), let .vector(bindable):
+            let letEnv = Env(outer: env)
+
+            for i in stride(from: 0, to: bindable.count - 1, by: 2) {
+                guard case let .symbol(key) = bindable[i] else { throw MalError.invalidArguments("let*") }
+                let value = bindable[i + 1]
+                letEnv.set(forKey: key, val: try eval(value, env: letEnv))
+            }
+
+            let expToEval = ast[2]
+            return try eval(expToEval, env: letEnv)
+        default:
+            throw MalError.invalidArguments("let*")
+        }
+
+    case .symbol("do"):
+        let exprsToEval = ast.dropFirst()
+        if exprsToEval.isEmpty { throw MalError.invalidArguments("do") }
+        return try exprsToEval.map { try eval($0, env: env) }.last!
+
+    case .symbol("if"):
+        guard 3...4 ~= ast.count else { throw MalError.invalidArguments("if") }
+
+        let condExpr = ast[1]
+        switch try eval(condExpr, env: env) {
+        case .bool(false), .null:
+            if let falseExpr = ast[safe: 3] {
+                return try eval(falseExpr, env: env)
+            }
+            return .null
+        default:
+            return try eval(ast[2], env: env)
+        }
+
+    case .symbol("fn*"):
+        guard ast.count == 3 else { throw MalError("fn*") }
+        let binds: [String]
+        switch ast[1] {
+        case let .list(xs), let .vector(xs):
+            binds = try xs.map {
+                guard case let .symbol(name) = $0 else { throw MalError.invalidArguments("fn*") }
+                return name
+            }
+        default:
+            throw MalError.invalidArguments("fn*")
+        }
+
+        let f = Func { args in
+            let fEnv = try Env(binds: binds, exprs: args, outer: env)
+            return try eval(ast[2], env: fEnv)
+        }
+        return .function(f)
+
+    default:
+        guard case let .list(evaluatedList) = try evalAst(expr, env: env) else { fatalError() }
+        guard case let .function(fn) = evaluatedList[0] else { throw MalError("not a function: \(evaluatedList[0])") }
+        return try fn.run(Array(evaluatedList.dropFirst()))
+    }
+}
+
+func print(_ expr: Expr) -> String {
+    return Expr.print(expr)
+}
+
+func rep(_ s: String, env: Env) -> String {
+    do {
+        let expr = try read(s)
+        let resExpr = try eval(expr, env: env)
+        let resultStr = print(resExpr)
+        return resultStr
+    } catch {
+        return error.localizedDescription
+    }
+}
+
+let replEnv: Env = Env(data: Core.ns.data)
+
+_ = rep("(def! not (fn* (a) (if a false true)))", env: replEnv)
+
+while true {
+    print("user> ", terminator: "")
+    guard let s = readLine() else { break }
+    print(rep(s, env: replEnv))
+}
diff --git a/swift5/Sources/step5_tco/main.swift b/swift5/Sources/step5_tco/main.swift
new file mode 100644 (file)
index 0000000..fdf9c1e
--- /dev/null
@@ -0,0 +1,147 @@
+import Foundation
+import core
+
+func read(_ s: String) throws -> Expr {
+    return try Reader.read(s)
+}
+
+private func evalAst(_ expr: Expr, env: Env) throws -> Expr {
+    switch expr {
+    case let .symbol(name):
+        return try env.get(name)
+    case let .vector(values):
+        return .vector(try values.map { try eval($0, env: env) })
+    case let .hashmap(values):
+        return .hashmap(try values.mapValues { try eval($0, env: env) })
+    case let .list(ast):
+        return .list(try ast.map { try eval($0, env: env) })
+    default:
+        return expr
+    }
+}
+
+func eval(_ expr: Expr, env: Env) throws -> Expr {
+
+    var env = env
+    var expr = expr
+
+    while true {
+
+        guard case let .list(ast) = expr else {
+            return try evalAst(expr, env: env)
+        }
+        if ast.isEmpty {
+            return expr
+        }
+
+        switch ast[0] {
+
+        case .symbol("def!"):
+            guard ast.count == 3 else { throw MalError.invalidArguments("def!") }
+            guard case let .symbol(name) = ast[1] else { throw MalError.invalidArguments("def!") }
+
+            let val = try eval(ast[2], env: env)
+            env.set(forKey: name, val: val)
+            return val
+
+        case .symbol("let*"):
+            guard ast.count == 3 else { throw MalError.invalidArguments("let*") }
+
+            switch ast[1] {
+            case let .list(bindable), let .vector(bindable):
+                let letEnv = Env(outer: env)
+
+                for i in stride(from: 0, to: bindable.count - 1, by: 2) {
+                    guard case let .symbol(key) = bindable[i] else { throw MalError.invalidArguments("let*") }
+                    let value = bindable[i + 1]
+                    letEnv.set(forKey: key, val: try eval(value, env: letEnv))
+                }
+
+                expr = ast[2]
+                env = letEnv
+            default:
+                throw MalError.invalidArguments("let*")
+            }
+
+        case .symbol("do"):
+            let exprsToEval = ast.dropFirst()
+            guard !exprsToEval.isEmpty else { throw MalError.invalidArguments("do") }
+            _ = try exprsToEval.dropLast().map { try eval($0, env: env) }
+            expr = exprsToEval.last!
+
+        case .symbol("if"):
+            guard 3...4 ~= ast.count else { throw MalError.invalidArguments("if") }
+
+            switch try eval(ast[1], env: env) {
+            case .bool(false), .null:
+                if let falseBranch = ast[safe: 3] {
+                    expr = falseBranch
+                } else {
+                    expr = .null
+                }
+            default:
+                expr = ast[2]
+            }
+
+        case .symbol("fn*"):
+            guard ast.count == 3 else { throw MalError("fn*") }
+            let binds: [String]
+
+            switch ast[1] {
+            case let .list(xs), let .vector(xs):
+                binds = try xs.map {
+                    guard case let .symbol(name) = $0 else { throw MalError.invalidArguments("fn*") }
+                    return name
+                }
+            default:
+                throw MalError.invalidArguments("fn*")
+            }
+
+            let run: ([Expr]) throws -> Expr = { args in
+                let fEnv = try Env(binds: binds, exprs: args, outer: env)
+                return try eval(ast[2], env: fEnv)
+            }
+
+            let f = Func(ast: ast[2], params: binds, env: env, run: run)
+            return .function(f)
+
+        default:
+            guard case let .list(evaluatedList) = try evalAst(expr, env: env) else { fatalError() }
+            guard case let .function(fn) = evaluatedList[0] else { throw MalError("not a function: \(evaluatedList[0])") }
+
+            let args = Array(evaluatedList.dropFirst())
+            if let ast = fn.ast, let fnEnv = fn.env {
+                let newEnv = try Env(binds: fn.params, exprs: args, outer: fnEnv)
+                env = newEnv
+                expr = ast
+            } else {
+                return try fn.run(args)
+            }
+        }
+    }
+}
+
+func print(_ expr: Expr) -> String {
+    return Expr.print(expr)
+}
+
+func rep(_ s: String, env: Env) -> String {
+    do {
+        let expr = try read(s)
+        let resExpr = try eval(expr, env: env)
+        let resultStr = print(resExpr)
+        return resultStr
+    } catch {
+        return error.localizedDescription
+    }
+}
+
+let replEnv: Env = Env(data: Core.ns.data)
+
+_ = rep("(def! not (fn* (a) (if a false true)))", env: replEnv)
+
+while true {
+    print("user> ", terminator: "")
+    guard let s = readLine() else { break }
+    print(rep(s, env: replEnv))
+}