swift5: step9_try
authorOleg Montak <montak.o@ya.ru>
Wed, 6 Nov 2019 17:01:08 +0000 (20:01 +0300)
committerOleg Montak <montak.o@ya.ru>
Fri, 8 Nov 2019 18:46:18 +0000 (21:46 +0300)
swift5/Package.swift
swift5/Sources/core/Core.swift
swift5/Sources/core/Env.swift
swift5/Sources/core/Errors.swift
swift5/Sources/step9_try/main.swift [new file with mode: 0644]

index ec03fde..fcb396e 100644 (file)
@@ -15,7 +15,8 @@ let package = Package(
         .executable(name: "step5_tco", targets: ["step5_tco"]),
         .executable(name: "step6_file", targets: ["step6_file"]),
         .executable(name: "step7_quote", targets: ["step7_quote"]),
-        .executable(name: "step8_macros", targets: ["step8_macros"])
+        .executable(name: "step8_macros", targets: ["step8_macros"]),
+        .executable(name: "step9_try", targets: ["step9_try"])
     ],
     dependencies: [
         // Dependencies declare other packages that this package depends on.
@@ -33,6 +34,7 @@ let package = Package(
         .target(name: "step5_tco", dependencies: ["core"]),
         .target(name: "step6_file", dependencies: ["core"]),
         .target(name: "step7_quote", dependencies: ["core"]),
-        .target(name: "step8_macros", dependencies: ["core"])
+        .target(name: "step8_macros", dependencies: ["core"]),
+        .target(name: "step9_try", dependencies: ["core"])
     ]
 )
index 95ecd79..a2b3b59 100644 (file)
@@ -1,6 +1,18 @@
 import Foundation
 
 private extension Func {
+    private static func hashMapDataFrom(_ args: [Expr]) throws -> [String: Expr] {
+        guard args.count.isMultiple(of: 2) else { throw MalError("invalid arguments") }
+
+        var data: [String: Expr] = [:]
+        for i in stride(from: 0, to: args.count - 1, by: 2) {
+            guard case let .string(key) = args[i] else { throw MalError("invalid arguments") }
+            let value = args[i + 1]
+            data[key] = value
+        }
+        return data
+    }
+
     static func infixOperation(_ op: @escaping (Int, Int) -> Int) -> Func {
         return Func { args in
             guard args.count == 2,
@@ -183,6 +195,180 @@ private extension Func {
             throw MalError.invalidArguments("rest")
         }
     }
+
+    static let `throw` = Func { args in
+        guard args.count > 0 else { throw MalError.invalidArguments("throw") }
+        throw args[0]
+    }
+
+    static let apply = Func { args in
+        guard args.count >= 2 else { throw MalError.invalidArguments("apply") }
+        guard case let .function(fn) = args[0] else { throw MalError.invalidArguments("apply") }
+
+        let lastArgs: [Expr]
+        switch args.last! {
+        case let .list(values), let .vector(values):
+            lastArgs = values
+        default:
+            throw MalError.invalidArguments("apply")
+        }
+
+
+        let fnArgs = Array(args.dropFirst().dropLast()) + lastArgs
+        return try fn.run(fnArgs)
+    }
+
+    static let map = Func { args in
+        guard args.count == 2 else { throw MalError.invalidArguments("map") }
+        guard case let .function(fn) = args[0] else { throw MalError.invalidArguments("map") }
+
+        switch args[1] {
+        case let .list(values), let .vector(values):
+            return .list(try values.map { try fn.run([$0]) })
+        default:
+            throw MalError.invalidArguments("map")
+        }
+    }
+
+    static let isNil = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("nil?") }
+        if case .null = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let isTrue = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("true?") }
+        if case .bool(true) = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let isFalse = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("false?") }
+        if case .bool(false) = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let isSymbol = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("symbol?") }
+        if case .symbol = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let symbol = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("symbol") }
+        guard case let .string(name) = args[0] else { throw MalError.invalidArguments("symbol") }
+        return .symbol(name)
+    }
+
+    static let keyword = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("keyword") }
+        guard case let .string(name) = args[0] else { throw MalError.invalidArguments("keyword") }
+        return name.first == keywordMagic
+            ? .string(name)
+            : .string(String(keywordMagic) + name)
+    }
+
+    static let isKeyword = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("keyword?") }
+        if case let .string(name) = args[0] {
+            return name.first == keywordMagic ? .bool(true) : .bool(false)
+        }
+        return .bool(false)
+    }
+
+    static let vector = Func { args in
+        return .vector(args)
+    }
+
+    static let isVector = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("vector?") }
+        if case .vector = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let isSequential = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("sequential?") }
+        switch args[0] {
+        case .list, .vector:
+            return .bool(true)
+        default:
+            return .bool(false)
+        }
+    }
+
+    static let hashmap = Func { args in
+        return .hashmap(try hashMapDataFrom(args))
+    }
+
+    static let isHashmap = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("map?") }
+        if case .hashmap = args[0] {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let assoc = Func { args in
+        guard args.count > 0 else { throw MalError.invalidArguments("assoc") }
+        guard case let .hashmap(data) = args[0] else { throw MalError.invalidArguments("assoc") }
+
+        let newData = try hashMapDataFrom(Array(args.dropFirst()))
+        return .hashmap(data.merging(newData, uniquingKeysWith: { _, new in new }))
+    }
+
+    static let dissoc = Func { args in
+        guard args.count > 0 else { throw MalError.invalidArguments("dissoc") }
+        guard case var .hashmap(data) = args[0] else { throw MalError.invalidArguments("dissoc") }
+
+        for key in args.dropFirst() {
+            guard case let .string(name) = key else { throw MalError.invalidArguments("dissoc") }
+            data.removeValue(forKey: name)
+        }
+        return .hashmap(data)
+    }
+
+    static let get = Func { args in
+        guard args.count == 2 else { throw MalError.invalidArguments("get") }
+        guard case let .string(key) = args[1] else { throw MalError.invalidArguments("get") }
+
+        switch args[0] {
+        case let .hashmap(data):
+            return data[key] ?? .null
+        case .null:
+            return .null
+        default:
+            throw MalError.invalidArguments("get")
+        }
+    }
+
+    static let contains = Func { args in
+        guard args.count == 2 else { throw MalError.invalidArguments("contains?") }
+        guard case let .hashmap(data) = args[0] else { throw MalError.invalidArguments("contains?") }
+        guard case let .string(key) = args[1] else { throw MalError.invalidArguments("contains?") }
+        return data.keys.contains(key) ? .bool(true) : .bool(false)
+    }
+
+    static let keys = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("keys") }
+        guard case let .hashmap(data) = args[0] else { throw MalError.invalidArguments("keys") }
+        return .list(data.keys.map(Expr.string))
+    }
+
+    static let vals = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("vals") }
+        guard case let .hashmap(data) = args[0] else { throw MalError.invalidArguments("vals") }
+        return .list(Array(data.values))
+    }
 }
 
 private let data: [String: Expr] = [
@@ -215,6 +401,27 @@ private let data: [String: Expr] = [
     "nth": .function(.nth),
     "first": .function(.first),
     "rest": .function(.rest),
+    "throw": .function(.throw),
+    "apply": .function(.apply),
+    "map": .function(.map),
+    "nil?": .function(.isNil),
+    "true?": .function(.isTrue),
+    "false?": .function(.isFalse),
+    "symbol?": .function(.isSymbol),
+    "symbol": .function(.symbol),
+    "keyword": .function(.keyword),
+    "keyword?": .function(.isKeyword),
+    "vector": .function(.vector),
+    "vector?": .function(.isVector),
+    "sequential?": .function(.isSequential),
+    "hash-map": .function(.hashmap),
+    "map?": .function(.isHashmap),
+    "assoc": .function(.assoc),
+    "dissoc": .function(.dissoc),
+    "get": .function(.get),
+    "contains?": .function(.contains),
+    "keys": .function(.keys),
+    "vals": .function(.vals)
 ]
 
 public enum Core {
index 3c70a0b..630a5a9 100644 (file)
@@ -30,7 +30,7 @@ public class Env {
     }
 
     public func get(_ key: String) throws -> Expr {
-        guard let val = find(key) else { throw MalError("\(key) not found") }
+        guard let val = find(key) else { throw MalError("'\(key)' not found") }
         return val
     }
 
index ee05dd1..ec5ce13 100644 (file)
@@ -38,3 +38,9 @@ extension MalError {
         return MalError("index out of range")
     }
 }
+
+extension Expr: Error, LocalizedError {
+    public var errorDescription: String? {
+        return "Error: \(self)"
+    }
+}
diff --git a/swift5/Sources/step9_try/main.swift b/swift5/Sources/step9_try/main.swift
new file mode 100644 (file)
index 0000000..ef9bdbf
--- /dev/null
@@ -0,0 +1,261 @@
+import Foundation
+import core
+
+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 asListOrVector(_ expr: Expr) -> [Expr]? {
+    switch expr {
+    case let .list(values), let .vector(values):
+        return values
+    default:
+        return nil
+    }
+}
+
+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])
+        }
+    }
+
+    let rest = try quasiquote(.list(Array(ast[1...])))
+    return .list([.symbol("cons"), try quasiquote(ast[0]), rest])
+}
+
+private func macroExpand(_ expr: Expr, env: Env) throws -> Expr {
+    var expr = expr
+    while true {
+        guard case let .list(ast) = expr,
+            case let .symbol(name) = ast.first,
+            case let .function(fn) = try? env.get(name),
+            fn.isMacro else {
+                break
+        }
+
+        expr = try fn.run(Array(ast.dropFirst()))
+    }
+    return expr
+}
+
+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 {
+
+        expr = try macroExpand(expr, env: env)
+
+        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("quote"):
+            guard ast.count == 2 else { throw MalError.invalidArguments("quote") }
+            return ast[1]
+
+        case .symbol("quasiquote"):
+            guard ast.count == 2 else { throw MalError.invalidArguments("quasiquote") }
+            expr = try quasiquote(ast[1])
+
+        case .symbol("defmacro!"):
+            guard ast.count == 3 else { throw MalError.invalidArguments("defmacro!") }
+            guard case let .symbol(name) = ast[1] else { throw MalError.invalidArguments("defmacro!") }
+
+            guard case let .function(val) = try eval(ast[2], env: env) else { throw MalError.invalidArguments("defmacro!") }
+            val.isMacro = true
+            env.set(forKey: name, val: .function(val))
+            return .function(val)
+
+        case .symbol("macroexpand"):
+            guard ast.count == 2 else { throw MalError.invalidArguments("macroexpand") }
+            return try macroExpand(ast[1], env: env)
+
+        case .symbol("try*"):
+            if ast.count == 2 {
+                expr = ast[1]
+                continue
+            }
+            guard ast.count == 3 else { throw MalError.invalidArguments("try*") }
+            guard case let .list(values) = ast[2], values.count == 3 else { throw MalError.invalidArguments("try*") }
+            guard case .symbol("catch*") = values[0] else { throw MalError.invalidArguments("try*") }
+            guard case let .symbol(bind) = values[1] else { throw MalError.invalidArguments("catch*") }
+
+            do {
+                expr = try eval(ast[1], env: env)
+            } catch {
+                let malErr = (error as? Expr) ?? .string(error.localizedDescription)
+                let newEnv = try Env(binds: [bind], exprs: [malErr], outer: env)
+                env = newEnv
+                expr = values[2]
+            }
+
+        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)
+
+replEnv.set(forKey: "eval", val: .function(Func { args in
+    guard let expr = args.first else { throw MalError.invalidArguments("eval") }
+    return try eval(expr, env: replEnv)
+}))
+replEnv.set(forKey: "*ARGV*", val: .list(CommandLine.arguments.dropFirst(2).map(Expr.string)))
+
+_ = rep("(def! not (fn* (a) (if a false true)))", env: replEnv)
+_ = rep(#"(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))"#, env: replEnv)
+_ = rep(#"(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))"#, env: replEnv)
+
+if CommandLine.arguments.count > 1 {
+    _ = rep("(load-file \"" + CommandLine.arguments[1] + "\")", env: replEnv)
+    exit(0)
+}
+
+while true {
+    print("user> ", terminator: "")
+    guard let s = readLine() else { break }
+    print(rep(s, env: replEnv))
+}