1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 01:28:26 +03:00

haskell: silent GHC warnings

Make all patterns exhaustive, report more invalid calls.

Change some types in order to simplify the code:
- String instead of MalVal for Env keys
- IOThrows instead of IO for env_bind, env_set and let_bind.

Use record syntactic sugar for MalFunc instead of hand-written
constructor. Remove unused env component, instead use the fn component
when the function is executed.

Give a type signature to each function.

Fix error reporting for invalid reset!.

Avoid name clashes.
This commit is contained in:
Nicolas Boulenguez 2019-06-30 00:46:25 +02:00
parent 608a88851b
commit b091e9541d
8 changed files with 245 additions and 180 deletions

View File

@ -3,19 +3,20 @@ module Core
where
import System.IO (hFlush, stdout)
import Control.Exception (catch)
import Control.Monad.Trans (liftIO)
import qualified Data.Map as Map
import Data.Foldable (foldlM)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.IORef (newIORef, readIORef, writeIORef)
import Readline (readline)
import Reader (read_str)
import Types
import Printer (_pr_str, _pr_list)
import Printer (_pr_list)
-- General functions
equal_Q :: [MalVal] -> IOThrows MalVal
equal_Q [a, b] = return $ if a == b then MalTrue else MalFalse
equal_Q _ = throwStr "illegal arguments to ="
@ -23,65 +24,77 @@ run_1 :: (MalVal -> MalVal) -> [MalVal] -> IOThrows MalVal
run_1 f (x:[]) = return $ f x
run_1 _ _ = throwStr "function takes a single argument"
run_2 :: (MalVal -> MalVal -> MalVal) -> [MalVal] -> IOThrows MalVal
run_2 f (x:y:[]) = return $ f x y
run_2 _ _ = throwStr "function takes a two arguments"
-- Error/Exception functions
throw :: [MalVal] -> IOThrows MalVal
throw (mv:[]) = throwMalVal mv
throw _ = throwStr "illegal arguments to throw"
-- Scalar functions
symbol (MalString str:[]) = return $ MalSymbol str
symbol :: [MalVal] -> IOThrows MalVal
symbol [MalString s] = return $ MalSymbol s
symbol _ = throwStr "symbol called with non-string"
keyword (MalString ('\x029e':str):[]) = return $ MalString $ "\x029e" ++ str
keyword (MalString str:[]) = return $ MalString $ "\x029e" ++ str
keyword :: [MalVal] -> IOThrows MalVal
keyword [k@(MalString ('\x029e' : _))] = return k
keyword [MalString s] = return $ MalString $ '\x029e' : s
keyword _ = throwStr "keyword called with non-string"
-- String functions
pr_str :: [MalVal] -> IOThrows MalVal
pr_str args = do
return $ MalString $ _pr_list True " " args
str :: [MalVal] -> IOThrows MalVal
str args = do
return $ MalString $ _pr_list False "" args
prn :: [MalVal] -> IOThrows MalVal
prn args = do
liftIO $ putStrLn $ _pr_list True " " args
liftIO $ hFlush stdout
return Nil
println :: [MalVal] -> IOThrows MalVal
println args = do
liftIO $ putStrLn $ _pr_list False " " args
liftIO $ hFlush stdout
return Nil
slurp :: [MalVal] -> IOThrows MalVal
slurp ([MalString path]) = do
str <- liftIO $ readFile path
return $ MalString str
contents <- liftIO $ readFile path
return $ MalString contents
slurp _ = throwStr "invalid arguments to slurp"
do_readline :: [MalVal] -> IOThrows MalVal
do_readline ([MalString prompt]) = do
str <- liftIO $ readline prompt
case str of
maybeLine <- liftIO $ readline prompt
case maybeLine of
Nothing -> throwStr "readline failed"
Just str -> return $ MalString str
Just line -> return $ MalString line
do_readline _ = throwStr "invalid arguments to readline"
read_string :: [MalVal] -> IOThrows MalVal
read_string [MalString s] = read_str s
read_string _ = throwStr "invalid read-string"
-- Numeric functions
num_op :: (Int -> Int -> Int) -> [MalVal] -> IOThrows MalVal
num_op op [MalNumber a, MalNumber b] = do
return $ MalNumber $ op a b
num_op _ _ = throwStr "illegal arguments to number operation"
cmp_op :: (Int -> Int -> Bool) -> [MalVal] -> IOThrows MalVal
cmp_op op [MalNumber a, MalNumber b] = do
return $ if op a b then MalTrue else MalFalse
cmp_op _ _ = throwStr "illegal arguments to comparison operation"
time_ms :: [MalVal] -> IOThrows MalVal
time_ms _ = do
t <- liftIO $ getPOSIXTime
return $ MalNumber $ round (t * 1000)
@ -89,51 +102,63 @@ time_ms _ = do
-- List functions
list :: [MalVal] -> IOThrows MalVal
list args = return $ MalList args Nil
-- Vector functions
vector :: [MalVal] -> IOThrows MalVal
vector args = return $ MalVector args Nil
-- Hash Map functions
_pairup [x] = throwStr "Odd number of elements to _pairup"
_pairup :: [MalVal] -> IOThrows [(String, MalVal)]
_pairup [] = return []
_pairup (MalString x:y:xs) = do
rest <- _pairup xs
return $ (x,y):rest
pairs <- _pairup xs
return $ (x,y):pairs
_pairup _ = throwStr "invalid hash-map or assoc"
hash_map :: [MalVal] -> IOThrows MalVal
hash_map args = do
pairs <- _pairup args
return $ MalHashMap (Map.fromList pairs) Nil
assoc :: [MalVal] -> IOThrows MalVal
assoc (MalHashMap hm _:kvs) = do
pairs <- _pairup kvs
return $ MalHashMap (Map.union (Map.fromList pairs) hm) Nil
assoc _ = throwStr "invalid call to assoc"
dissoc :: [MalVal] -> IOThrows MalVal
dissoc (MalHashMap hm _:ks) = do
let remover = (\hm (MalString k) -> Map.delete k hm) in
return $ MalHashMap (foldl remover hm ks) Nil
let remover acc (MalString k) = return $ Map.delete k acc
remover _ _ = throwStr "invalid dissoc"
newMap <- foldlM remover hm ks
return $ MalHashMap newMap Nil
dissoc _ = throwStr "invalid call to dissoc"
get :: [MalVal] -> IOThrows MalVal
get (MalHashMap hm _:MalString k:[]) = do
case Map.lookup k hm of
Just mv -> return mv
Nothing -> return Nil
get (Nil:MalString k:[]) = return Nil
get [Nil, MalString _] = return Nil
get _ = throwStr "invalid call to get"
contains_Q :: [MalVal] -> IOThrows MalVal
contains_Q (MalHashMap hm _:MalString k:[]) = do
if Map.member k hm then return MalTrue
else return MalFalse
contains_Q (Nil:MalString k:[]) = return MalFalse
contains_Q [Nil, MalString _] = return MalFalse
contains_Q _ = throwStr "invalid call to contains?"
keys :: [MalVal] -> IOThrows MalVal
keys (MalHashMap hm _:[]) = do
return $ MalList (map MalString (Map.keys hm)) Nil
keys _ = throwStr "invalid call to keys"
vals :: [MalVal] -> IOThrows MalVal
vals (MalHashMap hm _:[]) = do
return $ MalList (Map.elems hm) Nil
vals _ = throwStr "invalid call to vals"
@ -141,18 +166,28 @@ vals _ = throwStr "invalid call to vals"
-- Sequence functions
_sequential_Q :: MalVal -> MalVal
_sequential_Q (MalList _ _) = MalTrue
_sequential_Q (MalVector _ _) = MalTrue
_sequential_Q _ = MalFalse
cons x Nil = MalList [x] Nil
cons x (MalList lst _) = MalList (x:lst) Nil
cons x (MalVector lst _) = MalList (x:lst) Nil
cons :: [MalVal] -> IOThrows MalVal
cons [x, Nil ] = return (MalList [x] Nil)
cons [x, MalList lst _] = return (MalList (x : lst) Nil)
cons [x, MalVector lst _] = return (MalList (x : lst) Nil)
cons _ = throwStr "invalid cons"
concat1 a (MalList lst _) = a ++ lst
concat1 a (MalVector lst _) = a ++ lst
do_concat args = return $ MalList (foldl concat1 [] args) Nil
concat1 :: [MalVal] -> MalVal -> IOThrows [MalVal]
concat1 a (MalList lst _) = return $ a ++ lst
concat1 a (MalVector lst _) = return $ a ++ lst
concat1 _ _ = throwStr "invalid concat"
do_concat :: [MalVal] -> IOThrows MalVal
do_concat args = do
xs <- foldlM concat1 [] args
return $ MalList xs Nil
nth :: [MalVal] -> IOThrows MalVal
nth ((MalList lst _):(MalNumber idx):[]) = do
if idx < length lst then return $ lst !! idx
else throwStr "nth: index out of range"
@ -161,41 +196,53 @@ nth ((MalVector lst _):(MalNumber idx):[]) = do
else throwStr "nth: index out of range"
nth _ = throwStr "invalid call to nth"
first Nil = Nil
first (MalList lst _) = if length lst > 0 then lst !! 0 else Nil
first (MalVector lst _) = if length lst > 0 then lst !! 0 else Nil
first :: [MalVal] -> IOThrows MalVal
first [Nil ] = return Nil
first [MalList [] _ ] = return Nil
first [MalVector [] _ ] = return Nil
first [MalList (x : _) _] = return x
first [MalVector (x : _) _] = return x
first _ = throwStr "invalid first"
rest Nil = MalList [] Nil
rest (MalList lst _) = MalList (drop 1 lst) Nil
rest (MalVector lst _) = MalList (drop 1 lst) Nil
rest :: [MalVal] -> IOThrows MalVal
rest [Nil ] = return $ MalList [] Nil
rest [MalList (_ : xs) _] = return $ MalList xs Nil
rest [MalVector (_ : xs) _] = return $ MalList xs Nil
rest _ = throwStr "invalid rest"
empty_Q :: MalVal -> MalVal
empty_Q Nil = MalTrue
empty_Q (MalList [] _) = MalTrue
empty_Q (MalVector [] _) = MalTrue
empty_Q _ = MalFalse
count :: [MalVal] -> IOThrows MalVal
count (Nil:[]) = return $ MalNumber 0
count (MalList lst _:[]) = return $ MalNumber $ length lst
count (MalVector lst _:[]) = return $ MalNumber $ length lst
count _ = throwStr $ "non-sequence passed to count"
apply :: [MalVal] -> IOThrows MalVal
apply args = do
f <- _get_call args
lst <- _to_list (last args)
f $ (init (drop 1 args)) ++ lst
do_map :: [MalVal] -> IOThrows MalVal
do_map args = do
f <- _get_call args
lst <- _to_list (args !! 1)
do new_lst <- mapM (\x -> f [x]) lst
return $ MalList new_lst Nil
conj :: [MalVal] -> IOThrows MalVal
conj ((MalList lst _):args) = return $ MalList ((reverse args) ++ lst) Nil
conj ((MalVector lst _):args) = return $ MalVector (lst ++ args) Nil
conj _ = throwStr $ "illegal arguments to conj"
do_seq (l@(MalList [] _):[]) = return $ Nil
do_seq (l@(MalList lst m):[]) = return $ l
do_seq :: [MalVal] -> IOThrows MalVal
do_seq [MalList [] _] = return Nil
do_seq [l@(MalList _ _)] = return l
do_seq (MalVector [] _:[]) = return $ Nil
do_seq (MalVector lst _:[]) = return $ MalList lst Nil
do_seq (MalString []:[]) = return $ Nil
@ -205,15 +252,16 @@ do_seq _ = throwStr $ "seq: called on non-sequence"
-- Metadata functions
with_meta :: [MalVal] -> IOThrows MalVal
with_meta ((MalList lst _):m:[]) = return $ MalList lst m
with_meta ((MalVector lst _):m:[]) = return $ MalVector lst m
with_meta ((MalHashMap hm _):m:[]) = return $ MalHashMap hm m
with_meta ((MalAtom atm _):m:[]) = return $ MalAtom atm m
with_meta ((Func f _):m:[]) = return $ Func f m
with_meta ((MalFunc {fn=f, ast=a, env=e, params=p, macro=mc}):m:[]) = do
return $ MalFunc {fn=f, ast=a, env=e, params=p, macro=mc, meta=m}
with_meta [f@(MalFunc {}), m] = return $ f {meta=m}
with_meta _ = throwStr $ "invalid with-meta call"
do_meta :: [MalVal] -> IOThrows MalVal
do_meta ((MalList _ m):[]) = return m
do_meta ((MalVector _ m):[]) = return m
do_meta ((MalHashMap _ m):[]) = return m
@ -224,28 +272,34 @@ do_meta _ = throwStr $ "invalid meta call"
-- Atom functions
atom :: [MalVal] -> IOThrows MalVal
atom (val:[]) = do
ref <- liftIO $ newIORef val
return $ MalAtom ref Nil
atom _ = throwStr "invalid atom call"
deref :: [MalVal] -> IOThrows MalVal
deref (MalAtom ref _:[]) = do
val <- liftIO $ readIORef ref
return val
deref _ = throwStr "invalid deref call"
reset_BANG :: [MalVal] -> IOThrows MalVal
reset_BANG (MalAtom ref _:val:[]) = do
liftIO $ writeIORef ref $ val
return val
reset_BANG _ = throwStr "invalid deref call"
reset_BANG _ = throwStr "invalid reset!"
swap_BANG :: [MalVal] -> IOThrows MalVal
swap_BANG (MalAtom ref _:args) = do
val <- liftIO $ readIORef ref
f <- _get_call args
new_val <- f $ [val] ++ (tail args)
_ <- liftIO $ writeIORef ref $ new_val
return new_val
swap_BANG _ = throwStr "invalid swap!"
ns :: [(String, MalVal)]
ns = [
("=", _func equal_Q),
("throw", _func throw),
@ -266,7 +320,7 @@ ns = [
("prn", _func prn),
("println", _func println),
("readline", _func do_readline),
("read-string", _func (\[(MalString s)] -> read_str s)),
("read-string", _func read_string),
("slurp", _func slurp),
("<", _func $ cmp_op (<)),
@ -293,11 +347,11 @@ ns = [
("vals", _func $ vals),
("sequential?", _func $ run_1 _sequential_Q),
("cons", _func $ run_2 $ cons),
("cons", _func $ cons),
("concat", _func $ do_concat),
("nth", _func nth),
("first", _func $ run_1 $ first),
("rest", _func $ run_1 $ rest),
("first", _func $ first),
("rest", _func $ rest),
("empty?", _func $ run_1 $ empty_Q),
("count", _func $ count),
("apply", _func $ apply),

View File

@ -1,14 +1,13 @@
module Env
( Env, env_new, null_env, env_bind, env_find, env_get, env_set )
( Env, env_new, env_bind, env_find, env_get, env_set )
where
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.IORef (modifyIORef, newIORef, readIORef)
import Control.Monad.Trans (liftIO)
import Data.List (elemIndex)
import qualified Data.Map as Map
import Types
import Printer
-- These Env types are defined in Types module to avoid dep cycle
--data EnvData = EnvPair (Maybe Env, (Map.Map String MalVal))
@ -17,49 +16,42 @@ import Printer
env_new :: Maybe Env -> IO Env
env_new outer = newIORef $ EnvPair (outer, (Map.fromList []))
null_env = env_new Nothing
env_bind :: Env -> [MalVal] -> [MalVal] -> IO Env
env_bind :: Env -> [String] -> [MalVal] -> IO ()
env_bind envRef binds exprs = do
case (elemIndex (MalSymbol "&") binds) of
case (elemIndex "&" binds) of
Nothing -> do
-- bind binds to exprs
_ <- mapM (\(b,e) -> env_set envRef b e) $ zip binds exprs
return envRef
mapM_ (\(b,e) -> env_set envRef b e) $ zip binds exprs
Just idx -> do
-- Varargs binding
_ <- mapM (\(b,e) -> env_set envRef b e) $
zip (take idx binds) (take idx exprs)
_ <- env_set envRef (binds !! (idx + 1))
env_set envRef (binds !! (idx + 1))
(MalList (drop idx exprs) Nil)
return envRef
env_find :: Env -> MalVal -> IO (Maybe Env)
env_find envRef sym@(MalSymbol key) = do
env_find :: Env -> String -> IO (Maybe Env)
env_find envRef key = do
e <- readIORef envRef
case e of
EnvPair (o, m) -> case Map.lookup key m of
Nothing -> case o of
Nothing -> return Nothing
Just o -> env_find o sym
Just val -> return $ Just envRef
Just outer -> env_find outer key
Just _ -> return $ Just envRef
env_get :: Env -> MalVal -> IOThrows MalVal
env_get envRef sym@(MalSymbol key) = do
e1 <- liftIO $ env_find envRef sym
env_get :: Env -> String -> IOThrows MalVal
env_get envRef key = do
e1 <- liftIO $ env_find envRef key
case e1 of
Nothing -> throwStr $ "'" ++ key ++ "' not found"
Just eRef -> do
e2 <- liftIO $ readIORef eRef
case e2 of
EnvPair (o,m) -> case Map.lookup key m of
EnvPair (_, m) -> case Map.lookup key m of
Nothing -> throwStr $ "env_get error"
Just val -> return val
env_set :: Env -> MalVal -> MalVal -> IO MalVal
env_set envRef (MalSymbol key) val = do
e <- readIORef envRef
case e of
EnvPair (o,m) -> writeIORef envRef $ EnvPair (o, (Map.insert key val m))
return val
env_set :: Env -> String -> MalVal -> IO ()
env_set env key val = liftIO $ modifyIORef env f where
f (EnvPair (o, m)) = EnvPair (o, Map.insert key val m)

View File

@ -3,6 +3,7 @@ SRCS = step0_repl.hs step1_read_print.hs step2_eval.hs step3_env.hs \
step8_macros.hs step9_try.hs stepA_mal.hs
OTHER_SRCS = Readline.hs Types.hs Reader.hs Printer.hs Env.hs Core.hs
BINS = $(SRCS:%.hs=%)
ghc_flags = -Wall
#####################
@ -14,7 +15,7 @@ mal: $(word $(words $(BINS)),$(BINS))
cp $< $@
$(BINS): %: %.hs $(OTHER_SRCS)
ghc --make $< -o $@
ghc ${ghc_flags} --make $< -o $@
clean:
rm -f $(BINS) mal *.hi *.o

View File

@ -14,13 +14,15 @@ import Types
_pr_list :: Bool -> String -> [MalVal] -> String
_pr_list pr sep [] = []
_pr_list pr sep (x:[]) = (_pr_str pr x)
_pr_list _ _ [] = []
_pr_list pr _ [x] = _pr_str pr x
_pr_list pr sep (x:xs) = (_pr_str pr x) ++ sep ++ (_pr_list pr sep xs)
_flatTuples :: [(String, MalVal)] -> [MalVal]
_flatTuples ((a,b):xs) = MalString a : b : _flatTuples xs
_flatTuples _ = []
unescape :: Char -> String
unescape chr = case chr of
'\n' -> "\\n"
'\\' -> "\\\\"
@ -40,8 +42,5 @@ _pr_str pr (MalList items _) = "(" ++ (_pr_list pr " " items) ++ ")"
_pr_str pr (MalVector items _) = "[" ++ (_pr_list pr " " items) ++ "]"
_pr_str pr (MalHashMap m _) = "{" ++ (_pr_list pr " " (_flatTuples $ Map.assocs m)) ++ "}"
_pr_str pr (MalAtom r _) = "(atom " ++ (_pr_str pr (unsafePerformIO (readIORef r))) ++ ")"
_pr_str _ (Func f _) = "#<function>"
_pr_str _ (MalFunc {ast=ast, env=fn_env, params=params}) = "(fn* " ++ (show params) ++ " " ++ (show ast) ++ ")"
instance Show MalVal where show = _pr_str True
_pr_str _ (Func _ _) = "#<function>"
_pr_str _ (MalFunc {f_ast=a, f_params=p}) = "(fn* " ++ show p ++ " " ++ _pr_str True a ++ ")"

View File

@ -3,7 +3,7 @@ module Reader
where
import Text.ParserCombinators.Parsec (
Parser, parse, space, char, digit, letter, try,
Parser, parse, char, digit, letter, try,
(<|>), oneOf, noneOf, many, many1, skipMany, skipMany1, sepEndBy)
import qualified Data.Map as Map
@ -14,7 +14,7 @@ spaces = skipMany1 (oneOf ", \n")
comment :: Parser ()
comment = do
char ';'
_ <- char ';'
skipMany (noneOf "\r\n")
ignored :: Parser ()
@ -25,7 +25,7 @@ symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
escaped :: Parser Char
escaped = do
char '\\'
_ <- char '\\'
x <- oneOf "\\\"n"
case x of
'n' -> return '\n'
@ -44,9 +44,9 @@ read_negative_number = do
read_string :: Parser MalVal
read_string = do
char '"'
_ <- char '"'
x <- many (escaped <|> noneOf "\\\"")
char '"'
_ <- char '"'
return $ MalString x
read_symbol :: Parser MalVal
@ -62,7 +62,7 @@ read_symbol = do
read_keyword :: Parser MalVal
read_keyword = do
char ':'
_ <- char ':'
x <- many (letter <|> digit <|> symbol)
return $ MalString $ "\x029e" ++ x
@ -75,68 +75,69 @@ read_atom = read_number
read_list :: Parser MalVal
read_list = do
char '('
_ <- char '('
ignored
x <- sepEndBy read_form ignored
char ')'
_ <- char ')'
return $ MalList x Nil
read_vector :: Parser MalVal
read_vector = do
char '['
_ <- char '['
ignored
x <- sepEndBy read_form ignored
char ']'
_ <- char ']'
return $ MalVector x Nil
-- TODO: propagate error properly
_pairs [x] = error "Odd number of elements to _pairs"
_pairs :: [MalVal] -> [(String, MalVal)]
_pairs [] = []
_pairs (MalString x:y:xs) = (x,y):_pairs xs
_pairs _ = error "Invalid {..} hash map definition"
read_hash_map :: Parser MalVal
read_hash_map = do
char '{'
_ <- char '{'
ignored
x <- sepEndBy read_form ignored
char '}'
_ <- char '}'
return $ MalHashMap (Map.fromList $ _pairs x) Nil
-- reader macros
read_quote :: Parser MalVal
read_quote = do
char '\''
_ <- char '\''
x <- read_form
return $ MalList [MalSymbol "quote", x] Nil
read_quasiquote :: Parser MalVal
read_quasiquote = do
char '`'
_ <- char '`'
x <- read_form
return $ MalList [MalSymbol "quasiquote", x] Nil
read_splice_unquote :: Parser MalVal
read_splice_unquote = do
char '~'
char '@'
_ <- char '~'
_ <- char '@'
x <- read_form
return $ MalList [MalSymbol "splice-unquote", x] Nil
read_unquote :: Parser MalVal
read_unquote = do
char '~'
_ <- char '~'
x <- read_form
return $ MalList [MalSymbol "unquote", x] Nil
read_deref :: Parser MalVal
read_deref = do
char '@'
_ <- char '@'
x <- read_form
return $ MalList [MalSymbol "deref", x] Nil
read_with_meta :: Parser MalVal
read_with_meta = do
char '^'
_ <- char '^'
m <- read_form
x <- read_form
return $ MalList [MalSymbol "with-meta", x, m] Nil

View File

@ -11,28 +11,30 @@ import qualified System.Console.Readline as RL
import Control.Monad (when)
import System.Directory (getHomeDirectory, doesFileExist)
import System.IO (hGetLine, hFlush, hIsEOF, stdin, stdout)
import System.IO.Error (tryIOError)
history_file :: IO String
history_file = do
home <- getHomeDirectory
return $ home ++ "/.mal-history"
load_history :: IO ()
load_history = do
hfile <- history_file
fileExists <- doesFileExist hfile
when fileExists $ do
content <- readFile hfile
mapM RL.addHistory (lines content)
mapM_ RL.addHistory (lines content)
return ()
return ()
readline :: String -> IO (Maybe String)
readline prompt = do
hfile <- history_file
maybeLine <- RL.readline prompt
case maybeLine of
case maybeLine of
Just line -> do
RL.addHistory line
res <- tryIOError (appendFile hfile (line ++ "\n"))
_ <- tryIOError (appendFile hfile (line ++ "\n"))
return maybeLine
_ -> return maybeLine

View File

@ -1,14 +1,13 @@
module Types
(MalVal (..), MalError (..), IOThrows (..), Fn (..), EnvData (..), Env,
(MalVal (..), MalError (..), IOThrows, Fn (..), EnvData (..), Env,
throwStr, throwMalVal, _get_call, _to_list,
_func, _malfunc, _fn_Q, _macro_Q,
_func, _fn_Q, _macro_Q,
_nil_Q, _true_Q, _false_Q, _string_Q, _symbol_Q, _keyword_Q, _number_Q,
_list_Q, _vector_Q, _hash_map_Q, _atom_Q)
where
import Data.IORef (IORef)
import qualified Data.Map as Map
import Control.Exception as CE
import Control.Monad.Except
@ -26,12 +25,12 @@ data MalVal = Nil
| MalAtom (IORef MalVal) MalVal
| Func Fn MalVal
| MalFunc {fn :: Fn,
ast :: MalVal,
env :: Env,
params :: MalVal,
f_ast :: MalVal,
f_params :: [String],
macro :: Bool,
meta :: MalVal}
_equal_Q :: MalVal -> MalVal -> Bool
_equal_Q Nil Nil = True
_equal_Q MalFalse MalFalse = True
_equal_Q MalTrue MalTrue = True
@ -73,10 +72,12 @@ type Env = IORef EnvData
-- General functions --
_get_call :: [MalVal] -> IOThrows ([MalVal] -> IOThrows MalVal)
_get_call ((Func (Fn f) _) : _) = return f
_get_call (MalFunc {fn=(Fn f)} : _) = return f
_get_call _ = throwStr "_get_call first parameter is not a function "
_to_list :: MalVal -> IOThrows [MalVal]
_to_list (MalList lst _) = return lst
_to_list (MalVector lst _) = return lst
_to_list _ = throwStr "_to_list expected a MalList or MalVector"
@ -88,63 +89,69 @@ _to_list _ = throwStr "_to_list expected a MalList or MalVector"
-- Functions
_func fn = Func (Fn fn) Nil
_func_meta fn meta = Func (Fn fn) meta
_malfunc ast env params fn = MalFunc {fn=(Fn fn), ast=ast,
env=env, params=params,
macro=False, meta=Nil}
_malfunc_meta ast env params fn meta = MalFunc {fn=(Fn fn), ast=ast,
env=env, params=params,
macro=False, meta=meta}
_func :: ([MalVal] -> IOThrows MalVal) -> MalVal
_func f = Func (Fn f) Nil
_fn_Q :: MalVal -> MalVal
_fn_Q (MalFunc {macro=False}) = MalTrue
_fn_Q (Func _ _) = MalTrue
_fn_Q _ = MalFalse
_macro_Q :: MalVal -> MalVal
_macro_Q (MalFunc {macro=True}) = MalTrue
_macro_Q _ = MalFalse
-- Scalars
_nil_Q :: MalVal -> MalVal
_nil_Q Nil = MalTrue
_nil_Q _ = MalFalse
_true_Q :: MalVal -> MalVal
_true_Q MalTrue = MalTrue
_true_Q _ = MalFalse
_false_Q :: MalVal -> MalVal
_false_Q MalFalse = MalTrue
_false_Q _ = MalFalse
_symbol_Q :: MalVal -> MalVal
_symbol_Q (MalSymbol _) = MalTrue
_symbol_Q _ = MalFalse
_string_Q :: MalVal -> MalVal
_string_Q (MalString ('\x029e':_)) = MalFalse
_string_Q (MalString _) = MalTrue
_string_Q _ = MalFalse
_keyword_Q :: MalVal -> MalVal
_keyword_Q (MalString ('\x029e':_)) = MalTrue
_keyword_Q _ = MalFalse
_number_Q :: MalVal -> MalVal
_number_Q (MalNumber _) = MalTrue
_number_Q _ = MalFalse
-- Lists
_list_Q :: MalVal -> MalVal
_list_Q (MalList _ _) = MalTrue
_list_Q _ = MalFalse
-- Vectors
_vector_Q :: MalVal -> MalVal
_vector_Q (MalVector _ _) = MalTrue
_vector_Q _ = MalFalse
-- Hash Maps
_hash_map_Q :: MalVal -> MalVal
_hash_map_Q (MalHashMap _ _) = MalTrue
_hash_map_Q _ = MalFalse
-- Atoms
_atom_Q :: MalVal -> MalVal
_atom_Q (MalAtom _ _) = MalTrue
_atom_Q _ = MalFalse

View File

@ -3,7 +3,6 @@ import System.Environment (getArgs)
import Control.Monad (mapM)
import Control.Monad.Except (runExceptT)
import Control.Monad.Trans (liftIO)
import qualified Data.Map as Map
import qualified Data.Traversable as DT
import Readline (readline, load_history)
@ -39,9 +38,9 @@ quasiquote ast =
_ -> 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
is_macro_call (MalList ((MalSymbol a0) : _) _) env = do
maybeE <- liftIO $ env_find env a0
case maybeE of
Just e -> do
f <- env_get e a0
case f of
@ -51,7 +50,7 @@ is_macro_call (MalList (a0@(MalSymbol _) : rest) _) env = do
is_macro_call _ _ = return False
macroexpand :: MalVal -> Env -> IOThrows MalVal
macroexpand ast@(MalList (a0 : args) _) env = do
macroexpand ast@(MalList (MalSymbol a0 : args) _) env = do
mc <- is_macro_call ast env
if mc then do
mac <- env_get env a0
@ -66,71 +65,76 @@ macroexpand ast@(MalList (a0 : args) _) env = do
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
eval_ast (MalSymbol s) env = env_get env s
eval_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
eval_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
eval_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
eval_ast ast _ = return ast
let_bind :: Env -> [MalVal] -> IOThrows Env
let_bind env [] = return env
let_bind env (b:e:xs) = do
let_bind :: Env -> [MalVal] -> IOThrows ()
let_bind _ [] = return ()
let_bind env (MalSymbol b : e : xs) = do
evaled <- eval e env
x <- liftIO $ env_set env b evaled
liftIO $ env_set env b evaled
let_bind env xs
let_bind _ _ = throwStr "invalid let*"
unwrapSymbol :: MalVal -> IOThrows String
unwrapSymbol (MalSymbol s) = return s
unwrapSymbol _ = throwStr "fn* expects a sequence of symbols"
apply_ast :: MalVal -> Env -> IOThrows MalVal
apply_ast ast@(MalList [] _) env = do
apply_ast ast@(MalList [] _) _ = do
return ast
apply_ast ast@(MalList (MalSymbol "def!" : args) _) env = do
apply_ast (MalList (MalSymbol "def!" : args) _) env = do
case args of
(a1@(MalSymbol _): a2 : []) -> do
[MalSymbol a1, a2] -> do
evaled <- eval a2 env
liftIO $ env_set env a1 evaled
return evaled
_ -> throwStr "invalid def!"
apply_ast ast@(MalList (MalSymbol "let*" : args) _) env = do
apply_ast (MalList (MalSymbol "let*" : args) _) env = do
case args of
(a1 : a2 : []) -> do
params <- (_to_list a1)
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
apply_ast (MalList (MalSymbol "quote" : args) _) _ = do
case args of
a1 : [] -> return a1
_ -> throwStr "invalid quote"
apply_ast ast@(MalList (MalSymbol "quasiquote" : args) _) env = do
apply_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
apply_ast (MalList (MalSymbol "defmacro!" : args) _) env =
case args of
(a1 : a2 : []) -> do
(MalSymbol 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
MalFunc {} -> do
let new_func = func {macro=True, meta=Nil}
liftIO $ env_set env a1 new_func
return new_func
_ -> throwStr "defmacro! on non-function"
_ -> throwStr "invalid defmacro!"
apply_ast ast@(MalList (MalSymbol "macroexpand" : args) _) env = do
apply_ast (MalList (MalSymbol "macroexpand" : args) _) env = do
case args of
(a1 : []) -> macroexpand a1 env
_ -> throwStr "invalid macroexpand"
apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do
apply_ast (MalList (MalSymbol "try*" : args) _) env = do
case args of
(a1 : []) -> eval a1 env
(a1 : (MalList ((MalSymbol "catch*") : a21 : a22 : []) _) : []) -> do
[a1, MalList [MalSymbol "catch*", MalSymbol a21, a22] _] -> do
res <- liftIO $ runExceptT $ eval a1 env
case res of
Right val -> return val
@ -142,15 +146,16 @@ apply_ast ast@(MalList (MalSymbol "try*" : args) _) env = do
liftIO $ env_set try_env a21 exc
eval a22 try_env
_ -> throwStr "invalid try*"
apply_ast ast@(MalList (MalSymbol "do" : args) _) env = do
apply_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
_ -> throwStr "invalid do"
apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
apply_ast (MalList (MalSymbol "if" : args) _) env = do
case args of
(a1 : a2 : a3 : []) -> do
cond <- eval a1 env
@ -163,15 +168,16 @@ apply_ast ast@(MalList (MalSymbol "if" : args) _) env = do
then return Nil
else eval a2 env
_ -> throwStr "invalid if"
apply_ast ast@(MalList (MalSymbol "fn*" : args) _) env = do
apply_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))
symbols <- mapM unwrapSymbol params
let f xs = do
fn_env <- liftIO $ env_new $ Just env
liftIO $ env_bind fn_env symbols xs
eval a2 fn_env
return $ MalFunc {f_ast=a2, f_params=symbols, meta=Nil, macro=False, fn=Fn f}
_ -> throwStr "invalid fn*"
apply_ast ast@(MalList _ _) env = do
mc <- is_macro_call ast env
@ -185,15 +191,13 @@ apply_ast ast@(MalList _ _) env = do
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)
(MalList (MalFunc {fn=Fn f} : rest) _) ->
f rest
_ ->
throwStr $ "invalid apply: " ++ Printer._pr_str True el
_ -> return ast
apply_ast _ _ = throwStr "internal error in apply_ast"
eval :: MalVal -> Env -> IOThrows MalVal
eval ast env = do
@ -204,15 +208,15 @@ eval ast env = do
-- print
mal_print :: MalVal -> String
mal_print exp = show exp
mal_print = Printer._pr_str True
-- repl
rep :: Env -> String -> IOThrows String
rep env line = do
ast <- mal_read line
exp <- eval ast env
return $ mal_print exp
e <- eval ast env
return $ mal_print e
repl_loop :: Env -> IO ()
repl_loop env = do
@ -223,13 +227,18 @@ repl_loop env = do
Just str -> do
res <- runExceptT $ rep env str
out <- case res of
Left (StringError str) -> return $ "Error: " ++ str
Left (MalValError mv) -> return $ "Error: " ++ (show mv)
Left (StringError s) -> return $ "Error: " ++ s
Left (MalValError mv) -> return $ "Error: " ++ _pr_str True mv
Right val -> return val
putStrLn out
hFlush stdout
repl_loop env
evalBuiltIn :: Env -> [MalVal] -> IOThrows MalVal
evalBuiltIn repl_env [ast] = eval ast repl_env
evalBuiltIn _ _ = throwStr "invalid eval"
main :: IO ()
main = do
args <- getArgs
load_history
@ -237,23 +246,23 @@ main = do
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)
mapM_ (uncurry $ env_set repl_env) Core.ns
env_set repl_env "eval" (_func (evalBuiltIn repl_env))
env_set repl_env "*ARGV*" (MalList [] Nil)
-- core.mal: defined using the language itself
runExceptT $ rep repl_env "(def! *host-language* \"haskell\")"
runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
runExceptT $ 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)))))))"
runExceptT $ rep repl_env "(def! inc (fn* [x] (+ x 1)))"
runExceptT $ rep repl_env "(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))"
runExceptT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"
_ <- runExceptT $ rep repl_env "(def! *host-language* \"haskell\")"
_ <- runExceptT $ rep repl_env "(def! not (fn* (a) (if a false true)))"
_ <- runExceptT $ rep repl_env "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
_ <- runExceptT $ 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)))))))"
_ <- runExceptT $ rep repl_env "(def! inc (fn* [x] (+ x 1)))"
_ <- runExceptT $ rep repl_env "(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))"
_ <- runExceptT $ rep repl_env "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))"
if length args > 0 then do
env_set repl_env (MalSymbol "*ARGV*") (MalList (map MalString (drop 1 args)) Nil)
runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
env_set repl_env "*ARGV*" (MalList (map MalString (drop 1 args)) Nil)
_ <- runExceptT $ rep repl_env $ "(load-file \"" ++ (args !! 0) ++ "\")"
return ()
else do
runExceptT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))"
_ <- runExceptT $ rep repl_env "(println (str \"Mal [\" *host-language* \"]\"))"
repl_loop repl_env