Skip to content

Commit

Permalink
swift5: step6_file
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Montak committed Nov 8, 2019
1 parent e25a051 commit 1e447e2
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 6 deletions.
6 changes: 4 additions & 2 deletions swift5/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"])
]
)
59 changes: 57 additions & 2 deletions swift5/Sources/core/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions swift5/Sources/core/Printer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ extension Expr {
return "nil"
case .function:
return "#<function>"
case let .atom(expr):
return "(atom \(print(expr.val)))"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion swift5/Sources/core/Reader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
19 changes: 18 additions & 1 deletion swift5/Sources/core/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum Expr {
case vector([Expr])
case hashmap([String: Expr])
case function(Func)
case atom(Atom)
}

extension Expr: Equatable {
Expand All @@ -36,14 +37,16 @@ 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]
Expand All @@ -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
}
}
159 changes: 159 additions & 0 deletions swift5/Sources/step6_file/main.swift
Original file line number Diff line number Diff line change
@@ -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))
}

0 comments on commit 1e447e2

Please sign in to comment.