1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-10 12:47:45 +03:00
mal/haskell/Reader.hs
2016-02-26 00:44:47 -08:00

164 lines
3.5 KiB
Haskell

module Reader
( read_str )
where
import Text.ParserCombinators.Parsec (
Parser, parse, space, char, digit, letter, try,
(<|>), oneOf, noneOf, many, many1, skipMany, skipMany1, sepEndBy)
import qualified Data.Map as Map
import Types
spaces :: Parser ()
spaces = skipMany1 (oneOf ", \n")
comment :: Parser ()
comment = do
char ';'
skipMany (noneOf "\r\n")
ignored :: Parser ()
ignored = skipMany (spaces <|> comment)
symbol :: Parser Char
symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
escaped :: Parser Char
escaped = do
char '\\'
x <- oneOf "\\\"n"
case x of
'n' -> return '\n'
_ -> return x
read_number :: Parser MalVal
read_number = do
x <- many1 digit
return $ MalNumber $ read x
read_negative_number :: Parser MalVal
read_negative_number = do
sign <- char '-'
rest <- many1 digit
return $ MalNumber $ read $ sign:rest
read_string :: Parser MalVal
read_string = do
char '"'
x <- many (escaped <|> noneOf "\\\"")
char '"'
return $ MalString x
read_symbol :: Parser MalVal
read_symbol = do
first <- letter <|> symbol
rest <- many (letter <|> digit <|> symbol)
let str = first:rest
return $ case str of
"true" -> MalTrue
"false" -> MalFalse
"nil" -> Nil
_ -> MalSymbol str
read_keyword :: Parser MalVal
read_keyword = do
char ':'
x <- many (letter <|> digit <|> symbol)
return $ MalString $ "\x029e" ++ x
read_atom :: Parser MalVal
read_atom = read_number
<|> try read_negative_number
<|> read_string
<|> read_keyword
<|> read_symbol
read_list :: Parser MalVal
read_list = do
char '('
x <- sepEndBy read_form ignored
char ')'
return $ MalList x Nil
read_vector :: Parser MalVal
read_vector = do
char '['
x <- sepEndBy read_form ignored
char ']'
return $ MalVector x Nil
-- TODO: propagate error properly
_pairs [x] = error "Odd number of elements to _pairs"
_pairs [] = []
_pairs (MalString x:y:xs) = (x,y):_pairs xs
read_hash_map :: Parser MalVal
read_hash_map = do
char '{'
x <- sepEndBy read_form ignored
char '}'
return $ MalHashMap (Map.fromList $ _pairs x) Nil
-- reader macros
read_quote :: Parser MalVal
read_quote = do
char '\''
x <- read_form
return $ MalList [MalSymbol "quote", x] Nil
read_quasiquote :: Parser MalVal
read_quasiquote = do
char '`'
x <- read_form
return $ MalList [MalSymbol "quasiquote", x] Nil
read_splice_unquote :: Parser MalVal
read_splice_unquote = do
char '~'
char '@'
x <- read_form
return $ MalList [MalSymbol "splice-unquote", x] Nil
read_unquote :: Parser MalVal
read_unquote = do
char '~'
x <- read_form
return $ MalList [MalSymbol "unquote", x] Nil
read_deref :: Parser MalVal
read_deref = do
char '@'
x <- read_form
return $ MalList [MalSymbol "deref", x] Nil
read_with_meta :: Parser MalVal
read_with_meta = do
char '^'
m <- read_form
x <- read_form
return $ MalList [MalSymbol "with-meta", x, m] Nil
read_macro :: Parser MalVal
read_macro = read_quote
<|> read_quasiquote
<|> try read_splice_unquote <|> read_unquote
<|> read_deref
<|> read_with_meta
--
read_form :: Parser MalVal
read_form = do
ignored
x <- read_macro
<|> read_list
<|> read_vector
<|> read_hash_map
<|> read_atom
return $ x
read_str :: String -> IOThrows MalVal
read_str str = case parse read_form "Mal" str of
Left err -> throwStr $ show err
Right val -> return val