Merge pull request #370 from asarhaddon/hide-gensym-counter
[jackhill/mal.git] / scala / stepA_mal.scala
1 import types.{MalList, _list, _list_Q, MalVector, MalHashMap,
2 Func, MalFunction}
3 import env.Env
4
5 object stepA_mal {
6 // read
7 def READ(str: String): Any = {
8 reader.read_str(str)
9 }
10
11 // eval
12 def is_pair(x: Any): Boolean = {
13 types._sequential_Q(x) && x.asInstanceOf[MalList].value.length > 0
14 }
15
16 def quasiquote(ast: Any): Any = {
17 if (!is_pair(ast)) {
18 return _list(Symbol("quote"), ast)
19 } else {
20 val a0 = ast.asInstanceOf[MalList](0)
21 if (types._symbol_Q(a0) &&
22 a0.asInstanceOf[Symbol].name == "unquote") {
23 return ast.asInstanceOf[MalList](1)
24 } else if (is_pair(a0)) {
25 val a00 = a0.asInstanceOf[MalList](0)
26 if (types._symbol_Q(a00) &&
27 a00.asInstanceOf[Symbol].name == "splice-unquote") {
28 return _list(Symbol("concat"),
29 a0.asInstanceOf[MalList](1),
30 quasiquote(ast.asInstanceOf[MalList].drop(1)))
31 }
32 }
33 return _list(Symbol("cons"),
34 quasiquote(a0),
35 quasiquote(ast.asInstanceOf[MalList].drop(1)))
36 }
37 }
38
39 def is_macro_call(ast: Any, env: Env): Boolean = {
40 ast match {
41 case ml: MalList => {
42 if (ml.value.length > 0 &&
43 types._symbol_Q(ml(0)) &&
44 env.find(ml(0).asInstanceOf[Symbol]) != null) {
45 env.get(ml(0).asInstanceOf[Symbol]) match {
46 case f: MalFunction => return f.ismacro
47 case _ => return false
48 }
49 }
50 return false
51 }
52 case _ => return false
53 }
54 }
55
56 def macroexpand(orig_ast: Any, env: Env): Any = {
57 var ast = orig_ast;
58 while (is_macro_call(ast, env)) {
59 ast.asInstanceOf[MalList].value match {
60 case f :: args => {
61 val mac = env.get(f.asInstanceOf[Symbol])
62 ast = mac.asInstanceOf[MalFunction](args)
63 }
64 case _ => throw new Exception("macroexpand: invalid call")
65 }
66 }
67 ast
68 }
69
70 def eval_ast(ast: Any, env: Env): Any = {
71 ast match {
72 case s : Symbol => env.get(s)
73 case v: MalVector => v.map(EVAL(_, env))
74 case l: MalList => l.map(EVAL(_, env))
75 case m: MalHashMap => {
76 m.map{case (k,v) => (k, EVAL(v, env))}
77 }
78 case _ => ast
79 }
80 }
81
82 def EVAL(orig_ast: Any, orig_env: Env): Any = {
83 var ast = orig_ast; var env = orig_env;
84 while (true) {
85
86 //println("EVAL: " + printer._pr_str(ast,true))
87 if (!_list_Q(ast))
88 return eval_ast(ast, env)
89
90 // apply list
91 ast = macroexpand(ast, env)
92 if (!_list_Q(ast))
93 return eval_ast(ast, env)
94
95 ast.asInstanceOf[MalList].value match {
96 case Nil => {
97 return ast
98 }
99 case Symbol("def!") :: a1 :: a2 :: Nil => {
100 return env.set(a1.asInstanceOf[Symbol], EVAL(a2, env))
101 }
102 case Symbol("let*") :: a1 :: a2 :: Nil => {
103 val let_env = new Env(env)
104 for (g <- a1.asInstanceOf[MalList].value.grouped(2)) {
105 let_env.set(g(0).asInstanceOf[Symbol],EVAL(g(1),let_env))
106 }
107 env = let_env
108 ast = a2 // continue loop (TCO)
109 }
110 case Symbol("quote") :: a1 :: Nil => {
111 return a1
112 }
113 case Symbol("quasiquote") :: a1 :: Nil => {
114 ast = quasiquote(a1) // continue loop (TCO)
115 }
116 case Symbol("defmacro!") :: a1 :: a2 :: Nil => {
117 val f = EVAL(a2, env)
118 f.asInstanceOf[MalFunction].ismacro = true
119 return env.set(a1.asInstanceOf[Symbol], f)
120 }
121 case Symbol("macroexpand") :: a1 :: Nil => {
122 return macroexpand(a1, env)
123 }
124 case Symbol("try*") :: a1 :: rest => {
125 try {
126 return EVAL(a1, env)
127 } catch {
128 case t: Throwable => {
129 if (rest.length == 0) throw t
130 rest(0).asInstanceOf[MalList].value match {
131 case List(Symbol("catch*"), a21, a22) => {
132 val exc: Any = t match {
133 case mex: types.MalException => mex.value
134 case _ => t.getMessage
135 }
136 return EVAL(a22, new Env(env,
137 List(a21).iterator,
138 List(exc).iterator))
139 }
140 }
141 throw t
142 }
143 }
144 }
145 case Symbol("do") :: rest => {
146 eval_ast(_list(rest.slice(0,rest.length-1):_*), env)
147 ast = ast.asInstanceOf[MalList].value.last // continue loop (TCO)
148 }
149 case Symbol("if") :: a1 :: a2 :: rest => {
150 val cond = EVAL(a1, env)
151 if (cond == null || cond == false) {
152 if (rest.length == 0) return null
153 ast = rest(0) // continue loop (TCO)
154 } else {
155 ast = a2 // continue loop (TCO)
156 }
157 }
158 case Symbol("fn*") :: a1 :: a2 :: Nil => {
159 return new MalFunction(a2, env, a1.asInstanceOf[MalList],
160 (args: List[Any]) => {
161 EVAL(a2, new Env(env, types._toIter(a1), args.iterator))
162 }
163 )
164 }
165 case _ => {
166 // function call
167 eval_ast(ast, env).asInstanceOf[MalList].value match {
168 case f :: el => {
169 f match {
170 case fn: MalFunction => {
171 env = fn.gen_env(el)
172 ast = fn.ast // continue loop (TCO)
173 }
174 case fn: Func => {
175 return fn(el)
176 }
177 case _ => {
178 throw new Exception("attempt to call non-function: " + f)
179 }
180 }
181 }
182 case _ => throw new Exception("invalid apply")
183 }
184 }
185 }
186 }
187 }
188
189 // print
190 def PRINT(exp: Any): String = {
191 printer._pr_str(exp, true)
192 }
193
194 // repl
195 def main(args: Array[String]) = {
196 val repl_env: Env = new Env()
197 val REP = (str: String) => PRINT(EVAL(READ(str), repl_env))
198
199 // core.scala: defined using scala
200 core.ns.map{case (k: String,v: Any) => {
201 repl_env.set(Symbol(k), new Func(v))
202 }}
203 repl_env.set(Symbol("eval"), new Func((a: List[Any]) => EVAL(a(0), repl_env)))
204 repl_env.set(Symbol("*ARGV*"), _list(args.slice(1,args.length):_*))
205
206 // core.mal: defined using the language itself
207 REP("(def! *host-language* \"scala\")")
208 REP("(def! not (fn* (a) (if a false true)))")
209 REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
210 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)))))))")
211 REP("(def! inc (fn* [x] (+ x 1)))")
212 REP("(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))")
213 REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))")
214
215
216 if (args.length > 0) {
217 REP("(load-file \"" + args(0) + "\")")
218 System.exit(0)
219 }
220
221 // repl loop
222 REP("(println (str \"Mal [\" *host-language* \"]\"))")
223 var line:String = null
224 while ({line = readLine("user> "); line != null}) {
225 try {
226 println(REP(line))
227 } catch {
228 case e : Throwable => {
229 println("Error: " + e.getMessage)
230 println(" " + e.getStackTrace.mkString("\n "))
231 }
232 }
233 }
234 }
235 }
236
237 // vim: ts=2:sw=2