diff options
Diffstat (limited to 'haskell/step8_macros.hs')
| -rw-r--r-- | haskell/step8_macros.hs | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/haskell/step8_macros.hs b/haskell/step8_macros.hs new file mode 100644 index 0000000..9b272e8 --- /dev/null +++ b/haskell/step8_macros.hs @@ -0,0 +1,238 @@ +import System.IO (hFlush, stdout) +import System.Environment (getArgs) +import Control.Monad (mapM) +import Control.Monad.Error (runErrorT) +import Control.Monad.Trans (liftIO) +import qualified Data.Map as Map +import qualified Data.Traversable as DT + +import Readline (readline, load_history) +import Types +import Reader (read_str) +import Printer (_pr_str) +import Env (Env, env_new, env_bind, env_find, env_get, env_set) +import Core as Core + +-- read +mal_read :: String -> IOThrows MalVal +mal_read str = read_str str + +-- eval +is_pair (MalList x _:xs) = True +is_pair (MalVector x _:xs) = True +is_pair _ = False + +quasiquote :: MalVal -> MalVal +quasiquote ast = + case ast of + (MalList (MalSymbol "unquote" : a1 : []) _) -> a1 + (MalList (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) -> + MalList [(MalSymbol "concat"), a01, quasiquote (MalList rest Nil)] Nil + (MalVector (MalList (MalSymbol "splice-unquote" : a01 : []) _ : rest) _) -> + MalList [(MalSymbol "concat"), a01, quasiquote (MalVector rest Nil)] Nil + (MalList (a0 : rest) _) -> MalList [(MalSymbol "cons"), + quasiquote a0, + quasiquote (MalList rest Nil)] Nil + (MalVector (a0 : rest) _) -> MalList [(MalSymbol "cons"), + quasiquote a0, + quasiquote (MalVector rest Nil)] Nil + _ -> MalList [(MalSymbol "quote"), ast] Nil + +is_macro_call :: MalVal -> Env -> IOThrows Bool +is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do + e <- liftIO $ env_find env a0 + case e of + Just e -> do + f <- env_get e a0 + case f of + MalFunc {macro=True} -> return True + _ -> return False + Nothing -> return False +is_macro_call _ _ = return False + +macroexpand :: MalVal -> Env -> IOThrows MalVal +macroexpand ast@(MalList (a0 : args) _) env = do + mc <- is_macro_call ast env + if mc then do + mac <- env_get env a0 + case mac of + MalFunc {fn=(Fn f)} -> do + new_ast <- f args + macroexpand new_ast env + _ -> + return ast + else + return ast +macroexpand ast _ = return ast + +eval_ast :: MalVal -> Env -> IOThrows MalVal +eval_ast sym@(MalSymbol _) env = env_get env sym +eval_ast ast@(MalList lst m) env = do + new_lst <- mapM (\x -> (eval x env)) lst + return $ MalList new_lst m +eval_ast ast@(MalVector lst m) env = do + new_lst <- mapM (\x -> (eval x env)) lst + return $ MalVector new_lst m +eval_ast ast@(MalHashMap lst m) env = do + new_hm <- DT.mapM (\x -> (eval x env)) lst + return $ MalHashMap new_hm m +eval_ast ast env = return ast + +let_bind :: Env -> [MalVal] -> IOThrows Env +let_bind env [] = return env +let_bind env (b:e:xs) = do + evaled <- eval e env + x <- liftIO $ env_set env b evaled + let_bind env xs + +apply_ast :: MalVal -> Env -> IOThrows MalVal +apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do + case args of + (a1@(MalSymbol _): a2 : []) -> do + evaled <- eval a2 env + liftIO $ env_set env a1 evaled + _ -> throwStr "invalid def!" +apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do + case args of + (a1 : a2 : []) -> do + params <- (_to_list a1) + let_env <- liftIO $ env_new $ Just env + let_bind let_env params + eval a2 let_env + _ -> throwStr "invalid let*" +apply_ast ast@(MalList (MalSymbol "quote" : args) _) env = do + case args of + a1 : [] -> return a1 + _ -> throwStr "invalid quote" +apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do + case args of + a1 : [] -> eval (quasiquote a1) env + _ -> throwStr "invalid quasiquote" + +apply_ast ast@(MalList (MalSymbol "defmacro!" : args) _) env = do + case args of + (a1 : a2 : []) -> do + func <- eval a2 env + case func of + MalFunc {fn=f, ast=a, env=e, params=p} -> do + let new_func = MalFunc {fn=f, ast=a, env=e, + params=p, macro=True, + meta=Nil} in + liftIO $ env_set env a1 new_func + _ -> throwStr "defmacro! on non-function" + _ -> throwStr "invalid defmacro!" +apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do + case args of + (a1 : []) -> macroexpand a1 env + _ -> throwStr "invalid macroexpand" +apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do + case args of + ([]) -> return Nil + _ -> do + el <- eval_ast (MalList args Nil) env + case el of + (MalList lst _) -> return $ last lst + +apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do + case args of + (a1 : a2 : a3 : []) -> do + cond <- eval a1 env + if cond == MalFalse || cond == Nil + then eval a3 env + else eval a2 env + (a1 : a2 : []) -> do + cond <- eval a1 env + if cond == MalFalse || cond == Nil + then return Nil + else eval a2 env + _ -> throwStr "invalid if" +apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do + case args of + (a1 : a2 : []) -> do + params <- (_to_list a1) + return $ (_malfunc a2 env (MalList params Nil) + (\args -> do + fn_env1 <- liftIO $ env_new $ Just env + fn_env2 <- liftIO $ env_bind fn_env1 params args + eval a2 fn_env2)) + _ -> throwStr "invalid fn*" +apply_ast ast@(MalList _ _) env = do + mc <- is_macro_call ast env + if mc then do + new_ast <- macroexpand ast env + eval new_ast env + else + case ast of + MalList _ _ -> do + el <- eval_ast ast env + case el of + (MalList ((Func (Fn f) _) : rest) _) -> + f $ rest + (MalList ((MalFunc {ast=ast, + env=fn_env, + params=(MalList params Nil)} : rest)) _) -> do + fn_env1 <- liftIO $ env_new $ Just fn_env + fn_env2 <- liftIO $ env_bind fn_env1 params rest + eval ast fn_env2 + el -> + throwStr $ "invalid apply: " ++ (show el) + _ -> return ast + +eval :: MalVal -> Env -> IOThrows MalVal +eval ast env = do + case ast of + (MalList _ _) -> apply_ast ast env + _ -> eval_ast ast env + + +-- print +mal_print :: MalVal -> String +mal_print exp = show exp + +-- repl + +rep :: Env -> String -> IOThrows String +rep env line = do + ast <- mal_read line + exp <- eval ast env + return $ mal_print exp + +repl_loop :: Env -> IO () +repl_loop env = do + line <- readline "user> " + case line of + Nothing -> return () + Just "" -> repl_loop env + Just str -> do + res <- runErrorT $ rep env str + out <- case res of + Left (StringError str) -> return $ "Error: " ++ str + Left (MalValError mv) -> return $ "Error: " ++ (show mv) + Right val -> return val + putStrLn out + hFlush stdout + repl_loop env + +main = do + args <- getArgs + load_history + + repl_env <- env_new Nothing + + -- core.hs: defined using Haskell + (mapM (\(k,v) -> (env_set repl_env (MalSymbol k) v)) Core.ns) + env_set repl_env (MalSymbol "eval") (_func (\[ast] -> eval ast repl_env)) + env_set repl_env (MalSymbol "*ARGV*") (MalList [] Nil) + + -- core.mal: defined using the language itself + runErrorT $ rep repl_env "(def! not (fn* (a) (if a false true)))" + runErrorT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" + runErrorT $ 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)))))))" + runErrorT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" + + if length args > 0 then do + env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil) + runErrorT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")" + return () + else + repl_loop repl_env |
