swift3: step0-step5 basics
authorJoel Martin <github@martintribe.org>
Tue, 23 Feb 2016 06:49:28 +0000 (00:49 -0600)
committerJoel Martin <github@martintribe.org>
Tue, 23 Feb 2016 06:49:28 +0000 (00:49 -0600)
12 files changed:
swift3/Makefile [new file with mode: 0644]
swift3/Sources/core.swift [new file with mode: 0644]
swift3/Sources/env.swift [new file with mode: 0644]
swift3/Sources/printer.swift [new file with mode: 0644]
swift3/Sources/reader.swift [new file with mode: 0644]
swift3/Sources/step0_repl/main.swift [new file with mode: 0644]
swift3/Sources/step1_read_print/main.swift [new file with mode: 0644]
swift3/Sources/step2_eval/main.swift [new file with mode: 0644]
swift3/Sources/step3_env/main.swift [new file with mode: 0644]
swift3/Sources/step4_if_fn_do/main.swift [new file with mode: 0644]
swift3/Sources/step5_tco/main.swift [new file with mode: 0644]
swift3/Sources/types.swift [new file with mode: 0644]

diff --git a/swift3/Makefile b/swift3/Makefile
new file mode 100644 (file)
index 0000000..90ba33b
--- /dev/null
@@ -0,0 +1,15 @@
+STEP3_DEPS = Sources/types.swift Sources/reader.swift Sources/printer.swift Sources/env.swift
+STEP4_DEPS = $(STEP3_DEPS) Sources/core.swift
+
+STEPS = step0_repl step1_read_print step2_eval step3_env \
+       step4_if_fn_do step5_tco step6_file step7_quote \
+       step8_macros step9_try stepA_mal
+
+step1_read_print step2_eval step3_env: $(STEP3_DEPS)
+step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal: $(STEP4_DEPS)
+
+step%: Sources/step%/main.swift
+       swiftc $+ -o $@
+
+clean:
+       rm -f $(STEPS)
diff --git a/swift3/Sources/core.swift b/swift3/Sources/core.swift
new file mode 100644 (file)
index 0000000..7b28f02
--- /dev/null
@@ -0,0 +1,72 @@
+func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal {
+    switch (a, b) {
+    case (MV.MalInt(let i1), MV.MalInt(let i2)):
+        return MV.MalInt(op(i1, i2))
+    default:
+        throw MalError.General(msg: "Invalid IntOp call")
+    }
+}
+
+func CmpOp(op: (Int, Int) -> Bool, _ a: MalVal, _ b: MalVal) throws -> MalVal {
+    switch (a, b) {
+    case (MV.MalInt(let i1), MV.MalInt(let i2)):
+        return wraptf(op(i1, i2))
+    default:
+        throw MalError.General(msg: "Invalid CmpOp call")
+    }
+}
+
+
+
+let core_ns: Dictionary<String,(Array<MalVal>) throws -> MalVal> = [
+    "=":  { wraptf(equal_Q($0[0], $0[1])) },
+
+    "pr-str":  {
+        return MV.MalString($0.map { pr_str($0,true) }.joinWithSeparator(" "))
+    },
+    "str": {
+        return MV.MalString($0.map { pr_str($0,false) }.joinWithSeparator(""))
+    },
+    "prn": {
+        print($0.map { pr_str($0,true) }.joinWithSeparator(" "))
+        return MV.MalNil
+    },
+    "println": {
+        print($0.map { pr_str($0,false) }.joinWithSeparator(" "))
+        return MV.MalNil
+    },
+
+
+    "<":  { try CmpOp({ $0 < $1},  $0[0], $0[1]) },
+    "<=": { try CmpOp({ $0 <= $1}, $0[0], $0[1]) },
+    ">":  { try CmpOp({ $0 > $1},  $0[0], $0[1]) },
+    ">=": { try CmpOp({ $0 >= $1}, $0[0], $0[1]) },
+    "+":  { try IntOp({ $0 + $1},  $0[0], $0[1]) },
+    "-":  { try IntOp({ $0 - $1},  $0[0], $0[1]) },
+    "*":  { try IntOp({ $0 * $1},  $0[0], $0[1]) },
+    "/":  { try IntOp({ $0 / $1},  $0[0], $0[1]) },
+
+    "list": { MV.MalList($0) },
+    "list?": {
+        switch $0[0] {
+        case MV.MalList(_): return MV.MalTrue
+        default: return MV.MalFalse
+        }
+    },
+
+    "empty?": {
+        switch $0[0] {
+        case MV.MalList(let lst):
+            return lst.count == 0 ? MV.MalTrue : MV.MalFalse
+        case MV.MalNil: return MV.MalTrue
+        default: throw MalError.General(msg: "Invalid empty? call")
+        }
+    },
+    "count": {
+        switch $0[0] {
+        case MV.MalList(let lst): return MV.MalInt(lst.count)
+        case MV.MalNil: return MV.MalInt(0)
+        default: throw MalError.General(msg: "Invalid count call")
+        }
+    }
+]
diff --git a/swift3/Sources/env.swift b/swift3/Sources/env.swift
new file mode 100644 (file)
index 0000000..f88fea8
--- /dev/null
@@ -0,0 +1,82 @@
+class Env {
+    var outer: Env? = nil
+    var data: Dictionary<String, MalVal> = [:]
+
+    init(_ outer: Env? = nil, binds: MalVal? = nil,
+                              exprs: MalVal? = nil) throws {
+        self.outer = outer
+
+        if binds != nil {
+            switch (binds!, exprs!) {
+            case (MalVal.MalList(let bs), MalVal.MalList(let es)):
+                var pos = bs.startIndex
+
+                bhandle:
+                while pos < bs.endIndex {
+                    let b = bs[pos]
+                    switch b {
+                    case MalVal.MalSymbol("&"):
+                        switch bs[pos.successor()] {
+                        case MalVal.MalSymbol(let sym):
+                            if pos < es.endIndex {
+                                let slc = es[pos..<es.endIndex]
+                                data[sym] = MalVal.MalList(Array(slc))
+                            } else {
+                                data[sym] = MalVal.MalList([])
+                            }
+                            break bhandle
+                        default:
+                            throw MalError.General(msg: "Env invalid varargs")
+                        }
+                    case MalVal.MalSymbol(let sym):
+                        let e = es[pos]
+                        data[sym] = e
+                    default:
+                        throw MalError.General(msg: "Env binds has non-symbol")
+                    }
+                    pos = pos.successor()
+                }
+            default:
+                throw MalError.General(msg: "invalid Env init call")
+            }
+        }
+    }
+
+    func find(key: MalVal) throws -> Env? {
+        switch key {
+        case MalVal.MalSymbol(let str):
+            if data[str] != nil {
+                return self
+            } else if outer != nil {
+                return try outer!.find(key)
+            } else {
+                return nil
+            }
+        default:
+            throw MalError.General(msg: "invalid Env.find call")
+        }
+    }
+
+    func get(key: MalVal) throws -> MalVal {
+        switch key {
+        case MalVal.MalSymbol(let str):
+            let env = try self.find(key)
+            if env == nil {
+                throw MalError.General(msg: "'\(str)' not found")
+            }
+            return env!.data[str]!
+        default:
+            throw MalError.General(msg: "invalid Env.find call")
+        }
+    }
+
+    func set(key: MalVal, _ val: MalVal) throws -> MalVal {
+        switch key {
+        case MalVal.MalSymbol(let str):
+            data[str] = val
+            return val
+        default:
+            throw MalError.General(msg: "invalid Env.find call")
+        }
+    }
+}
diff --git a/swift3/Sources/printer.swift b/swift3/Sources/printer.swift
new file mode 100644 (file)
index 0000000..6b861fe
--- /dev/null
@@ -0,0 +1,34 @@
+
+func pr_str(obj: MalVal, _ print_readably: Bool = true) -> String {
+    switch obj {
+    case MalVal.MalList(let lst):
+        let elems = lst.map { pr_str($0, print_readably) }
+        return "(" + elems.joinWithSeparator(" ")  + ")"
+    case MalVal.MalVector(let lst):
+        let elems = lst.map { pr_str($0, print_readably) }
+        return "[" + elems.joinWithSeparator(" ")  + "]"
+    case MalVal.MalString(let str):
+        if print_readably {
+            let s1 = str.stringByReplacingOccurrencesOfString(
+                "\\", withString: "\\\\")
+            let s2 = s1.stringByReplacingOccurrencesOfString(
+                "\"", withString: "\\\"")
+            let s3 = s2.stringByReplacingOccurrencesOfString(
+                "\n", withString: "\\n")
+            return "\"" + s3 + "\""
+        } else {
+            return str
+        }
+    case MalVal.MalSymbol(let str):
+        return str
+    case MalVal.MalInt(let i): return String(i)
+    case MalVal.MalNil:        return "nil"
+    case MalVal.MalFalse:      return "false"
+    case MalVal.MalTrue:       return "true"
+    case MalVal.MalFunc(_, nil, _, _):
+        return "#<native function>"
+    case MalVal.MalFunc(_, let ast, _, let params):
+        return "(fn* \(pr_str(params![0])) \(pr_str(ast![0])))"
+    default:                   return String(obj)
+    }
+}
diff --git a/swift3/Sources/reader.swift b/swift3/Sources/reader.swift
new file mode 100644 (file)
index 0000000..4a2bbfd
--- /dev/null
@@ -0,0 +1,141 @@
+let token_delim: Set<Character> = [
+    ";", ",", "\"", "`", " ", "\n", "{", "}", "(", ")", "[", "]"
+]
+
+let int_char: Set<Character> = [
+    "-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+]
+
+let float_char: Set<Character> = [
+    ".", "-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+]
+
+let whitespace: Set<Character> = [" ", "\t", "\n", ","]
+
+class Reader {
+    var str: String
+    var pos: String.Index
+    init(_ str: String) {
+        self.str = str
+        pos = str.startIndex
+    }
+}
+
+func read_int(rdr: Reader) -> MalVal {
+    let start = rdr.pos
+    for cidx in rdr.pos..<rdr.str.endIndex {
+        rdr.pos = cidx
+        if !int_char.contains(rdr.str[cidx]) { break }
+        rdr.pos = cidx.successor()
+    }
+    let matchStr = rdr.str.substringWithRange(start..<rdr.pos)
+    if matchStr == "-" {
+        return MalVal.MalSymbol("-")
+    } else {
+        return MalVal.MalInt(Int(matchStr)!)
+    }
+}
+
+func skip_whitespace(rdr: Reader) {
+    for cidx in rdr.pos..<rdr.str.endIndex {
+        rdr.pos = cidx
+        if !whitespace.contains(rdr.str[rdr.pos]) { break }
+    }
+}
+
+func read_string(rdr: Reader) throws -> MalVal {
+    let start = rdr.pos
+    var escaped = false
+    if rdr.str[rdr.pos] != "\"" {
+        throw MalError.Reader(msg: "read_string call on non-string")
+    }
+    for cidx in rdr.pos.successor()..<rdr.str.endIndex {
+        rdr.pos = cidx.successor()
+        if escaped {
+            escaped = false
+            continue
+        }
+        if rdr.str[cidx] == "\\" { escaped = true }
+        if rdr.str[cidx] == "\"" { break }
+    }
+    if rdr.pos > rdr.str.endIndex {
+        throw MalError.Reader(msg: "Expected '\"', got EOF")
+    }
+    let matchStr = rdr.str.substringWithRange(
+        start.successor()..<rdr.pos.predecessor())
+    let s1 = matchStr.stringByReplacingOccurrencesOfString(
+        "\\\"", withString: "\"")
+    let s2 = s1.stringByReplacingOccurrencesOfString(
+        "\\n", withString: "\n")
+    let s3 = s2.stringByReplacingOccurrencesOfString(
+        "\\\\", withString: "\\")
+    return MalVal.MalString(s3)
+}
+
+func read_symbol(rdr: Reader) throws -> MalVal {
+    let start = rdr.pos
+    for cidx in rdr.pos..<rdr.str.endIndex {
+        rdr.pos = cidx
+        if token_delim.contains(rdr.str[cidx]) { break }
+        rdr.pos = cidx.successor()
+    }
+    let matchStr = rdr.str.substringWithRange(start..<rdr.pos)
+    switch matchStr {
+        case "nil": return MalVal.MalNil
+        case "true": return MalVal.MalTrue
+        case "false": return MalVal.MalFalse
+        default: return MalVal.MalSymbol(matchStr)
+    }
+}
+
+func read_atom(rdr: Reader) throws -> MalVal {
+    if rdr.str.characters.count == 0 {
+        throw MalError.Reader(msg: "Empty string passed to read_atom")
+    }
+    switch rdr.str[rdr.pos] {
+    case let c where int_char.contains(c): return read_int(rdr)
+    case "\"": return try read_string(rdr)
+    default: return try read_symbol(rdr)
+    }
+}
+
+func read_list(rdr: Reader, start: Character = "(", end: Character = ")") throws -> Array<MalVal> {
+    if rdr.str[rdr.pos] != start {
+        throw MalError.Reader(msg: "expected '\(start)'")
+    }
+    rdr.pos = rdr.pos.successor()
+    var lst: [MalVal] = []
+    while rdr.pos < rdr.str.endIndex {
+        if (rdr.str[rdr.pos] == end) { break }
+        lst.append(try read_form(rdr))
+    }
+    if rdr.pos >= rdr.str.endIndex {
+        throw MalError.Reader(msg: "Expected '\(end)', got EOF")
+    }
+    rdr.pos = rdr.pos.successor()
+    return lst
+}
+
+func read_form(rdr: Reader) throws -> MalVal {
+    if rdr.str.characters.count == 0 {
+        throw MalError.Reader(msg: "Empty string passed to read_form")
+    }
+    //print("read_form: \(rdr.pos): \(rdr.str[rdr.pos])")
+    skip_whitespace(rdr)
+    var res: MalVal
+    switch rdr.str[rdr.pos] {
+    case "(": res = MalVal.MalList(try read_list(rdr))
+    case ")": throw MalError.Reader(msg: "unexpected ')'")
+
+    case "[": res = MalVal.MalVector(try read_list(rdr, start: "[", end: "]"))
+    case "]": throw MalError.Reader(msg: "unexpected ']'")
+
+    default: res = try read_atom(rdr)
+    }
+    skip_whitespace(rdr)
+    return res
+}
+
+func read_str(str: String) throws -> MalVal {
+    return try read_form(Reader(str)) 
+}
diff --git a/swift3/Sources/step0_repl/main.swift b/swift3/Sources/step0_repl/main.swift
new file mode 100644 (file)
index 0000000..2fadc16
--- /dev/null
@@ -0,0 +1,10 @@
+import Foundation
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    print("\(line!)")
+}
diff --git a/swift3/Sources/step1_read_print/main.swift b/swift3/Sources/step1_read_print/main.swift
new file mode 100644 (file)
index 0000000..60d76aa
--- /dev/null
@@ -0,0 +1,31 @@
+import Foundation
+
+func READ(str: String) throws -> MalVal {
+    return try read_str(str)
+}
+
+func EVAL(ast: MalVal, _ env: String) throws -> MalVal {
+    return ast
+}
+
+func PRINT(exp: MalVal) -> String {
+    return pr_str(exp, true)
+}
+
+
+func rep(str:String) throws -> String {
+    return PRINT(try EVAL(try READ(str), ""))
+}
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    do {
+        print(try rep(line!))
+    } catch (MalError.Reader(let msg)) {
+        print("Error: \(msg)")
+    }
+}
diff --git a/swift3/Sources/step2_eval/main.swift b/swift3/Sources/step2_eval/main.swift
new file mode 100644 (file)
index 0000000..1b11d3a
--- /dev/null
@@ -0,0 +1,82 @@
+import Foundation
+
+func READ(str: String) throws -> MalVal {
+    return try read_str(str)
+}
+
+func eval_ast(ast: MalVal, _ env: Dictionary<String, MalVal>) throws -> MalVal {
+    switch ast {
+    case MalVal.MalSymbol(let sym):
+        if env[sym] == nil {
+            throw MalError.General(msg: "'\(sym)' not found")
+        }
+        return env[sym]!
+    case MalVal.MalList(let lst):
+        return MalVal.MalList(try lst.map { try EVAL($0, env) })
+    default:
+        return ast
+    }
+}
+
+func EVAL(ast: MalVal, _ env: Dictionary<String, MalVal>) throws -> MalVal {
+    switch ast {
+    case MalVal.MalList: true
+    default: return try eval_ast(ast, env)
+    }
+
+    switch try eval_ast(ast, env) {
+    case MalVal.MalList(let elst):
+        switch elst[0] {
+        case MalVal.MalFunc(let fn,_,_,_):
+            let args = Array(elst[1..<elst.count])
+            return try fn(args)
+        default:
+            throw MalError.General(msg: "Cannot apply on '\(elst[0])'")
+        }
+    default: throw MalError.General(msg: "Invalid apply")
+    }
+}
+
+func PRINT(exp: MalVal) -> String {
+    return pr_str(exp, true)
+}
+
+
+func rep(str:String) throws -> String {
+    return PRINT(try EVAL(try READ(str), repl_env))
+}
+
+func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal {
+    switch (a, b) {
+    case (MalVal.MalInt(let i1), MalVal.MalInt(let i2)):
+        return MalVal.MalInt(op(i1, i2))
+    default:
+        throw MalError.General(msg: "Invalid IntOp call")
+    }
+}
+
+var repl_env: Dictionary<String,MalVal> = [
+    "+": MalVal.MalFunc({ try IntOp({ $0 + $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil),
+    "-": MalVal.MalFunc({ try IntOp({ $0 - $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil),
+    "*": MalVal.MalFunc({ try IntOp({ $0 * $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil),
+    "/": MalVal.MalFunc({ try IntOp({ $0 / $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil)
+]
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    do {
+        print(try rep(line!))
+    } catch (MalError.Reader(let msg)) {
+        print("Error: \(msg)")
+    } catch (MalError.General(let msg)) {
+        print("Error: \(msg)")
+    }
+}
diff --git a/swift3/Sources/step3_env/main.swift b/swift3/Sources/step3_env/main.swift
new file mode 100644 (file)
index 0000000..6e75293
--- /dev/null
@@ -0,0 +1,107 @@
+import Foundation
+
+func READ(str: String) throws -> MalVal {
+    return try read_str(str)
+}
+
+func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal {
+    switch ast {
+    case MalVal.MalSymbol:
+        return try env.get(ast)
+    case MalVal.MalList(let lst):
+        return MalVal.MalList(try lst.map { try EVAL($0, env) })
+    default:
+        return ast
+    }
+}
+
+func EVAL(ast: MalVal, _ env: Env) throws -> MalVal {
+    switch ast {
+    case MalVal.MalList: true
+    default: return try eval_ast(ast, env)
+    }
+
+    switch ast {
+    case MalVal.MalList(let lst):
+        switch lst[0] {
+        case MalVal.MalSymbol("def!"):
+            return try env.set(lst[1], try EVAL(lst[2], env))
+        case MalVal.MalSymbol("let*"):
+            let let_env = try Env(env)
+            switch lst[1] {
+            case MalVal.MalList(let binds):
+                var idx = binds.startIndex
+                while idx < binds.endIndex {
+                    let v = try EVAL(binds[idx.successor()], let_env)
+                    try let_env.set(binds[idx], v)
+                    idx = idx.successor().successor()
+                }
+                return try EVAL(lst[2], let_env)
+            default:
+                throw MalError.General(msg: "Invalid let* bindings")
+            }
+        default:
+            switch try eval_ast(ast, env) {
+            case MalVal.MalList(let elst):
+                switch elst[0] {
+                case MalVal.MalFunc(let fn,_,_,_):
+                    let args = Array(elst[1..<elst.count])
+                    return try fn(args)
+                default:
+                    throw MalError.General(msg: "Cannot apply on '\(elst[0])'")
+                }
+            default: throw MalError.General(msg: "Invalid apply")
+            }
+        }
+    default:
+        throw MalError.General(msg: "Invalid apply")
+    }
+}
+
+func PRINT(exp: MalVal) -> String {
+    return pr_str(exp, true)
+}
+
+
+func rep(str:String) throws -> String {
+    return PRINT(try EVAL(try READ(str), repl_env))
+}
+
+func IntOp(op: (Int, Int) -> Int, _ a: MalVal, _ b: MalVal) throws -> MalVal {
+    switch (a, b) {
+    case (MalVal.MalInt(let i1), MalVal.MalInt(let i2)):
+        return MalVal.MalInt(op(i1, i2))
+    default:
+        throw MalError.General(msg: "Invalid IntOp call")
+    }
+}
+
+var repl_env: Env = try Env()
+try repl_env.set(MalVal.MalSymbol("+"),
+                 MalVal.MalFunc({ try IntOp({ $0 + $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil))
+try repl_env.set(MalVal.MalSymbol("-"),
+                 MalVal.MalFunc({ try IntOp({ $0 - $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil))
+try repl_env.set(MalVal.MalSymbol("*"),
+                 MalVal.MalFunc({ try IntOp({ $0 * $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil))
+try repl_env.set(MalVal.MalSymbol("/"),
+                 MalVal.MalFunc({ try IntOp({ $0 / $1}, $0[0], $0[1]) },
+                                ast:nil, env:nil, params:nil))
+
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    do {
+        print(try rep(line!))
+    } catch (MalError.Reader(let msg)) {
+        print("Error: \(msg)")
+    } catch (MalError.General(let msg)) {
+        print("Error: \(msg)")
+    }
+}
diff --git a/swift3/Sources/step4_if_fn_do/main.swift b/swift3/Sources/step4_if_fn_do/main.swift
new file mode 100644 (file)
index 0000000..61b1552
--- /dev/null
@@ -0,0 +1,119 @@
+import Foundation
+
+func READ(str: String) throws -> MalVal {
+    return try read_str(str)
+}
+
+func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal {
+    switch ast {
+    case MalVal.MalSymbol:
+        return try env.get(ast)
+    case MalVal.MalList(let lst):
+        return MalVal.MalList(try lst.map { try EVAL($0, env) })
+    default:
+        return ast
+    }
+}
+
+func EVAL(ast: MalVal, _ env: Env) throws -> MalVal {
+    switch ast {
+    case MalVal.MalList: true
+    default: return try eval_ast(ast, env)
+    }
+
+    switch ast {
+    case MalVal.MalList(let lst):
+        switch lst[0] {
+        case MalVal.MalSymbol("def!"):
+            return try env.set(lst[1], try EVAL(lst[2], env))
+        case MalVal.MalSymbol("let*"):
+            let let_env = try Env(env)
+            switch lst[1] {
+            case MalVal.MalList(let binds):
+                var idx = binds.startIndex
+                while idx < binds.endIndex {
+                    let v = try EVAL(binds[idx.successor()], let_env)
+                    try let_env.set(binds[idx], v)
+                    idx = idx.successor().successor()
+                }
+                return try EVAL(lst[2], let_env)
+            default:
+                throw MalError.General(msg: "Invalid let* bindings")
+            }
+        case MalVal.MalSymbol("do"):
+            let slc = lst[lst.startIndex.successor()..<lst.endIndex]
+            switch try eval_ast(MV.MalList(Array(slc)), env) {
+            case MalVal.MalList(let elst):
+                return elst[elst.endIndex.predecessor()]
+            default:
+                throw MalError.General(msg: "Invalid do form")
+            }
+        case MalVal.MalSymbol("if"):
+            switch try EVAL(lst[1], env) {
+            case MalVal.MalFalse, MalVal.MalNil:
+                if lst.count > 3 {
+                    return try EVAL(lst[3], env)
+                } else {
+                    return MalVal.MalNil
+                }
+            default:
+                return try EVAL(lst[2], env)
+            }
+        case MalVal.MalSymbol("fn*"):
+            return MalVal.MalFunc( { 
+                return try EVAL(lst[2], Env(env, binds: lst[1],
+                                                 exprs: MalVal.MalList($0)))
+            }, ast:nil, env:nil, params:nil)
+        default:
+            switch try eval_ast(ast, env) {
+            case MalVal.MalList(let elst):
+                switch elst[0] {
+                case MalVal.MalFunc(let fn, _, _, _):
+                    let args = Array(elst[1..<elst.count])
+                    return try fn(args)
+                default:
+                    throw MalError.General(msg: "Cannot apply on '\(elst[0])'")
+                }
+            default: throw MalError.General(msg: "Invalid apply")
+            }
+        }
+    default:
+        throw MalError.General(msg: "Invalid apply")
+    }
+}
+
+func PRINT(exp: MalVal) -> String {
+    return pr_str(exp, true)
+}
+
+
+func rep(str:String) throws -> String {
+    return PRINT(try EVAL(try READ(str), repl_env))
+}
+
+var repl_env: Env = try Env()
+
+// core.swift: defined using Swift
+for (k, fn) in core_ns {
+    try repl_env.set(MalVal.MalSymbol(k),
+                     MalVal.MalFunc(fn,ast:nil,env:nil,params:nil))
+}
+
+// core.mal: defined using the language itself
+try rep("(def! not (fn* (a) (if a false true)))")
+
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    do {
+        print(try rep(line!))
+    } catch (MalError.Reader(let msg)) {
+        print("Error: \(msg)")
+    } catch (MalError.General(let msg)) {
+        print("Error: \(msg)")
+    }
+}
diff --git a/swift3/Sources/step5_tco/main.swift b/swift3/Sources/step5_tco/main.swift
new file mode 100644 (file)
index 0000000..df1b439
--- /dev/null
@@ -0,0 +1,124 @@
+import Foundation
+
+func READ(str: String) throws -> MalVal {
+    return try read_str(str)
+}
+
+func eval_ast(ast: MalVal, _ env: Env) throws -> MalVal {
+    switch ast {
+    case MalVal.MalSymbol:
+        return try env.get(ast)
+    case MalVal.MalList(let lst):
+        return MalVal.MalList(try lst.map { try EVAL($0, env) })
+    default:
+        return ast
+    }
+}
+
+func EVAL(orig_ast: MalVal, _ orig_env: Env) throws -> MalVal {
+  var ast = orig_ast, env = orig_env
+  while true {
+    switch ast {
+    case MalVal.MalList: true
+    default: return try eval_ast(ast, env)
+    }
+
+    switch ast {
+    case MalVal.MalList(let lst):
+        switch lst[0] {
+        case MalVal.MalSymbol("def!"):
+            return try env.set(lst[1], try EVAL(lst[2], env))
+        case MalVal.MalSymbol("let*"):
+            let let_env = try Env(env)
+            switch lst[1] {
+            case MalVal.MalList(let binds):
+                var idx = binds.startIndex
+                while idx < binds.endIndex {
+                    let v = try EVAL(binds[idx.successor()], let_env)
+                    try let_env.set(binds[idx], v)
+                    idx = idx.successor().successor()
+                }
+                env = let_env
+                ast = lst[2] // TCO
+            default:
+                throw MalError.General(msg: "Invalid let* bindings")
+            }
+        case MalVal.MalSymbol("do"):
+            let slc = lst[lst.startIndex.successor()..<lst.endIndex.predecessor()]
+            try eval_ast(MV.MalList(Array(slc)), env)
+            ast = lst[lst.endIndex.predecessor()]
+        case MalVal.MalSymbol("if"):
+            switch try EVAL(lst[1], env) {
+            case MalVal.MalFalse, MalVal.MalNil:
+                if lst.count > 3 {
+                    ast = lst[3] // TCO
+                } else {
+                    return MalVal.MalNil
+                }
+            default:
+                ast = lst[2] // TCO
+            }
+        case MalVal.MalSymbol("fn*"):
+            return MalVal.MalFunc( { 
+                return try EVAL(lst[2], Env(env, binds: lst[1],
+                                                 exprs: MalVal.MalList($0)))
+            }, ast:[lst[2]], env:env, params:[lst[1]])
+        default:
+            switch try eval_ast(ast, env) {
+            case MalVal.MalList(let elst):
+                switch elst[0] {
+                case MalVal.MalFunc(let fn, nil, _, _):
+                    let args = Array(elst[1..<elst.count])
+                    return try fn(args)
+                case MalVal.MalFunc(_, let a, let e, let p):
+                    let args = Array(elst[1..<elst.count])
+                    ast = a![0]
+                    env = try Env(e, binds: p![0],
+                                     exprs: MalVal.MalList(args)) // TCO
+                default:
+                    throw MalError.General(msg: "Cannot apply on '\(elst[0])'")
+                }
+            default: throw MalError.General(msg: "Invalid apply")
+            }
+        }
+    default:
+        throw MalError.General(msg: "Invalid apply")
+    }
+  }
+}
+
+func PRINT(exp: MalVal) -> String {
+    return pr_str(exp, true)
+}
+
+
+func rep(str:String) throws -> String {
+    return PRINT(try EVAL(try READ(str), repl_env))
+}
+
+var repl_env: Env = try Env()
+
+// core.swift: defined using Swift
+for (k, fn) in core_ns {
+    try repl_env.set(MalVal.MalSymbol(k),
+                     MalVal.MalFunc(fn,ast:nil,env:nil,params:nil))
+}
+
+// core.mal: defined using the language itself
+try rep("(def! not (fn* (a) (if a false true)))")
+
+
+while true {
+    print("user> ", terminator: "")
+    let line = readLine(stripNewline: true)
+    if line == nil { break }
+    if line == "" { continue }
+
+    do {
+        print(try rep(line!))
+    } catch (MalError.Reader(let msg)) {
+        print("Error: \(msg)")
+    } catch (MalError.General(let msg)) {
+        print("Error: \(msg)")
+    }
+}
diff --git a/swift3/Sources/types.swift b/swift3/Sources/types.swift
new file mode 100644 (file)
index 0000000..9cdb849
--- /dev/null
@@ -0,0 +1,70 @@
+
+enum MalError: ErrorType {
+    case Reader(msg: String)
+    case General(msg: String)
+}
+
+enum MalVal {
+    case MalNil
+    case MalTrue
+    case MalFalse
+    case MalInt(Int)
+    case MalFloat(Float)
+    case MalString(String)
+    case MalSymbol(String)
+    case MalList(Array<MalVal>)
+    case MalVector(Array<MalVal>)
+    // TODO: ast and params wrapped in arrays because otherwise
+    // compiler throws a fault
+    case MalFunc((Array<MalVal>) throws -> MalVal,
+                 ast: Array<MalVal>?, env: Env?, params: Array<MalVal>?)
+}
+
+typealias MV = MalVal
+
+// General functions
+
+func wraptf(a: Bool) -> MalVal {
+    return a ? MV.MalTrue : MV.MalFalse
+}
+
+func cmp_seqs(a: Array<MalVal>, _ b: Array<MalVal>) -> Bool {
+    if a.count != b.count { return false }
+    var idx = a.startIndex
+    while idx < a.endIndex {
+        if !equal_Q(a[idx], b[idx]) { return false }
+        idx = idx.successor()
+    }
+    return true
+}
+
+func equal_Q(a: MalVal, _ b: MalVal) -> Bool {
+    switch (a, b) {
+    case (MV.MalNil, MV.MalNil): return true
+    case (MV.MalFalse, MV.MalFalse): return true
+    case (MV.MalTrue, MV.MalTrue): return true
+    case (MV.MalInt(let i1), MV.MalInt(let i2)): return i1 == i2
+    case (MV.MalString(let s1), MV.MalString(let s2)): return s1 == s2
+    case (MV.MalSymbol(let s1), MV.MalSymbol(let s2)): return s1 == s2
+    case (MV.MalList(let l1), MV.MalList(let l2)):
+        return cmp_seqs(l1, l2)
+    case (MV.MalList(let l1), MV.MalVector(let l2)):
+        return cmp_seqs(l1, l2)
+    case (MV.MalVector(let l1), MV.MalList(let l2)):
+        return cmp_seqs(l1, l2)
+    case (MV.MalVector(let l1), MV.MalVector(let l2)):
+        return cmp_seqs(l1, l2)
+    default:
+        return false
+    }
+}
+
+func rest(a: MalVal) throws -> MalVal {
+    switch a {
+    case MV.MalList(let lst):
+        let slc = lst[lst.startIndex.successor()..<lst.endIndex]
+        return MV.MalList(Array(slc))
+    default:
+        throw MalError.General(msg: "Invalid rest call")
+    }
+}