Merge pull request #377 from asarhaddon/fix-runtests-pre-eval
[jackhill/mal.git] / haskell / stepA_mal.hs
CommitLineData
5400d4bf 1import System.IO (hFlush, stdout)
c150ec41 2import System.Environment (getArgs)
5400d4bf 3import Control.Monad (mapM)
53db2d63 4import Control.Monad.Except (runExceptT)
5400d4bf 5import Control.Monad.Trans (liftIO)
c150ec41
JM
6import qualified Data.Map as Map
7import qualified Data.Traversable as DT
8
9import Readline (readline, load_history)
10import Types
11import Reader (read_str)
12import Printer (_pr_str)
13import Env (Env, env_new, env_bind, env_find, env_get, env_set)
14import Core as Core
15
16-- read
5400d4bf 17mal_read :: String -> IOThrows MalVal
c150ec41
JM
18mal_read str = read_str str
19
20-- eval
21is_pair (MalList x _:xs) = True
22is_pair (MalVector x _:xs) = True
23is_pair _ = False
24
25quasiquote :: MalVal -> MalVal
26quasiquote ast =
27 case ast of
28 (MalList (MalSymbol "unquote" : a1 : []) _) -> a1
29 (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
30 MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil
31 (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) ->
32 MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil
33 (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"),
34 quasiquote a0,
35 quasiquote (MalList rest Nil)] Nil
36 (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"),
37 quasiquote a0,
38 quasiquote (MalVector rest Nil)] Nil
39 _ -> MalList [(MalSymbol "quote"), ast] Nil
40
5400d4bf 41is_macro_call :: MalVal -> Env -> IOThrows Bool
c150ec41 42is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do
5400d4bf 43 e <- liftIO $ env_find env a0
c150ec41
JM
44 case e of
45 Just e -> do
46 f <- env_get e a0
47 case f of
48 MalFunc {macro=True} -> return True
49 _ -> return False
50 Nothing -> return False
51is_macro_call _ _ = return False
52
5400d4bf 53macroexpand :: MalVal -> Env -> IOThrows MalVal
c150ec41
JM
54macroexpand ast@(MalList (a0 : args) _) env = do
55 mc <- is_macro_call ast env
56 if mc then do
57 mac <- env_get env a0
53db2d63 58 case mac of
c150ec41
JM
59 MalFunc {fn=(Fn f)} -> do
60 new_ast <- f args
61 macroexpand new_ast env
62 _ ->
63 return ast
64 else
65 return ast
66macroexpand ast _ = return ast
67
5400d4bf 68eval_ast :: MalVal -> Env -> IOThrows MalVal
c150ec41
JM
69eval_ast sym@(MalSymbol _) env = env_get env sym
70eval_ast ast@(MalList lst m) env = do
71 new_lst <- mapM (\x -> (eval x env)) lst
72 return $ MalList new_lst m
73eval_ast ast@(MalVector lst m) env = do
74 new_lst <- mapM (\x -> (eval x env)) lst
75 return $ MalVector new_lst m
76eval_ast ast@(MalHashMap lst m) env = do
77 new_hm <- DT.mapM (\x -> (eval x env)) lst
78 return $ MalHashMap new_hm m
79eval_ast ast env = return ast
80
5400d4bf 81let_bind :: Env -> [MalVal] -> IOThrows Env
c150ec41
JM
82let_bind env [] = return env
83let_bind env (b:e:xs) = do
84 evaled <- eval e env
5400d4bf 85 x <- liftIO $ env_set env b evaled
c150ec41
JM
86 let_bind env xs
87
5400d4bf 88apply_ast :: MalVal -> Env -> IOThrows MalVal
cffab551
DM
89apply_ast ast@(MalList [] _) env = do
90 return ast
c150ec41
JM
91apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
92 case args of
93 (a1@(MalSymbol _): a2 : []) -> do
94 evaled <- eval a2 env
5400d4bf
JM
95 liftIO $ env_set env a1 evaled
96 _ -> throwStr "invalid def!"
c150ec41
JM
97apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
98 case args of
99 (a1 : a2 : []) -> do
100 params <- (_to_list a1)
5400d4bf 101 let_env <- liftIO $ env_new $ Just env
c150ec41
JM
102 let_bind let_env params
103 eval a2 let_env
5400d4bf 104 _ -> throwStr "invalid let*"
c150ec41
JM
105apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do
106 case args of
107 a1 : [] -> return a1
5400d4bf 108 _ -> throwStr "invalid quote"
c150ec41
JM
109apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
110 case args of
111 a1 : [] -> eval (quasiquote a1) env
5400d4bf 112 _ -> throwStr "invalid quasiquote"
c150ec41
JM
113
114apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do
115 case args of
116 (a1 : a2 : []) -> do
117 func <- eval a2 env
118 case func of
119 MalFunc {fn=f, ast=a, env=e, params=p} -> do
120 let new_func = MalFunc {fn=f, ast=a, env=e,
121 params=p, macro=True,
122 meta=Nil} in
5400d4bf
JM
123 liftIO $ env_set env a1 new_func
124 _ -> throwStr "defmacro! on non-function"
53db2d63 125 _ -> throwStr "invalid defmacro!"
c150ec41
JM
126apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do
127 case args of
128 (a1 : []) -> macroexpand a1 env
53db2d63 129 _ -> throwStr "invalid macroexpand"
5400d4bf
JM
130apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do
131 case args of
132 (a1 : []) -> eval a1 env
133 (a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do
53db2d63 134 res <- liftIO $ runExceptT $ eval a1 env
5400d4bf
JM
135 case res of
136 Right val -> return val
137 Left err -> do
138 exc <- case err of
139 (StringError str) -> return $ MalString str
140 (MalValError mv) -> return $ mv
141 try_env <- liftIO $ env_new $ Just env
142 liftIO $ env_set try_env a21 exc
143 eval a22 try_env
144 _ -> throwStr "invalid try*"
c150ec41
JM
145apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
146 case args of
147 ([]) -> return Nil
148 _ -> do
149 el <- eval_ast (MalList args Nil) env
150 case el of
151 (MalList lst _) -> return $ last lst
53db2d63 152
c150ec41
JM
153apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
154 case args of
155 (a1 : a2 : a3 : []) -> do
156 cond <- eval a1 env
157 if cond == MalFalse || cond == Nil
158 then eval a3 env
159 else eval a2 env
160 (a1 : a2 : []) -> do
161 cond <- eval a1 env
162 if cond == MalFalse || cond == Nil
163 then return Nil
164 else eval a2 env
5400d4bf 165 _ -> throwStr "invalid if"
c150ec41
JM
166apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
167 case args of
168 (a1 : a2 : []) -> do
169 params <- (_to_list a1)
170 return $ (_malfunc a2 env (MalList params Nil)
171 (\args -> do
5400d4bf
JM
172 fn_env1 <- liftIO $ env_new $ Just env
173 fn_env2 <- liftIO $ env_bind fn_env1 params args
c150ec41 174 eval a2 fn_env2))
5400d4bf 175 _ -> throwStr "invalid fn*"
c150ec41
JM
176apply_ast ast@(MalList _ _) env = do
177 mc <- is_macro_call ast env
178 if mc then do
179 new_ast <- macroexpand ast env
180 eval new_ast env
181 else
182 case ast of
183 MalList _ _ -> do
184 el <- eval_ast ast env
185 case el of
186 (MalList ((Func (Fn f) _) : rest) _) ->
187 f $ rest
188 (MalList ((MalFunc {ast=ast,
189 env=fn_env,
190 params=(MalList params Nil)} : rest)) _) -> do
5400d4bf
JM
191 fn_env1 <- liftIO $ env_new $ Just fn_env
192 fn_env2 <- liftIO $ env_bind fn_env1 params rest
c150ec41
JM
193 eval ast fn_env2
194 el ->
5400d4bf 195 throwStr $ "invalid apply: " ++ (show el)
c150ec41
JM
196 _ -> return ast
197
5400d4bf 198eval :: MalVal -> Env -> IOThrows MalVal
c150ec41
JM
199eval ast env = do
200 case ast of
201 (MalList _ _) -> apply_ast ast env
202 _ -> eval_ast ast env
203
204
205-- print
206mal_print :: MalVal -> String
207mal_print exp = show exp
208
209-- repl
210
5400d4bf 211rep :: Env -> String -> IOThrows String
c150ec41
JM
212rep env line = do
213 ast <- mal_read line
214 exp <- eval ast env
215 return $ mal_print exp
216
217repl_loop :: Env -> IO ()
218repl_loop env = do
219 line <- readline "user> "
220 case line of
221 Nothing -> return ()
222 Just "" -> repl_loop env
223 Just str -> do
53db2d63 224 res <- runExceptT $ rep env str
5400d4bf
JM
225 out <- case res of
226 Left (StringError str) -> return $ "Error: " ++ str
227 Left (MalValError mv) -> return $ "Error: " ++ (show mv)
228 Right val -> return val
c150ec41 229 putStrLn out
5400d4bf 230 hFlush stdout
c150ec41
JM
231 repl_loop env
232
233main = do
234 args <- getArgs
235 load_history
236
237 repl_env <- env_new Nothing
238
239 -- core.hs: defined using Haskell
240 (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns)
241 env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env))
242 env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil)
243
244 -- core.mal: defined using the language itself
53db2d63
P
245 runExceptT $ rep repl_env "(def! *host-language* \"haskell\")"
246 runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
247 runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
248 runExceptT $ rep repl_env "(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)))))))"
14ab099c
NB
249 runExceptT $ rep repl_env "(def! inc (fn* [x] (+ x 1)))"
250 runExceptT $ rep repl_env "(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))"
53db2d63 251 runExceptT $ rep repl_env "(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)))))))))"
c150ec41
JM
252
253 if length args > 0 then do
254 env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
53db2d63 255 runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
c150ec41
JM
256 return ()
257 else do
53db2d63 258 runExceptT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))"
c150ec41 259 repl_loop repl_env