Last active
March 1, 2019 16:43
-
-
Save ocramz/39b60daedc4ae3bec5df to your computer and use it in GitHub Desktop.
sch48h_ch4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Main where | |
import Control.Monad | |
import Text.ParserCombinators.Parsec hiding (spaces) | |
import System.Environment | |
import Control.Monad.Error | |
data LispVal = Atom String | |
| List [LispVal] | |
| DottedList [LispVal] LispVal | |
| Number Integer | |
| String String | |
| Bool Bool | |
data LispError = NumArgs Integer [LispVal] | |
| TypeMismatch String LispVal | |
| Parser ParseError | |
| BadSpecialForm String LispVal | |
| NotFunction String String | |
| UnboundVar String String | |
| Default String | |
instance Show LispVal where show = showVal | |
parseString :: Parser LispVal | |
parseString = do | |
char '"' | |
x <- many (noneOf "\" ") | |
char '"' | |
return $ String x | |
parseExpr :: Parser LispVal | |
parseExpr = parseAtom | |
<|> parseString | |
<|> parseNumber | |
<|> parseQuoted | |
<|> do char '(' | |
x <- (try parseList) <|> parseDottedList | |
char ')' | |
return x | |
parseAtom :: Parser LispVal | |
parseAtom = do | |
first <- letter <|> symbol | |
rest <- many (letter <|> digit <|> symbol) | |
let atom = [first] ++ rest | |
return $ case atom of | |
"#t" -> Bool True | |
"#f" -> Bool False | |
_ -> Atom atom | |
parseNumber :: Parser LispVal | |
parseNumber = liftM (Number . read) $ many1 digit | |
parseList :: Parser LispVal | |
parseList = liftM List $ sepBy parseExpr spaces | |
parseDottedList :: Parser LispVal | |
parseDottedList = do | |
head <- endBy parseExpr spaces | |
tail <- char '.' >> spaces >> parseExpr | |
return $ DottedList head tail | |
parseQuoted :: Parser LispVal | |
parseQuoted = do | |
char '\'' | |
x <- parseExpr | |
return $ List [Atom "quote", x] | |
symbol :: Parser Char | |
symbol = oneOf "!#$%&|*+-/:<=>?@^_~" | |
readExpr :: String -> ThrowsError LispVal | |
readExpr input = case parse parseExpr "lisp" input of | |
Left err -> throwError $ Parser err | |
Right val -> return val | |
spaces :: Parser () | |
spaces = skipMany1 space | |
showVal :: LispVal -> String | |
showVal (String c) = "\"" ++ c ++ "\"" | |
showVal (Atom n) = n | |
showVal (Number c) = show c | |
showVal (Bool True) = "#t" | |
showVal (Bool False) = "#f" | |
showVal (List c) = "(" ++ unwordsList c ++ ")" | |
showVal (DottedList h t) = "(" ++ unwordsList h ++ " . " ++ showVal t ++ ")" | |
unwordsList :: [LispVal] -> String | |
unwordsList = unwords . map showVal | |
eval :: LispVal -> ThrowsError LispVal | |
eval val@(String _) = return val | |
eval val@(Number _) = return val | |
eval val@(Bool _) = return val | |
eval (List [Atom "quote", val]) = return val | |
eval (List (Atom func : args)) = mapM eval args >>= apply func | |
eval badForm = throwError $ BadSpecialForm "Unrecognized special form" badForm | |
apply :: String -> [LispVal] -> ThrowsError LispVal | |
apply func args = maybe (throwError $ NotFunction "Unrecognized primitive function args " func) ($ args) (lookup func primitives) | |
primitives :: [(String, [LispVal] -> ThrowsError LispVal)] | |
primitives = [("+", numericBinop (+)), | |
("-", numericBinop (-)), | |
("*", numericBinop (*)), | |
("/", numericBinop div), | |
("mod", numericBinop mod), | |
("quotient", numericBinop quot), | |
("remainder", numericBinop rem)] | |
numericBinop :: (Integer -> Integer -> Integer) -> [LispVal] -> ThrowsError LispVal | |
numericBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal | |
numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op | |
unpackNum :: LispVal -> ThrowsError Integer | |
unpackNum (Number n) = return n | |
unpackNum (String n) = let parsed = reads n in | |
if null parsed | |
then throwError $ TypeMismatch "number" $ String n | |
else return $ fst $ parsed !! 0 | |
unpackNum (List [n]) = unpackNum n | |
unpackNum notNum = throwError $ TypeMismatch "number" notNum | |
showError :: LispError -> String | |
showError (UnboundVar msg varname) = msg ++ ": " ++ varname | |
showError (BadSpecialForm msg frm) = msg ++ ": " ++ show frm | |
showError (NotFunction msg f) = msg ++ ": " ++ show f | |
showError (NumArgs expected found) = "Expected " ++ show expected ++ " args: found values " ++ unwordsList found | |
showError (TypeMismatch expected found) = "Invalid type: expected " ++ expected ++ ", found " ++ show found | |
showError (ParseError pe) = "Parse error at " ++ show pe | |
instance Show LispError where show = showError | |
instance Error LispError where | |
noMsg = Default "An error has occurred" | |
strMsg = Default | |
type ThrowsError = Either LispError | |
trapError act = catchError act (return . show) | |
extractValue :: ThrowsError a -> a | |
extractValue (Right val) = val | |
main :: IO () | |
main = do | |
args <- getArgs | |
evaled <- return $ liftM show $ readExpr (args !! 0) >>= eval | |
putStrLn $ extractValue $ trapError evaled |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment