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