swift5: step4_if_fn_do
authorOleg Montak <montak.o@ya.ru>
Thu, 31 Oct 2019 20:05:00 +0000 (23:05 +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 [new file with mode: 0644]
swift5/Sources/core/Env.swift
swift5/Sources/core/Printer.swift
swift5/Sources/core/Reader.swift
swift5/Sources/core/Types.swift
swift5/Sources/core/Utils.swift [new file with mode: 0644]
swift5/Sources/step3_env/main.swift
swift5/Sources/step4_if_fn_do/main.swift [new file with mode: 0644]

index a5fdc34..fcd7e3a 100644 (file)
@@ -10,7 +10,8 @@ let package = Package(
         .executable(name: "step0_repl", targets: ["step0_repl"]),
         .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: "step3_env", targets: ["step3_env"]),
+        .executable(name: "step4_if_fn_do", targets: ["step4_if_fn_do"])
     ],
     dependencies: [
         // Dependencies declare other packages that this package depends on.
@@ -23,6 +24,7 @@ let package = Package(
         .target(name: "step0_repl", dependencies: ["core"]),
         .target(name: "step1_read_print", dependencies: ["core"]),
         .target(name: "step2_eval", dependencies: ["core"]),
-        .target(name: "step3_env", dependencies: ["core"])
+        .target(name: "step3_env", dependencies: ["core"]),
+        .target(name: "step4_if_fn_do", dependencies: ["core"])
     ]
 )
diff --git a/swift5/Sources/core/Core.swift b/swift5/Sources/core/Core.swift
new file mode 100644 (file)
index 0000000..960cfeb
--- /dev/null
@@ -0,0 +1,105 @@
+import Foundation
+
+private extension Func {
+    static func infixOperation(_ op: @escaping (Int, Int) -> Int) -> Func {
+        return Func { args in
+            guard args.count == 2,
+                case let .number(a) = args[0],
+                case let .number(b) = args[1] else { throw MalError("invalid arguments") }
+
+            return .number(op(a, b))
+        }
+    }
+
+    static func comparisonOperation(_ op: @escaping (Int, Int) -> Bool) -> Func {
+        return Func { args in
+            guard args.count == 2,
+                case let .number(a) = args[0],
+                case let .number(b) = args[1] else { throw MalError("invalid arguments") }
+
+            return .bool(op(a, b))
+        }
+    }
+
+    static let prn = Func { args in
+        let printFunc = curry(Expr.print)(true)
+        let result = args.map(printFunc).joined(separator: " ")
+        print(result)
+        return .null
+    }
+
+    static let str = Func { args in
+        let printFunc = curry(Expr.print)(false)
+        let result = args.map(printFunc).joined(separator: "")
+        return .string(result)
+    }
+
+    static let prStr = Func { args in
+        let printFunc = curry(Expr.print)(true)
+        let result = args.map(printFunc).joined(separator: " ")
+        return .string(result)
+    }
+
+    static let println = Func { args in
+        let printFunc = curry(Expr.print)(false)
+        let result = args.map(printFunc).joined(separator: " ")
+        print(result)
+        return .null
+    }
+
+    static let list = Func { args in .list(args) }
+
+    static let isList = Func { args in
+        if case .list = args.first {
+            return .bool(true)
+        }
+        return .bool(false)
+    }
+
+    static let isEmpty = Func { args in
+        switch args.first {
+        case let .list(xs), let .vector(xs):
+            return .bool(xs.isEmpty)
+        default:
+            return .bool(false)
+        }
+    }
+
+    static let count = Func { args in
+        switch args.first {
+        case let .list(xs), let .vector(xs):
+            return .number(xs.count)
+        default:
+            return .number(0)
+        }
+    }
+
+    static let eq = Func { args in
+        guard args.count == 2 else { throw MalError("eq: invalid arguments") }
+        return args[0] == args[1] ? .bool(true) : .bool(false)
+    }
+}
+
+private let data: [String: Expr] = [
+    "+": .function(.infixOperation(+)),
+    "-": .function(.infixOperation(-)),
+    "*": .function(.infixOperation(*)),
+    "/": .function(.infixOperation(/)),
+    "prn": .function(.prn),
+    "println": .function(.println),
+    "pr-str": .function(.prStr),
+    "str": .function(.str),
+    "list": .function(.list),
+    "list?": .function(.isList),
+    "empty?": .function(.isEmpty),
+    "count": .function(.count),
+    "=": .function(.eq),
+    "<": .function(.comparisonOperation(<)),
+    "<=": .function(.comparisonOperation(<=)),
+    ">": .function(.comparisonOperation(>)),
+    ">=": .function(.comparisonOperation(>=))
+]
+
+public enum Core {
+    public static let ns: Env = Env.init(data: data, outer: nil)
+}
index 5d2bba0..3c70a0b 100644 (file)
@@ -1,19 +1,31 @@
 import Foundation
 
-public struct Env {
-    private var _outer: [Env]
-    private var outer: Env? {
-        return _outer.first
-    }
-
-    private var data: [String: Expr]
+public class Env {
+    private var outer: Env?
+    public private(set) var data: [String: Expr]
 
     public init(data: [String: Expr] = [:], outer: Env? = nil) {
-        self._outer = outer != nil ? [outer!] : []
+        self.outer = outer
         self.data = data
     }
 
-    public mutating func set(forKey key: String, val: Expr) {
+    public init(binds: [String], exprs: [Expr], outer: Env? = nil) throws {
+        self.outer = outer
+        self.data = [:]
+
+        for i in 0..<binds.count {
+            let bindName = binds[i]
+            if bindName == "&" {
+                guard let key = binds[safe: i + 1] else { throw MalError("invalid variadic function definition") }
+                data[key] = .list(Array(exprs[i...]))
+                break
+            }
+            guard let exp = exprs[safe: i] else { throw MalError("function call: invalid arguments") }
+            data[bindName] = exp
+        }
+    }
+
+    public func set(forKey key: String, val: Expr) {
         data[key] = val
     }
 
index 310515b..e1034ee 100644 (file)
@@ -21,8 +21,12 @@ extension Expr {
             return printString(s, readable: readable)
         case let .symbol(s):
             return s
+        case let .bool(b):
+            return b ? "true" : "false"
+        case .null:
+            return "nil"
         case .function:
-            return "#function"
+            return "#<function>"
         }
     }
 }
@@ -31,7 +35,7 @@ private func printString(_ s: String, readable: Bool) -> String {
     if s.first == keywordMagic {
         return ":" + s.dropFirst()
     }
-    return "\"" + (readable ? unescape(s) : s) + "\""
+    return readable ? ("\"" + unescape(s) + "\"") : s
 }
 
 private func unescape(_ s: String) -> String {
@@ -46,7 +50,3 @@ extension Expr: CustomDebugStringConvertible {
         Expr.print(self)
     }
 }
-
-private func curry<A, B, C>(_ function: @escaping (A, B) -> C) -> (A) -> (B) -> C {
-    return { (a: A) -> (B) -> C in { (b: B) -> C in function(a, b) } }
-}
index 3823561..670fe3e 100644 (file)
@@ -17,6 +17,8 @@ private extension Parsers {
         hashmap,
         eString,
         number,
+        null,
+        bool,
         symbol,
         keyword,
         sugar
@@ -73,6 +75,23 @@ private extension Parsers {
     static let symbolRest = oneOf(symbolHead, char(from: "0123456789."))
     static let symbol = name.map(Expr.symbol)
 
+    static let bool = name.map(makeBool)
+    static func makeBool(_ s: String) -> Expr? {
+        switch s {
+        case "true": return .bool(true)
+        case "false": return .bool(false)
+        default: return nil
+        }
+    }
+
+    static let null = name.map(makeNull)
+    static func makeNull(_ s: String) -> Expr? {
+        if s == "nil" {
+            return .null
+        }
+        return nil
+    }
+
     static let name = (symbolHead <*> symbolRest.zeroOrMore).map { String($0) + String($1) }
     static let keyword = (":" *> name).map { Expr.string(String(keywordMagic) + $0) }
 
index 3aa8d80..ccb32ae 100644 (file)
@@ -4,6 +4,8 @@ public let keywordMagic: Character = "\u{029E}"
 
 public enum Expr {
     case number(Int)
+    case bool(Bool)
+    case null
     case string(String)
     case symbol(String)
     case list([Expr])
@@ -12,6 +14,35 @@ public enum Expr {
     case function(Func)
 }
 
+extension Expr: Equatable {
+    public static func == (lhs: Self, rhs: Self) -> Bool {
+        switch (lhs, rhs) {
+        case let (.number(a), .number(b)):
+            return a == b
+        case let (.bool(a), .bool(b)):
+            return a == b
+        case (.null, .null):
+            return true
+        case let (.string(a), .string(b)):
+            return a == b
+        case let (.symbol(a), .symbol(b)):
+            return a == b
+        case let (.list(a), .list(b)),
+             let (.vector(a), .vector(b)),
+             let (.list(a), .vector(b)),
+             let (.vector(a), .list(b)):
+                return a == b
+        case let (.hashmap(a), .hashmap(b)):
+            return a == b
+        case let (.function(a), .function(b)):
+            return a == b
+
+        default:
+            return false
+        }
+    }
+}
+
 public struct Func {
     public let run: ([Expr]) throws -> Expr
 
@@ -19,3 +50,9 @@ public struct Func {
         self.run = run
     }
 }
+
+extension Func: Equatable {
+    public static func == (lhs: Self, rhs: Self) -> Bool {
+        return false
+    }
+}
diff --git a/swift5/Sources/core/Utils.swift b/swift5/Sources/core/Utils.swift
new file mode 100644 (file)
index 0000000..e659733
--- /dev/null
@@ -0,0 +1,11 @@
+import Foundation
+
+public func curry<A, B, C>(_ function: @escaping (A, B) -> C) -> (A) -> (B) -> C {
+    return { (a: A) -> (B) -> C in { (b: B) -> C in function(a, b) } }
+}
+
+public extension Collection {
+    subscript (safe index: Index) -> Element? {
+        return indices.contains(index) ? self[index] : nil
+    }
+}
index 7acba26..9c10dcf 100644 (file)
@@ -1,9 +1,6 @@
 import Foundation
 import core
 
-
-// MARK: - Special Forms
-
 extension Func {
 
     static fileprivate func infixOperation(_ op: @escaping (Int, Int) -> Int) -> Func {
@@ -27,17 +24,17 @@ func read(_ s: String) throws -> Expr {
     return try Reader.read(s)
 }
 
-func eval(_ expr: Expr, env: inout Env) throws -> Expr {
+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) })
+        return .vector(try values.map { try eval($0, env: env) })
     case let .hashmap(values):
-        return .hashmap(try values.mapValues { try eval($0, env: &env) })
+        return .hashmap(try values.mapValues { try eval($0, env: env) })
     case .list:
-        return try eval_list(expr, env: &env)
+        return try eval_list(expr, env: env)
     default:
         return expr
     }
@@ -54,12 +51,12 @@ private func isDefinition(_ values: [Expr]) -> Bool {
     return isTaggedList(values, tagName: "def!")
 }
 
-private func evalDefinition(_ values: [Expr], env: inout Env) throws -> Expr {
+private func evalDefinition(_ values: [Expr], env: Env) throws -> Expr {
     guard values.count == 3 else { throw MalError("def!: invalid arguments") }
     guard case let .symbol(name) = values[1] else { throw MalError("def!: invalid arguments") }
 
     let expToEval = values[2]
-    let val = try eval(expToEval, env: &env)
+    let val = try eval(expToEval, env: env)
     env.set(forKey: name, val: val)
     return val
 }
@@ -68,27 +65,27 @@ private func isLetForm(_ values: [Expr]) -> Bool {
     return isTaggedList(values, tagName: "let*")
 }
 
-private func evalLetForm(_ values: [Expr], env: inout Env) throws -> Expr {
+private func evalLetForm(_ values: [Expr], env: Env) throws -> Expr {
     guard values.count == 3 else { throw MalError("let*: invalid arguments") }
 
     switch values[1] {
     case let .list(bindable), let .vector(bindable):
-        var letEnv = Env(outer: env)
+        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))
+            letEnv.set(forKey: key, val: try eval(value, env: letEnv))
         }
 
         let expToEval = values[2]
-        return try eval(expToEval, env: &letEnv)
+        return try eval(expToEval, env: letEnv)
     default:
         throw MalError("let*: invalid arguments")
     }
 }
 
-func eval_list(_ expr: Expr, env: inout Env) throws -> Expr {
+func eval_list(_ expr: Expr, env: Env) throws -> Expr {
     guard case let .list(values) = expr else { fatalError() }
 
     if values.isEmpty {
@@ -96,14 +93,14 @@ func eval_list(_ expr: Expr, env: inout Env) throws -> Expr {
     }
 
     if isDefinition(values) {
-        return try evalDefinition(values, env: &env)
+        return try evalDefinition(values, env: env)
     }
 
     if isLetForm(values) {
-        return try evalLetForm(values, env: &env)
+        return try evalLetForm(values, env: env)
     }
 
-    let evaluated = try values.map { try eval($0, env: &env) }
+    let evaluated = try values.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()))
 }
@@ -112,10 +109,10 @@ func print(_ expr: Expr) -> String {
     return Expr.print(expr)
 }
 
-func rep(_ s: String, env: inout Env) -> String {
+func rep(_ s: String, env: Env) -> String {
     do {
         let expr = try read(s)
-        let resExpr = try eval(expr, env: &env)
+        let resExpr = try eval(expr, env: env)
         let resultStr = print(resExpr)
         return resultStr
     } catch {
@@ -126,5 +123,5 @@ func rep(_ s: String, env: inout Env) -> String {
 while true {
     print("user> ", terminator: "")
     guard let s = readLine() else { break }
-    print(rep(s, env: &replEnv))
+    print(rep(s, env: replEnv))
 }
diff --git a/swift5/Sources/step4_if_fn_do/main.swift b/swift5/Sources/step4_if_fn_do/main.swift
new file mode 100644 (file)
index 0000000..005e955
--- /dev/null
@@ -0,0 +1,178 @@
+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))
+}