Commit | Line | Data |
---|---|---|
5400d4bf | 1 | import System.IO (hFlush, stdout) |
2988d38e | 2 | import System.Environment (getArgs) |
5400d4bf | 3 | import Control.Monad (mapM) |
53db2d63 | 4 | import Control.Monad.Except (runExceptT) |
5400d4bf | 5 | import Control.Monad.Trans (liftIO) |
6116c2d5 | 6 | import Data.Foldable (foldlM, foldrM) |
2988d38e JM |
7 | |
8 | import Readline (readline, load_history) | |
9 | import Types | |
10 | import Reader (read_str) | |
11 | import Printer (_pr_str) | |
52371c3e | 12 | import Env (env_new, env_bind, env_get, env_set) |
6116c2d5 | 13 | import Core (ns) |
2988d38e JM |
14 | |
15 | -- read | |
6116c2d5 | 16 | |
5400d4bf | 17 | mal_read :: String -> IOThrows MalVal |
6116c2d5 | 18 | mal_read = read_str |
2988d38e JM |
19 | |
20 | -- eval | |
2988d38e | 21 | |
6116c2d5 | 22 | -- starts-with is replaced with pattern matching. |
53db2d63 | 23 | |
6116c2d5 NB |
24 | qqIter :: Env -> MalVal -> [MalVal] -> IOThrows [MalVal] |
25 | qqIter env (MalSeq _ (Vect False) [MalSymbol "splice-unquote", x]) acc = do | |
26 | evaluated <- eval env x | |
27 | case evaluated of | |
28 | MalSeq _ (Vect False) xs -> return $ xs ++ acc | |
29 | _ -> throwStr "invalid splice-unquote argument" | |
30 | qqIter _ (MalSeq _ (Vect False) (MalSymbol "splice-unquote" : _)) _ = throwStr "invalid splice-unquote" | |
31 | qqIter env x acc = (: acc) <$> quasiquote x env | |
2988d38e | 32 | |
6116c2d5 NB |
33 | quasiquote :: MalVal -> Env -> IOThrows MalVal |
34 | quasiquote (MalSeq _ (Vect False) [MalSymbol "unquote", x]) env = eval env x | |
52371c3e NB |
35 | -- FIXME This line |
36 | quasiquote (MalSeq m _ ys) env = MalSeq m (Vect False) <$> foldrM (qqIter env) [] ys | |
37 | -- is adapted to broken tests. It should be: | |
38 | -- quasiquote (MalSeq m v ys) env = MalSeq m v <$> foldrM (qqIter env) [] ys | |
6116c2d5 NB |
39 | quasiquote ast _ = return ast |
40 | ||
41 | -- eval_ast is replaced with pattern matching. | |
42 | ||
43 | let_bind :: Env -> [MalVal] -> IOThrows () | |
44 | let_bind _ [] = return () | |
45 | let_bind env (MalSymbol b : e : xs) = do | |
46 | liftIO . env_set env b =<< eval env e | |
47 | let_bind env xs | |
48 | let_bind _ _ = throwStr "invalid let*" | |
49 | ||
50 | unWrapSymbol :: MalVal -> IOThrows String | |
51 | unWrapSymbol (MalSymbol s) = return s | |
52 | unWrapSymbol _ = throwStr "fn* parameter must be symbols" | |
53 | ||
54 | newFunction :: MalVal -> Env -> [String] -> MalVal | |
55 | newFunction a env p = MalFunction {f_ast=a, f_params=p, macro=False, meta=Nil, | |
56 | fn=(\args -> do | |
57 | fn_env <- liftIO $ env_new env | |
58 | ok <- liftIO $ env_bind fn_env p args | |
59 | case ok of | |
60 | True -> eval fn_env a | |
61 | False -> throwStr $ "actual parameters do not match signature " ++ show p)} | |
62 | ||
63 | apply_ast :: [MalVal] -> Env -> IOThrows MalVal | |
64 | ||
65 | apply_ast [] _ = return $ toList [] | |
66 | ||
67 | apply_ast [MalSymbol "def!", MalSymbol a1, a2] env = do | |
68 | evd <- eval env a2 | |
69 | liftIO $ env_set env a1 evd | |
70 | return evd | |
71 | apply_ast (MalSymbol "def!" : _) _ = throwStr "invalid def!" | |
72 | ||
73 | apply_ast [MalSymbol "let*", MalSeq _ _ params, a2] env = do | |
74 | let_env <- liftIO $ env_new env | |
75 | let_bind let_env params | |
76 | eval let_env a2 | |
77 | apply_ast (MalSymbol "let*" : _) _ = throwStr "invalid let*" | |
78 | ||
79 | apply_ast [MalSymbol "quote", a1] _ = return a1 | |
80 | apply_ast (MalSymbol "quote" : _) _ = throwStr "invalid quote" | |
81 | ||
82 | apply_ast [MalSymbol "quasiquote", a1] env = quasiquote a1 env | |
83 | apply_ast (MalSymbol "quasiquote" : _) _ = throwStr "invalid quasiquote" | |
84 | ||
85 | apply_ast (MalSymbol "do" : args) env = foldlM (const $ eval env) Nil args | |
86 | ||
87 | apply_ast [MalSymbol "if", a1, a2, a3] env = do | |
88 | cond <- eval env a1 | |
89 | eval env $ case cond of | |
90 | Nil -> a3 | |
91 | MalBoolean False -> a3 | |
92 | _ -> a2 | |
93 | apply_ast [MalSymbol "if", a1, a2] env = do | |
94 | cond <- eval env a1 | |
95 | case cond of | |
96 | Nil -> return Nil | |
97 | MalBoolean False -> return Nil | |
98 | _ -> eval env a2 | |
99 | apply_ast (MalSymbol "if" : _) _ = throwStr "invalid if" | |
100 | ||
101 | apply_ast [MalSymbol "fn*", MalSeq _ _ params, ast] env = newFunction ast env <$> mapM unWrapSymbol params | |
102 | apply_ast (MalSymbol "fn*" : _) _ = throwStr "invalid fn*" | |
103 | ||
104 | apply_ast ast env = do | |
105 | evd <- mapM (eval env) ast | |
106 | case evd of | |
107 | MalFunction {fn=f} : args -> f args | |
108 | _ -> throwStr $ "invalid apply: " ++ Printer._pr_str True (toList ast) | |
109 | ||
110 | eval :: Env -> MalVal -> IOThrows MalVal | |
111 | eval env (MalSymbol sym) = do | |
112 | maybeVal <- liftIO $ env_get env sym | |
113 | case maybeVal of | |
114 | Nothing -> throwStr $ "'" ++ sym ++ "' not found" | |
115 | Just val -> return val | |
116 | eval env (MalSeq _ (Vect False) xs) = apply_ast xs env | |
117 | eval env (MalSeq m (Vect True) xs) = MalSeq m (Vect True) <$> mapM (eval env) xs | |
118 | eval env (MalHashMap m xs) = MalHashMap m <$> mapM (eval env) xs | |
119 | eval _ ast = return ast | |
2988d38e JM |
120 | |
121 | ||
6116c2d5 | 122 | |
2988d38e | 123 | mal_print :: MalVal -> String |
6116c2d5 | 124 | mal_print = Printer._pr_str True |
2988d38e JM |
125 | |
126 | -- repl | |
127 | ||
5400d4bf | 128 | rep :: Env -> String -> IOThrows String |
6116c2d5 | 129 | rep env line = mal_print <$> (eval env =<< mal_read line) |
2988d38e JM |
130 | |
131 | repl_loop :: Env -> IO () | |
132 | repl_loop env = do | |
133 | line <- readline "user> " | |
134 | case line of | |
135 | Nothing -> return () | |
136 | Just "" -> repl_loop env | |
137 | Just str -> do | |
53db2d63 | 138 | res <- runExceptT $ rep env str |
5400d4bf | 139 | out <- case res of |
6116c2d5 | 140 | Left mv -> return $ "Error: " ++ Printer._pr_str True mv |
5400d4bf | 141 | Right val -> return val |
2988d38e | 142 | putStrLn out |
5400d4bf | 143 | hFlush stdout |
2988d38e JM |
144 | repl_loop env |
145 | ||
6116c2d5 NB |
146 | -- Read and evaluate a line. Ignore successful results, but crash in |
147 | -- case of error. This is intended for the startup procedure. | |
148 | re :: Env -> String -> IO () | |
149 | re repl_env line = do | |
150 | res <- runExceptT $ eval repl_env =<< mal_read line | |
151 | case res of | |
152 | Left mv -> error $ "Startup failed: " ++ Printer._pr_str True mv | |
153 | Right _ -> return () | |
154 | ||
155 | defBuiltIn :: Env -> (String, Fn) -> IO () | |
156 | defBuiltIn env (sym, f) = | |
157 | env_set env sym $ MalFunction {fn=f, f_ast=Nil, f_params=[], macro=False, meta=Nil} | |
158 | ||
159 | evalFn :: Env -> Fn | |
160 | evalFn env [ast] = eval env ast | |
161 | evalFn _ _ = throwStr "illegal call of eval" | |
162 | ||
163 | main :: IO () | |
2988d38e JM |
164 | main = do |
165 | args <- getArgs | |
166 | load_history | |
167 | ||
6116c2d5 | 168 | repl_env <- env_new [] |
2988d38e JM |
169 | |
170 | -- core.hs: defined using Haskell | |
6116c2d5 NB |
171 | mapM_ (defBuiltIn repl_env) Core.ns |
172 | defBuiltIn repl_env ("eval", evalFn repl_env) | |
2988d38e JM |
173 | |
174 | -- core.mal: defined using the language itself | |
6116c2d5 NB |
175 | re repl_env "(def! not (fn* (a) (if a false true)))" |
176 | re repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" | |
177 | ||
178 | case args of | |
179 | script : scriptArgs -> do | |
180 | env_set repl_env "*ARGV*" $ toList $ MalString <$> scriptArgs | |
181 | re repl_env $ "(load-file \"" ++ script ++ "\")" | |
182 | [] -> do | |
183 | env_set repl_env "*ARGV*" $ toList [] | |
184 | repl_loop repl_env |