swift5: step6_file
authorOleg Montak <montak.o@ya.ru>
Mon, 4 Nov 2019 12:59:19 +0000 (15:59 +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/Printer.swift
swift5/Sources/core/Reader.swift
swift5/Sources/core/Types.swift
swift5/Sources/step6_file/main.swift [new file with mode: 0644]

index 00656ed..bfd7238 100644 (file)
@@ -12,7 +12,8 @@ let package = Package(
         .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: "step5_tco", targets: ["step5_tco"])
+        .executable(name: "step5_tco", targets: ["step5_tco"]),
+        .executable(name: "step6_file", targets: ["step6_file"])
     ],
     dependencies: [
         // Dependencies declare other packages that this package depends on.
@@ -27,6 +28,7 @@ let package = Package(
         .target(name: "step2_eval", dependencies: ["core"]),
         .target(name: "step3_env", dependencies: ["core"]),
         .target(name: "step4_if_fn_do", dependencies: ["core"]),
-        .target(name: "step5_tco", dependencies: ["core"])
+        .target(name: "step5_tco", dependencies: ["core"]),
+        .target(name: "step6_file", dependencies: ["core"])
     ]
 )
index 960cfeb..ec97e53 100644 (file)
@@ -75,9 +75,57 @@ private extension Func {
     }
 
     static let eq = Func { args in
-        guard args.count == 2 else { throw MalError("eq: invalid arguments") }
+        guard args.count == 2 else { throw MalError.invalidArguments("eq") }
         return args[0] == args[1] ? .bool(true) : .bool(false)
     }
+
+    static let readString = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("read-string") }
+        guard case let .string(s) = args[0] else { throw MalError.invalidArguments("read-string") }
+        return try Reader.read(s)
+    }
+
+    static let slurp = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("slurp") }
+        guard case let .string(filename) = args[0] else { throw MalError.invalidArguments("slurp") }
+        return .string(try String(contentsOfFile: filename))
+    }
+
+    static let atom = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("atom") }
+        return .atom(Atom(args[0]))
+    }
+
+    static let isAtom = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("atom?") }
+        if case .atom = args[0] {
+            return .bool(true)
+        } else {
+            return .bool(false)
+        }
+    }
+
+    static let deref = Func { args in
+        guard args.count == 1 else { throw MalError.invalidArguments("deref") }
+        guard case let .atom(atom) = args[0] else { throw MalError.invalidArguments("deref") }
+        return atom.val
+    }
+
+    static let reset = Func { args in
+        guard args.count == 2 else { throw MalError.invalidArguments("reset!") }
+        guard case let .atom(atom) = args[0] else { throw MalError.invalidArguments("reset!") }
+        atom.val = args[1]
+        return args[1]
+    }
+
+    static let swap = Func { args in
+        guard args.count >= 2 else { throw MalError.invalidArguments("reset!") }
+        guard case let .atom(atom) = args[0] else { throw MalError.invalidArguments("swap!") }
+        guard case let .function(fn) = args[1] else { throw MalError.invalidArguments("swap!") }
+        let otherArgs = args.dropFirst(2)
+        atom.val = try fn.run([atom.val] + otherArgs)
+        return atom.val
+    }
 }
 
 private let data: [String: Expr] = [
@@ -97,7 +145,14 @@ private let data: [String: Expr] = [
     "<": .function(.comparisonOperation(<)),
     "<=": .function(.comparisonOperation(<=)),
     ">": .function(.comparisonOperation(>)),
-    ">=": .function(.comparisonOperation(>=))
+    ">=": .function(.comparisonOperation(>=)),
+    "read-string": .function(.readString),
+    "slurp": .function(.slurp),
+    "atom": .function(.atom),
+    "atom?": .function(.isAtom),
+    "deref": .function(.deref),
+    "reset!": .function(.reset),
+    "swap!": .function(.swap)
 ]
 
 public enum Core {
index e1034ee..93a935b 100644 (file)
@@ -27,6 +27,8 @@ extension Expr {
             return "nil"
         case .function:
             return "#<function>"
+        case let .atom(expr):
+            return "(atom \(print(expr.val)))"
         }
     }
 }
index 670fe3e..8124017 100644 (file)
@@ -115,7 +115,7 @@ private extension Parsers {
 extension Parsers {
 
     static let whitespace = char(from: " \n\r\t,")
-    static let comment = char(from: ";") <* char(excluding: "\r\n").zeroOrMore
+    static let comment = char(from: ";") <* char(excluding: "\n\r").zeroOrMore
     static let trash = oneOf(whitespace, comment)
 }
 
index 09a0bf5..36a35c6 100644 (file)
@@ -12,6 +12,7 @@ public enum Expr {
     case vector([Expr])
     case hashmap([String: Expr])
     case function(Func)
+    case atom(Atom)
 }
 
 extension Expr: Equatable {
@@ -36,6 +37,8 @@ extension Expr: Equatable {
             return a == b
         case let (.function(a), .function(b)):
             return a == b
+        case let (.atom(a), .atom(b)):
+            return a == b
 
         default:
             return false
@@ -43,7 +46,7 @@ extension Expr: Equatable {
     }
 }
 
-public class Func {
+final public class Func {
     public let run: ([Expr]) throws -> Expr
     public let ast: Expr?
     public let params: [String]
@@ -67,3 +70,17 @@ extension Func: Equatable {
         return lhs === rhs
     }
 }
+
+final public class Atom {
+    public var val: Expr
+
+    public init(_ val: Expr) {
+        self.val = val
+    }
+}
+
+extension Atom: Equatable {
+    public static func == (lhs: Atom, rhs: Atom) -> Bool {
+        return lhs.val == rhs.val
+    }
+}
diff --git a/swift5/Sources/step6_file/main.swift b/swift5/Sources/step6_file/main.swift
new file mode 100644 (file)
index 0000000..b0612d9
--- /dev/null
@@ -0,0 +1,159 @@
+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)
+
+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)
+
+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))
+}