}
return .list(values)
}
+
+ static let nth = Func { args in
+ guard args.count == 2 else { throw MalError.invalidArguments("nth") }
+ guard case let .number(index) = args[1] else { throw MalError.invalidArguments("nth") }
+
+ switch args.first {
+ case let .list(values), let .vector(values):
+ guard values.indices ~= index else { throw MalError.outOfRange() }
+ return values[index]
+ default:
+ throw MalError.invalidArguments("nth")
+ }
+ }
+
+ static let first = Func { args in
+ switch args.first {
+ case let .list(values), let .vector(values):
+ return values.first ?? .null
+ case .null:
+ return .null
+ default:
+ throw MalError.invalidArguments("first")
+ }
+ }
+
+ static let rest = Func { args in
+ switch args.first {
+ case let .list(values), let .vector(values):
+ return .list(Array(values.dropFirst()))
+ case .null:
+ return .list([])
+ default:
+ throw MalError.invalidArguments("rest")
+ }
+ }
}
private let data: [String: Expr] = [
"reset!": .function(.reset),
"swap!": .function(.swap),
"cons": .function(.cons),
- "concat": .function(.concat)
+ "concat": .function(.concat),
+ "nth": .function(.nth),
+ "first": .function(.first),
+ "rest": .function(.rest),
]
public enum Core {
--- /dev/null
+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 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 {
+ 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("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))
+}