.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.
.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"])
]
)
}
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] = [
"<": .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 {
return "nil"
case .function:
return "#<function>"
+ case let .atom(expr):
+ return "(atom \(print(expr.val)))"
}
}
}
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)
}
case vector([Expr])
case hashmap([String: Expr])
case function(Func)
+ case atom(Atom)
}
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
}
}
-public class Func {
+final public class Func {
public let run: ([Expr]) throws -> Expr
public let ast: Expr?
public let params: [String]
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
+ }
+}
--- /dev/null
+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))
+}