haskell: adapt quasiquote to existing test, drop redundant import
[jackhill/mal.git] / haskell / step7_quote.hs
CommitLineData
5400d4bf 1import System.IO (hFlush, stdout)
2988d38e 2import System.Environment (getArgs)
5400d4bf 3import Control.Monad (mapM)
53db2d63 4import Control.Monad.Except (runExceptT)
5400d4bf 5import Control.Monad.Trans (liftIO)
6116c2d5 6import Data.Foldable (foldlM, foldrM)
2988d38e
JM
7
8import Readline (readline, load_history)
9import Types
10import Reader (read_str)
11import Printer (_pr_str)
52371c3e 12import Env (env_new, env_bind, env_get, env_set)
6116c2d5 13import Core (ns)
2988d38e
JM
14
15-- read
6116c2d5 16
5400d4bf 17mal_read :: String -> IOThrows MalVal
6116c2d5 18mal_read = read_str
2988d38e
JM
19
20-- eval
2988d38e 21
6116c2d5 22-- starts-with is replaced with pattern matching.
53db2d63 23
6116c2d5
NB
24qqIter :: Env -> MalVal -> [MalVal] -> IOThrows [MalVal]
25qqIter 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"
30qqIter _ (MalSeq _ (Vect False) (MalSymbol "splice-unquote" : _)) _ = throwStr "invalid splice-unquote"
31qqIter env x acc = (: acc) <$> quasiquote x env
2988d38e 32
6116c2d5
NB
33quasiquote :: MalVal -> Env -> IOThrows MalVal
34quasiquote (MalSeq _ (Vect False) [MalSymbol "unquote", x]) env = eval env x
52371c3e
NB
35-- FIXME This line
36quasiquote (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
39quasiquote ast _ = return ast
40
41-- eval_ast is replaced with pattern matching.
42
43let_bind :: Env -> [MalVal] -> IOThrows ()
44let_bind _ [] = return ()
45let_bind env (MalSymbol b : e : xs) = do
46 liftIO . env_set env b =<< eval env e
47 let_bind env xs
48let_bind _ _ = throwStr "invalid let*"
49
50unWrapSymbol :: MalVal -> IOThrows String
51unWrapSymbol (MalSymbol s) = return s
52unWrapSymbol _ = throwStr "fn* parameter must be symbols"
53
54newFunction :: MalVal -> Env -> [String] -> MalVal
55newFunction 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
63apply_ast :: [MalVal] -> Env -> IOThrows MalVal
64
65apply_ast [] _ = return $ toList []
66
67apply_ast [MalSymbol "def!", MalSymbol a1, a2] env = do
68 evd <- eval env a2
69 liftIO $ env_set env a1 evd
70 return evd
71apply_ast (MalSymbol "def!" : _) _ = throwStr "invalid def!"
72
73apply_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
77apply_ast (MalSymbol "let*" : _) _ = throwStr "invalid let*"
78
79apply_ast [MalSymbol "quote", a1] _ = return a1
80apply_ast (MalSymbol "quote" : _) _ = throwStr "invalid quote"
81
82apply_ast [MalSymbol "quasiquote", a1] env = quasiquote a1 env
83apply_ast (MalSymbol "quasiquote" : _) _ = throwStr "invalid quasiquote"
84
85apply_ast (MalSymbol "do" : args) env = foldlM (const $ eval env) Nil args
86
87apply_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
93apply_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
99apply_ast (MalSymbol "if" : _) _ = throwStr "invalid if"
100
101apply_ast [MalSymbol "fn*", MalSeq _ _ params, ast] env = newFunction ast env <$> mapM unWrapSymbol params
102apply_ast (MalSymbol "fn*" : _) _ = throwStr "invalid fn*"
103
104apply_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
110eval :: Env -> MalVal -> IOThrows MalVal
111eval 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
116eval env (MalSeq _ (Vect False) xs) = apply_ast xs env
117eval env (MalSeq m (Vect True) xs) = MalSeq m (Vect True) <$> mapM (eval env) xs
118eval env (MalHashMap m xs) = MalHashMap m <$> mapM (eval env) xs
119eval _ ast = return ast
2988d38e
JM
120
121-- print
6116c2d5 122
2988d38e 123mal_print :: MalVal -> String
6116c2d5 124mal_print = Printer._pr_str True
2988d38e
JM
125
126-- repl
127
5400d4bf 128rep :: Env -> String -> IOThrows String
6116c2d5 129rep env line = mal_print <$> (eval env =<< mal_read line)
2988d38e
JM
130
131repl_loop :: Env -> IO ()
132repl_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.
148re :: Env -> String -> IO ()
149re 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
155defBuiltIn :: Env -> (String, Fn) -> IO ()
156defBuiltIn env (sym, f) =
157 env_set env sym $ MalFunction {fn=f, f_ast=Nil, f_params=[], macro=False, meta=Nil}
158
159evalFn :: Env -> Fn
160evalFn env [ast] = eval env ast
161evalFn _ _ = throwStr "illegal call of eval"
162
163main :: IO ()
2988d38e
JM
164main = 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