Change quasiquote algorithm
[jackhill/mal.git] / impls / swift4 / Sources / step7_quote / main.swift
CommitLineData
bdda2112 1
2import Foundation
3
4func READ(_ input: String) throws -> MalData {
5 return try read_str(input)
6}
7
fbfe6784
NB
8func starts_with(_ ast: MalData, _ sym: String) -> MalData? {
9 if let list = ast as? [MalData],
10 2 == list.count,
11 let a0 = list[0] as? Symbol,
12 a0.name == sym {
13 return list[1]
14 } else {
15 return nil
bdda2112 16 }
fbfe6784
NB
17}
18
19func qqIter(_ lst: [MalData]) -> MalData {
20 var result:MalData = []
21 for elt in lst.reversed() {
22 if let x = starts_with(elt, "splice-unquote") {
23 result = [Symbol("concat"), x, result]
24 } else {
25 result = [Symbol("cons"), quasiquote(elt), result]
bdda2112 26 }
fbfe6784
NB
27 }
28 return result
29}
30
31func quasiquote(_ ast: MalData) -> MalData {
32 switch ast.dataType {
33 case .List:
34 if let x = starts_with(ast, "unquote") {
35 return x
36 } else {
37 return qqIter (ast.listForm)
bdda2112 38 }
fbfe6784
NB
39 case .Vector:
40 return [Symbol("vec"), qqIter (ast.listForm)]
41 case .Symbol:
42 return [Symbol("quote"), ast]
43 case .HashMap:
44 return [Symbol("quote"), ast]
45 default:
46 return ast
bdda2112 47 }
fbfe6784 48}
bdda2112 49
fbfe6784 50func EVAL(_ anAst: MalData, env anEnv: Env) throws -> MalData {
bdda2112 51 var ast = anAst, env = anEnv
52 while true {
53 switch ast.dataType {
54 case .List:
55 let list = ast as! [MalData]
56 guard !list.isEmpty else { return list }
57 if let sym = list[0] as? Symbol {
58 switch sym.name {
59 case "def!":
60 let value = try EVAL(list[2], env: env), key = list[1] as! Symbol
61 env.set(value, forKey: key)
62 return value
63 case "let*":
64 let newEnv = Env(outer: env), expr = list[2]
65 let bindings = list[1].listForm
66 for i in stride(from: 0, to: bindings.count-1, by: 2) {
67 let key = bindings[i], value = bindings[i+1]
68 let result = try EVAL(value, env: newEnv)
69 newEnv.set(result, forKey: key as! Symbol)
70 }
71 env = newEnv
72 ast = expr
73 continue
74 case "do":
75 try _ = list.dropFirst().dropLast().map { try EVAL($0, env: env) }
76 ast = list.last ?? Nil()
77 continue
78 case "if":
79 let predicate = try EVAL(list[1], env: env)
80 if predicate as? Bool == false || predicate is Nil {
81 ast = list.count>3 ? list[3] : Nil()
82 } else {
83 ast = list[2]
84 }
85 continue
90ca8485 86 case "fn*":
bdda2112 87 let fn = {(params: [MalData]) -> MalData in
88 let newEnv = Env(binds: (list[1].listForm as! [Symbol]), exprs: params, outer: env)
89 return try EVAL(list[2], env: newEnv)
90ca8485 90 }
bdda2112 91 return Function(ast: list[2], params: (list[1].listForm as! [Symbol]), env:env , fn: fn)
92 case "quote":
93 return list[1]
fbfe6784
NB
94 case "quasiquoteexpand":
95 return quasiquote(list[1])
bdda2112 96 case "quasiquote":
97 ast = quasiquote(list[1])
98 continue
99 default:
100 break
101 }
102 }
103 // not a symbol. maybe: function, list, or some wrong type
104 let evaluated = try eval_ast(list, env: env) as! [MalData]
105 guard let function = evaluated[0] as? Function else {
106 throw MalError.SymbolNotFound(list[0] as? Symbol ?? Symbol("Symbol"))
107 }
108 if let fnAst = function.ast { // a full fn
109 ast = fnAst
110 env = Env(binds: function.params!, exprs: evaluated.dropFirst().listForm, outer: function.env!)
111 } else { // normal function
112 return try function.fn(evaluated.dropFirst().listForm)
113 }
114 continue
115 case .Vector:
116 let vector = ast as! ContiguousArray<MalData>
117 return try ContiguousArray(vector.map { element in try EVAL(element, env: env) })
118 case .HashMap:
119 let hashMap = ast as! HashMap<String, MalData>
120 return try hashMap.mapValues { value in try EVAL(value, env: env) }
121 default:
122 return try eval_ast(ast, env: env)
123 }
124 }
125}
126
127func PRINT(_ input: MalData) -> String {
128 return pr_str(input, print_readably: true)
129}
130
131@discardableResult func rep(_ input: String, env: Env) throws -> String {
132 return try PRINT(EVAL(READ(input), env: env))
133}
134
135func eval_ast(_ ast: MalData, env: Env) throws -> MalData {
136 switch ast.dataType {
137 case .Symbol:
138 let sym = ast as! Symbol
139 if let function = try? env.get(forKey: sym) {
140 return function
141 } else {
142 throw MalError.SymbolNotFound(sym)
143 }
144 case .List:
145 let list = ast as! [MalData]
146 return try list.map { element in try EVAL(element, env: env) }
147 case .Atom:
148 return (ast as! Atom).value
149 default:
150 return ast
151 }
152}
153
154
155
156var repl_env = Env()
157for (key, value) in ns {
158 repl_env.set(Function(fn: value), forKey: Symbol(key))
159}
160repl_env.set(Function(fn: { try EVAL($0[0], env: repl_env) }), forKey: Symbol("eval"))
161repl_env.set([], forKey: Symbol("*ARGV*"))
162
163try rep("(def! not (fn* (a) (if a false true)))", env: repl_env)
e6d41de4 164try rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", env: repl_env)
bdda2112 165
166if CommandLine.argc > 1 {
167 let fileName = CommandLine.arguments[1],
168 args = List(CommandLine.arguments.dropFirst(2))
169 repl_env.set(args, forKey: Symbol("*ARGV*"))
170 try rep("(load-file \"\(fileName)\")", env: repl_env)
171 exit(0)
172}
173
174while true {
175 print("user> ", terminator: "")
176 if let input = readLine(strippingNewline: true) {
177 guard input != "" else { continue }
178 do {
179 try print(rep(input, env: repl_env))
180 } catch let error as MalError {
181 print(error.info())
182 }
df6c9561
JM
183 } else {
184 exit(0);
bdda2112 185 }
186}