Remove gensym, inc and or from step files.
[jackhill/mal.git] / haskell / Core.hs
CommitLineData
fa9a9758
JM
1module Core
2( ns )
3where
4
5400d4bf 5import System.IO (hFlush, stdout)
6116c2d5 6import Control.Monad.Except (throwError)
5400d4bf 7import Control.Monad.Trans (liftIO)
fa9a9758 8import qualified Data.Map as Map
b091e954 9import Data.Foldable (foldlM)
c150ec41 10import Data.Time.Clock.POSIX (getPOSIXTime)
b091e954 11import Data.IORef (newIORef, readIORef, writeIORef)
fa9a9758 12
c150ec41 13import Readline (readline)
fa9a9758
JM
14import Reader (read_str)
15import Types
b091e954 16import Printer (_pr_list)
fa9a9758
JM
17
18-- General functions
19
6116c2d5
NB
20equal_Q :: Fn
21equal_Q [a, b] = return $ MalBoolean $ a == b
5400d4bf 22equal_Q _ = throwStr "illegal arguments to ="
fa9a9758 23
5400d4bf
JM
24-- Error/Exception functions
25
6116c2d5
NB
26throw :: Fn
27throw [mv] = throwError mv
5400d4bf 28throw _ = throwStr "illegal arguments to throw"
c150ec41 29
6116c2d5
NB
30-- Unary predicates
31
32pred1 :: (MalVal -> Bool) -> Fn
33pred1 hostPred [x] = return $ MalBoolean $ hostPred x
34pred1 _ _ = throwStr "illegal call to unary predicate"
35
36atom_Q :: MalVal -> Bool
37atom_Q (MalAtom _ _) = True
38atom_Q _ = False
39
40false_Q :: MalVal -> Bool
41false_Q (MalBoolean False) = True
42false_Q _ = False
43
44fn_Q :: MalVal -> Bool
45fn_Q (MalFunction {macro=False}) = True
46fn_Q _ = False
47
48macro_Q :: MalVal -> Bool
49macro_Q (MalFunction {macro=True}) = True
50macro_Q _ = False
51
52map_Q :: MalVal -> Bool
53map_Q (MalHashMap _ _) = True
54map_Q _ = False
55
56keyword_Q :: MalVal -> Bool
57keyword_Q (MalString (c : _)) = c == keywordMagic
58keyword_Q _ = False
59
60list_Q :: MalVal -> Bool
61list_Q (MalSeq _ (Vect False) _) = True
62list_Q _ = False
63
64nil_Q :: MalVal -> Bool
65nil_Q Nil = True
66nil_Q _ = False
67
68number_Q :: MalVal -> Bool
69number_Q (MalNumber _) = True
70number_Q _ = False
71
72string_Q :: MalVal -> Bool
73string_Q (MalString "") = True
74string_Q (MalString (c : _)) = c /= keywordMagic
75string_Q _ = False
76
77symbol_Q :: MalVal -> Bool
78symbol_Q (MalSymbol _) = True
79symbol_Q _ = False
80
81true_Q :: MalVal -> Bool
82true_Q (MalBoolean True) = True
83true_Q _ = False
84
85vector_Q :: MalVal -> Bool
86vector_Q (MalSeq _ (Vect True) _) = True
87vector_Q _ = False
88
c150ec41
JM
89-- Scalar functions
90
6116c2d5 91symbol :: Fn
b091e954 92symbol [MalString s] = return $ MalSymbol s
5400d4bf 93symbol _ = throwStr "symbol called with non-string"
c150ec41 94
6116c2d5
NB
95keyword :: Fn
96keyword [kw@(MalString (c : _))] | c == keywordMagic = return kw
97keyword [MalString s] = return $ MalString (keywordMagic : s)
5400d4bf 98keyword _ = throwStr "keyword called with non-string"
2988d38e 99
fa9a9758
JM
100-- String functions
101
6116c2d5 102pr_str :: Fn
87cb47ec 103pr_str args = liftIO $ MalString <$> _pr_list True " " args
fa9a9758 104
6116c2d5 105str :: Fn
87cb47ec 106str args = liftIO $ MalString <$> _pr_list False "" args
fa9a9758 107
6116c2d5 108prn :: Fn
87cb47ec
NB
109prn args = liftIO $ do
110 putStrLn =<< _pr_list True " " args
111 hFlush stdout
fa9a9758
JM
112 return Nil
113
6116c2d5 114println :: Fn
87cb47ec
NB
115println args = liftIO $ do
116 putStrLn =<< _pr_list False " " args
117 hFlush stdout
fa9a9758
JM
118 return Nil
119
6116c2d5
NB
120slurp :: Fn
121slurp [MalString path] = MalString <$> liftIO (readFile path)
5400d4bf 122slurp _ = throwStr "invalid arguments to slurp"
c150ec41 123
6116c2d5
NB
124do_readline :: Fn
125do_readline [MalString prompt] = do
b091e954
NB
126 maybeLine <- liftIO $ readline prompt
127 case maybeLine of
5400d4bf 128 Nothing -> throwStr "readline failed"
b091e954 129 Just line -> return $ MalString line
5400d4bf 130do_readline _ = throwStr "invalid arguments to readline"
fa9a9758 131
6116c2d5 132read_string :: Fn
b091e954
NB
133read_string [MalString s] = read_str s
134read_string _ = throwStr "invalid read-string"
135
fa9a9758
JM
136-- Numeric functions
137
6116c2d5
NB
138num_op :: (Int -> Int -> Int) -> Fn
139num_op op [MalNumber a, MalNumber b] = return $ MalNumber $ op a b
5400d4bf 140num_op _ _ = throwStr "illegal arguments to number operation"
c150ec41 141
6116c2d5
NB
142cmp_op :: (Int -> Int -> Bool) -> Fn
143cmp_op op [MalNumber a, MalNumber b] = return $ MalBoolean $ op a b
5400d4bf 144cmp_op _ _ = throwStr "illegal arguments to comparison operation"
fa9a9758 145
6116c2d5
NB
146time_ms :: Fn
147time_ms [] = MalNumber . round . (* 1000) <$> liftIO getPOSIXTime
148time_ms _ = throwStr "invalid time-ms"
fa9a9758
JM
149
150
151-- List functions
152
6116c2d5
NB
153list :: Fn
154list = return . toList
fa9a9758
JM
155
156-- Vector functions
157
6116c2d5
NB
158vector :: Fn
159vector = return . MalSeq (MetaData Nil) (Vect True)
fa9a9758
JM
160
161-- Hash Map functions
162
6116c2d5
NB
163hash_map :: Fn
164hash_map kvs =
165 case keyValuePairs kvs of
166 Just pairs -> return $ MalHashMap (MetaData Nil) $ Map.fromList pairs
167 Nothing -> throwStr "invalid call to hash-map"
168
169assoc :: Fn
170assoc (MalHashMap _ hm : kvs) =
171 case keyValuePairs kvs of
172 Just pairs -> return $ MalHashMap (MetaData Nil) $ Map.union (Map.fromList pairs) hm
173 Nothing -> throwStr "invalid assoc"
5400d4bf 174assoc _ = throwStr "invalid call to assoc"
c150ec41 175
6116c2d5
NB
176remover :: Map.Map String MalVal -> MalVal -> IOThrows (Map.Map String MalVal)
177remover m (MalString k) = return $ Map.delete k m
178remover _ _ = throwStr "invalid dissoc"
179
180dissoc :: Fn
181dissoc (MalHashMap _ hm : ks) = MalHashMap (MetaData Nil) <$> foldlM remover hm ks
5400d4bf 182dissoc _ = throwStr "invalid call to dissoc"
c150ec41 183
6116c2d5
NB
184get :: Fn
185get [MalHashMap _ hm, MalString k] =
c150ec41
JM
186 case Map.lookup k hm of
187 Just mv -> return mv
188 Nothing -> return Nil
b091e954 189get [Nil, MalString _] = return Nil
5400d4bf 190get _ = throwStr "invalid call to get"
c150ec41 191
6116c2d5
NB
192contains_Q :: Fn
193contains_Q [MalHashMap _ hm, MalString k] = return $ MalBoolean $ Map.member k hm
194contains_Q [Nil, MalString _] = return $ MalBoolean False
5400d4bf 195contains_Q _ = throwStr "invalid call to contains?"
c150ec41 196
6116c2d5
NB
197keys :: Fn
198keys [MalHashMap _ hm] = return $ toList $ MalString <$> Map.keys hm
5400d4bf 199keys _ = throwStr "invalid call to keys"
c150ec41 200
6116c2d5
NB
201vals :: Fn
202vals [MalHashMap _ hm] = return $ toList $ Map.elems hm
5400d4bf 203vals _ = throwStr "invalid call to vals"
c150ec41 204
fa9a9758
JM
205-- Sequence functions
206
6116c2d5
NB
207sequential_Q :: MalVal -> Bool
208sequential_Q (MalSeq _ _ _) = True
209sequential_Q _ = False
210
211cons :: Fn
212cons [x, Nil ] = return $ toList [x]
213cons [x, MalSeq _ _ lst] = return $ toList (x : lst)
214cons _ = throwStr "illegal call to cons"
215
216unwrapSeq :: MalVal -> IOThrows [MalVal]
217unwrapSeq (MalSeq _ _ xs) = return xs
218unwrapSeq _ = throwStr "invalid concat"
219
220do_concat :: Fn
221do_concat args = toList . concat <$> mapM unwrapSeq args
222
223nth :: Fn
44c06139
NB
224nth [MalSeq _ _ lst, MalNumber idx] =
225 case drop idx lst of
226 x : _ -> return x
227 [] -> throwStr "nth: index out of range"
228-- See https://wiki.haskell.org/Avoiding_partial_functions
229nth _ = throwStr "invalid call to nth"
6116c2d5
NB
230
231first :: Fn
232first [Nil ] = return Nil
233first [MalSeq _ _ [] ] = return Nil
234first [MalSeq _ _ (x : _)] = return x
235first _ = throwStr "illegal call to first"
236
237rest :: Fn
238rest [Nil ] = return $ toList []
239rest [MalSeq _ _ [] ] = return $ toList []
240rest [MalSeq _ _ (_ : xs)] = return $ toList xs
241rest _ = throwStr "illegal call to rest"
242
243empty_Q :: MalVal -> Bool
244empty_Q Nil = True
245empty_Q (MalSeq _ _ []) = True
246empty_Q _ = False
247
248count :: Fn
249count [Nil ] = return $ MalNumber 0
250count [MalSeq _ _ lst] = return $ MalNumber $ length lst
251count _ = throwStr "non-sequence passed to count"
252
253concatLast :: [MalVal] -> IOThrows [MalVal]
254concatLast [MalSeq _ _ lst] = return lst
255concatLast (a : as) = (a :) <$> concatLast as
256concatLast _ = throwStr "last argument of apply must be a sequence"
257
258apply :: Fn
259apply (MalFunction {fn=f} : xs) = f =<< concatLast xs
260apply _ = throwStr "Illegal call to apply"
261
262do_map :: Fn
263do_map [MalFunction {fn=f}, MalSeq _ _ args] = toList <$> mapM (\x -> f [x]) args
264do_map _ = throwStr "Illegal call to map"
265
266conj :: Fn
267conj (MalSeq _ (Vect False) lst : args) = return $ toList $ reverse args ++ lst
268conj (MalSeq _ (Vect True) lst : args) = return $ MalSeq (MetaData Nil) (Vect True) $ lst ++ args
269conj _ = throwStr "illegal arguments to conj"
270
271do_seq :: Fn
272do_seq [Nil ] = return Nil
273do_seq [MalSeq _ _ [] ] = return Nil
274do_seq [MalSeq _ _ lst ] = return $ toList lst
275do_seq [MalString "" ] = return Nil
276do_seq [MalString s ] = return $ toList $ MalString <$> pure <$> s
277do_seq _ = throwStr "seq: called on non-sequence"
defa41f3 278
c150ec41
JM
279-- Metadata functions
280
6116c2d5
NB
281with_meta :: Fn
282with_meta [MalSeq _ v x, m] = return $ MalSeq (MetaData m) v x
283with_meta [MalHashMap _ x, m] = return $ MalHashMap (MetaData m) x
284with_meta [MalAtom _ x, m] = return $ MalAtom (MetaData m) x
285with_meta [f@(MalFunction {}), m] = return $ f {meta=m}
286with_meta _ = throwStr "invalid with-meta call"
287
288do_meta :: Fn
289do_meta [MalSeq (MetaData m) _ _ ] = return m
290do_meta [MalHashMap (MetaData m) _] = return m
291do_meta [MalAtom (MetaData m) _ ] = return m
292do_meta [MalFunction {meta=m} ] = return m
293do_meta _ = throwStr "invalid meta call"
c150ec41
JM
294
295-- Atom functions
296
6116c2d5
NB
297atom :: Fn
298atom [val] = MalAtom (MetaData Nil) <$> liftIO (newIORef val)
5400d4bf 299atom _ = throwStr "invalid atom call"
c150ec41 300
6116c2d5
NB
301deref :: Fn
302deref [MalAtom _ ref] = liftIO $ readIORef ref
5400d4bf 303deref _ = throwStr "invalid deref call"
c150ec41 304
6116c2d5
NB
305reset_BANG :: Fn
306reset_BANG [MalAtom _ ref, val] = do
5400d4bf 307 liftIO $ writeIORef ref $ val
c150ec41 308 return val
b091e954 309reset_BANG _ = throwStr "invalid reset!"
c150ec41 310
6116c2d5
NB
311swap_BANG :: Fn
312swap_BANG (MalAtom _ ref : MalFunction {fn=f} : args) = do
5400d4bf 313 val <- liftIO $ readIORef ref
6116c2d5
NB
314 new_val <- f (val : args)
315 liftIO $ writeIORef ref new_val
c150ec41 316 return new_val
6116c2d5 317swap_BANG _ = throwStr "Illegal swap!"
fa9a9758 318
6116c2d5 319ns :: [(String, Fn)]
fa9a9758 320ns = [
6116c2d5
NB
321 ("=", equal_Q),
322 ("throw", throw),
323 ("nil?", pred1 nil_Q),
324 ("true?", pred1 true_Q),
325 ("false?", pred1 false_Q),
326 ("string?", pred1 string_Q),
327 ("symbol", symbol),
328 ("symbol?", pred1 symbol_Q),
329 ("keyword", keyword),
330 ("keyword?", pred1 keyword_Q),
331 ("number?", pred1 number_Q),
332 ("fn?", pred1 fn_Q),
333 ("macro?", pred1 macro_Q),
334
335 ("pr-str", pr_str),
336 ("str", str),
337 ("prn", prn),
338 ("println", println),
339 ("readline", do_readline),
340 ("read-string", read_string),
341 ("slurp", slurp),
342
343 ("<", cmp_op (<)),
344 ("<=", cmp_op (<=)),
345 (">", cmp_op (>)),
346 (">=", cmp_op (>=)),
347 ("+", num_op (+)),
348 ("-", num_op (-)),
349 ("*", num_op (*)),
350 ("/", num_op (div)),
351 ("time-ms", time_ms),
352
353 ("list", list),
354 ("list?", pred1 list_Q),
355 ("vector", vector),
356 ("vector?", pred1 vector_Q),
357 ("hash-map", hash_map),
358 ("map?", pred1 map_Q),
359 ("assoc", assoc),
360 ("dissoc", dissoc),
361 ("get", get),
362 ("contains?", contains_Q),
363 ("keys", keys),
364 ("vals", vals),
365
366 ("sequential?", pred1 sequential_Q),
367 ("cons", cons),
368 ("concat", do_concat),
369 ("nth", nth),
370 ("first", first),
371 ("rest", rest),
372 ("empty?", pred1 empty_Q),
373 ("count", count),
374 ("apply", apply),
375 ("map", do_map),
376
377 ("conj", conj),
378 ("seq", do_seq),
379
380 ("with-meta", with_meta),
381 ("meta", do_meta),
382 ("atom", atom),
383 ("atom?", pred1 atom_Q),
384 ("deref", deref),
385 ("reset!", reset_BANG),
386 ("swap!", swap_BANG)]