Last active
December 11, 2015 23:08
-
-
Save hiratara/4674200 to your computer and use it in GitHub Desktop.
A code copied from http://ja.wikibooks.org/wiki/48%E6%99%82%E9%96%93%E3%81%A7Scheme%E3%82%92%E6%9B%B8%E3%81%93%E3%81%86
This file contains hidden or 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
(write "Hello, scheme!") |
This file contains hidden or 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
{-# LANGUAGE ExistentialQuantification #-} | |
module Main (main) where | |
import qualified Text.ParserCombinators.Parsec as P | |
import qualified Text.ParserCombinators.Parsec.Token as T | |
import qualified System.Environment as ENV | |
import qualified Control.Monad as M | |
import qualified Control.Monad.Error as E | |
import qualified System.IO as IO | |
import qualified Data.IORef as R | |
main :: IO () | |
main = do args <- ENV.getArgs | |
if null args then runRepl else runOne $ args | |
data LispVal = Atom String | |
| List [LispVal] | |
| DottedList [LispVal] LispVal | |
| Number Integer | |
| String String | |
| Bool Bool | |
| PrimitiveFunc ([LispVal] -> ThrowsError LispVal) | |
| Func {params :: [String], vararg :: (Maybe String), | |
body :: [LispVal], closure :: Env} | |
| IOFunc ([LispVal] -> IOThrowsError LispVal) | |
| Port IO.Handle | |
showVal :: LispVal -> String | |
showVal (String contents) = "\"" ++ contents ++ "\"" | |
showVal (Atom name) = name | |
showVal (Number contents) = show contents | |
showVal (Bool True) = "#t" | |
showVal (Bool False) = "#f" | |
showVal (List contents) = "(" ++ unwordsList contents ++ ")" | |
showVal (DottedList head tail) = "(" ++ unwordsList head ++ " . " ++ showVal tail ++ ")" | |
showVal (PrimitiveFunc _) = "<primitive>" | |
showVal (Func {params = args, vararg = varargs, body = body, closure = env}) = | |
"(lambda (" ++ unwords (map show args) ++ | |
(case varargs of | |
Nothing -> "" | |
Just arg -> " . " ++ arg) ++ ") ...)" | |
showVal (IOFunc _) = "<IO primitive>" | |
showVal (Port _) = "<IO port>" | |
unwordsList :: [LispVal] -> String | |
unwordsList = unwords . map showVal | |
instance Show LispVal where show = showVal | |
data LispError = NumArgs Integer [LispVal] | |
| TypeMismatch String LispVal | |
| Parser P.ParseError | |
| BadSpecialForm String LispVal | |
| NotFunction String String | |
| UnboundVar String String | |
| Default String | |
showError :: LispError -> String | |
showError (UnboundVar message varname) = message ++ ": " ++ varname | |
showError (BadSpecialForm message form) = message ++ ": " ++ show form | |
showError (NotFunction message func) = message ++ ": " ++ show func | |
showError (NumArgs expected found) = "Expected " ++ show expected | |
++ " args; found values " ++ unwordsList found | |
showError (TypeMismatch expected found) = "Invalid type: expected " ++ expected | |
++ ", found " ++ show found | |
showError (Parser parseErr) = "Parse error at " ++ show parseErr | |
instance Show LispError where show = showError | |
instance E.Error LispError where | |
noMsg = Default "An error has occurred" | |
strMsg = Default | |
type ThrowsError = Either LispError | |
trapError :: (E.MonadError e m, Show e) => m String -> m String | |
trapError action = E.catchError action (return . show) | |
extractValue :: ThrowsError a -> a | |
extractValue (Right val) = val | |
symbol :: P.Parser Char | |
symbol = P.oneOf "!#$%&|*+-/:<=>?@^_~" | |
spaces' :: P.Parser () | |
spaces' = P.skipMany1 P.space | |
parseString :: P.Parser LispVal | |
parseString = do _ <- P.char '"' | |
x <- P.many (P.noneOf "\"") | |
_ <- P.char '"' | |
return (String x) | |
parseAtom :: P.Parser LispVal | |
parseAtom = do first <- P.letter P.<|> symbol | |
rest <- P.many (P.letter P.<|> P.digit P.<|> symbol) | |
let atom = first:rest | |
return $ case atom of | |
"#t" -> Bool True | |
"#f" -> Bool False | |
_ -> Atom atom | |
parseNumber :: P.Parser LispVal | |
parseNumber = M.liftM (Number . read) $ P.many1 P.digit | |
parseList :: P.Parser LispVal | |
parseList = M.liftM List $ P.sepBy parseExpr P.spaces | |
parseDottedList :: P.Parser LispVal | |
parseDottedList = do | |
head <- P.endBy parseExpr spaces' | |
tail <- P.char '.' >> spaces' >> parseExpr | |
return $ DottedList head tail | |
parseQuoted :: P.Parser LispVal | |
parseQuoted = do | |
P.char '\'' | |
x <- parseExpr | |
return $ List [Atom "quote", x] | |
parseExpr :: P.Parser LispVal | |
parseExpr = parseAtom P.<|> parseString P.<|> parseNumber | |
P.<|> parseQuoted | |
P.<|> do _ <- P.char '(' | |
x <- P.try parseList P.<|> parseDottedList | |
_ <- P.char ')' | |
return x | |
readOrThrow :: P.Parser a -> String -> ThrowsError a | |
readOrThrow parser input = case P.parse parser "lisp" input of | |
Left err -> E.throwError $ Parser err | |
Right val -> return val | |
readExpr :: String -> ThrowsError LispVal | |
readExpr = readOrThrow parseExpr | |
readExprList :: String -> ThrowsError [LispVal] | |
readExprList = readOrThrow (P.endBy parseExpr spaces') | |
eval :: Env -> LispVal -> IOThrowsError LispVal | |
eval _ val@(String _) = return val | |
eval _ val@(Number _) = return val | |
eval _ val@(Bool _) = return val | |
eval env (Atom id) = getVar env id | |
eval _ (List [Atom "quote", val]) = return val | |
eval env (List [Atom "if", pred, conseq, alt]) = | |
do result <- eval env pred | |
case result of | |
Bool False -> eval env alt | |
otherwise -> eval env conseq | |
eval env (List [Atom "set!", Atom var, form]) = | |
eval env form >>= setVar env var | |
eval env (List [Atom "define", Atom var, form]) = | |
eval env form >>= defineVar env var | |
eval env (List (Atom "define" : List (Atom var : params) : body)) = | |
makeNormalFunc env params body >>= defineVar env var | |
eval env (List (Atom "define" : DottedList (Atom var : params) varargs : body)) = | |
makeVarargs varargs env params body >>= defineVar env var | |
eval env (List (Atom "lambda" : List params : body)) = | |
makeNormalFunc env params body | |
eval env (List (Atom "lambda" : DottedList params varargs : body)) = | |
makeVarargs varargs env params body | |
eval env (List (Atom "lambda" : varargs@(Atom _) : body)) = | |
makeVarargs varargs env [] body | |
eval env (List [Atom "load", String filename]) = | |
load filename >>= M.liftM last . mapM (eval env) | |
eval env (List (function : args)) = do | |
func <- eval env function | |
argVals <- M.mapM (eval env) args | |
apply func argVals | |
eval _ badForm = E.throwError $ | |
BadSpecialForm "Unrecognized special form" badForm | |
makeFunc :: Maybe String -> Env -> [LispVal] -> [LispVal] -> | |
IOThrowsError LispVal | |
makeFunc varargs env params body = return $ | |
Func (map showVal params) varargs body env | |
makeNormalFunc :: Env -> [LispVal] -> [LispVal] -> IOThrowsError LispVal | |
makeNormalFunc = makeFunc Nothing | |
makeVarargs :: LispVal -> Env -> [LispVal] -> [LispVal] -> | |
IOThrowsError LispVal | |
makeVarargs = makeFunc . Just . showVal | |
apply :: LispVal -> [LispVal] -> IOThrowsError LispVal | |
apply (PrimitiveFunc func) args = liftThrows $ func args | |
apply (Func params varargs body closure) args = | |
if num params /= num args && varargs == Nothing | |
then E.throwError $ NumArgs (num params) args | |
else (E.liftIO $ bindVars closure $ zip params args) >>= bindVarArgs varargs >>= evalBody | |
where | |
remainingArgs = drop (length params) args | |
num = toInteger . length | |
evalBody env = M.liftM last $ mapM (eval env) body | |
bindVarArgs arg env = case arg of | |
Just argName -> E.liftIO $ bindVars env [(argName, List $ remainingArgs)] | |
Nothing -> return env | |
apply (IOFunc func) args = func args | |
primitiveBindings :: IO Env | |
primitiveBindings = nullEnv | |
>>= (flip bindVars $ | |
map (makeFunc PrimitiveFunc) primitives) | |
>>= (flip bindVars $ | |
map (makeFunc IOFunc) ioPrimitives) | |
where makeFunc constractor (var, func) = (var, constractor func) | |
primitives :: [(String, [LispVal] -> ThrowsError LispVal)] | |
primitives = [("+", numericBinop (+)), | |
("-", numericBinop (-)), | |
("*", numericBinop (*)), | |
("/", numericBinop div), | |
("mod", numericBinop mod), | |
("quotient", numericBinop quot), | |
("remainder", numericBinop rem), | |
("=", numBoolBinop (==)), | |
("<", numBoolBinop (<)), | |
(">", numBoolBinop (>)), | |
("/=", numBoolBinop (/=)), | |
(">=", numBoolBinop (>=)), | |
("<=", numBoolBinop (<=)), | |
("&&", boolBoolBinop (&&)), | |
("||", boolBoolBinop (||)), | |
("string=?", strBoolBinop (==)), | |
("string<?", strBoolBinop (<)), | |
("string>?", strBoolBinop (>)), | |
("string<=?", strBoolBinop (<=)), | |
("string>=?", strBoolBinop (>=)), | |
("car", car), | |
("cdr", cdr), | |
("cons", cons), | |
("eq?", eqv), | |
("eqv?", eqv), | |
("equal?", equal)] | |
numericBinop :: (Integer -> Integer -> Integer) -> | |
[LispVal] -> ThrowsError LispVal | |
numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op | |
boolBinop :: (LispVal -> ThrowsError a) -> (a -> a -> Bool) -> | |
[LispVal] -> ThrowsError LispVal | |
boolBinop unpacker op args = if length args /= 2 | |
then E.throwError $ NumArgs 2 args | |
else do left <- unpacker $ args !! 0 | |
right <- unpacker $ args !! 1 | |
return $ Bool $ left `op` right | |
numBoolBinop :: (Integer -> Integer -> Bool) -> | |
[LispVal] -> ThrowsError LispVal | |
numBoolBinop = boolBinop unpackNum | |
strBoolBinop :: (String -> String -> Bool) -> | |
[LispVal] -> ThrowsError LispVal | |
strBoolBinop = boolBinop unpackStr | |
boolBoolBinop :: (Bool -> Bool -> Bool) -> | |
[LispVal] -> ThrowsError LispVal | |
boolBoolBinop = boolBinop unpackBool | |
unpackNum :: LispVal -> ThrowsError Integer | |
unpackNum (Number n) = return n | |
unpackNum (String n) = let parsed = reads n in | |
if null parsed | |
then E.throwError $ TypeMismatch "number" $ String n | |
else return . fst $ parsed !! 0 | |
unpackNum (List [n]) = unpackNum n | |
unpackNum notNum = E.throwError $ TypeMismatch "number" notNum | |
unpackStr :: LispVal -> ThrowsError String | |
unpackStr (String s) = return s | |
unpackStr (Number s) = return $ show s | |
unpackStr (Bool s) = return $ show s | |
unpackStr notString = E.throwError $ TypeMismatch "string" notString | |
unpackBool :: LispVal -> ThrowsError Bool | |
unpackBool (Bool b) = return b | |
unpackBool notBool = E.throwError $ TypeMismatch "boolean" notBool | |
car :: [LispVal] -> ThrowsError LispVal | |
car [List (x : xs)] = return x | |
car [DottedList (x : xs) _] = return x | |
car [badArg] = E.throwError $ TypeMismatch "pair" badArg | |
car badArgList = E.throwError $ NumArgs 1 badArgList | |
cdr :: [LispVal] -> ThrowsError LispVal | |
cdr [List (x : xs)] = return $ List xs | |
cdr [DottedList [xs] x] = return x | |
cdr [DottedList (_ : xs) x] = return $ DottedList xs x | |
cdr [badArg] = E.throwError $ TypeMismatch "pair" badArg | |
cdr badArgList = E.throwError $ NumArgs 1 badArgList | |
cons :: [LispVal] -> ThrowsError LispVal | |
cons [x1, List []] = return $ List [x1] | |
cons [x, List xs] = return $ List $ x : xs | |
cons [x, DottedList xs xlast] = return $ DottedList (x : xs) xlast | |
cons [x1, x2] = return $ DottedList [x1] x2 | |
cons badArgList = E.throwError $ NumArgs 2 badArgList | |
eqv :: [LispVal] -> ThrowsError LispVal | |
eqv [(Bool arg1), (Bool arg2)] = return $ Bool $ arg1 == arg2 | |
eqv [(Number arg1), (Number arg2)] = return $ Bool $ arg1 == arg2 | |
eqv [(String arg1), (String arg2)] = return $ Bool $ arg1 == arg2 | |
eqv [(Atom arg1), (Atom arg2)] = return $ Bool $ arg1 == arg2 | |
eqv [(DottedList xs x), (DottedList ys y)] = eqv [List $ xs ++ [x], List $ ys ++ [y]] | |
eqv [(List arg1), (List arg2)] = return $ Bool $ (length arg1 == length arg2) && | |
(all eqvPair $ zip arg1 arg2) | |
where eqvPair (x1, x2) = case eqv [x1, x2] of | |
Left err -> False | |
Right (Bool val) -> val | |
eqv [_, _] = return $ Bool False | |
eqv badArgList = E.throwError $ NumArgs 2 badArgList | |
data Unpacker = forall a. Eq a => AnyUnpacker (LispVal -> ThrowsError a) | |
unpackEquals :: LispVal -> LispVal -> Unpacker -> ThrowsError Bool | |
unpackEquals arg1 arg2 (AnyUnpacker unpacker) = | |
do unpacked1 <- unpacker arg1 | |
unpacked2 <- unpacker arg2 | |
return $ unpacked1 == unpacked2 | |
`E.catchError` (const $ return False) | |
equal :: [LispVal] -> ThrowsError LispVal | |
equal [arg1, arg2] = do | |
primitiveEquals <- M.liftM or $ M.mapM (unpackEquals arg1 arg2) | |
[AnyUnpacker unpackNum, AnyUnpacker unpackStr, AnyUnpacker unpackBool] | |
eqvEquals <- eqv [arg1, arg2] | |
return $ Bool $ (primitiveEquals || let (Bool x) = eqvEquals in x) | |
equal badArgList = E.throwError $ NumArgs 2 badArgList | |
flushStr :: String -> IO () | |
flushStr str = putStr str >> IO.hFlush IO.stdout | |
readPrompt :: String -> IO String | |
readPrompt prompt = flushStr prompt >> getLine | |
evalString :: Env -> String -> IO String | |
evalString env expr = runIOThrows . M.liftM show $ | |
liftThrows (readExpr expr) >>= eval env | |
evalAndPrint :: Env -> String -> IO () | |
evalAndPrint env = (>>= putStrLn) . evalString env | |
until_ :: Monad m => (a -> Bool) -> m a -> (a -> m ()) -> m () | |
until_ pred prompt action = do | |
result <- prompt | |
if pred result | |
then return () | |
else action result >> until_ pred prompt action | |
runOne :: [String] -> IO () | |
runOne args = do | |
let file = head args | |
let argVals = map String $ drop 1 args | |
env <- primitiveBindings >>= flip bindVars [("args", List argVals)] | |
(runIOThrows $ M.liftM show $ eval env (List [Atom "load", String file])) | |
>>= IO.hPutStrLn IO.stderr | |
runRepl :: IO () | |
runRepl = primitiveBindings >>= | |
until_ (== "quit") (readPrompt "Lisp>>> ") . evalAndPrint | |
type Env = R.IORef [(String, R.IORef LispVal)] | |
nullEnv :: IO Env | |
nullEnv = R.newIORef [] | |
type IOThrowsError = E.ErrorT LispError IO | |
liftThrows :: ThrowsError a -> IOThrowsError a | |
liftThrows (Left err) = E.throwError err | |
liftThrows (Right val) = return val | |
runIOThrows :: IOThrowsError String -> IO String | |
runIOThrows action = E.runErrorT (trapError action) >>= return . extractValue | |
isBound :: Env -> String -> IO Bool | |
isBound envRef var = R.readIORef envRef >>= return . maybe False (const True) . lookup var | |
getVar :: Env -> String -> IOThrowsError LispVal | |
getVar envRef var = do env <- E.liftIO $ R.readIORef envRef | |
maybe | |
(E.throwError $ | |
UnboundVar "Getting an unbound variable: " var) | |
(E.liftIO . R.readIORef) | |
(lookup var env) | |
setVar :: Env -> String -> LispVal -> IOThrowsError LispVal | |
setVar envRef var value = do | |
env <- E.liftIO $ R.readIORef envRef | |
maybe | |
(E.throwError $ UnboundVar "Setting an unbound variable: " var) | |
(E.liftIO . (flip R.writeIORef value)) | |
(lookup var env) | |
return value | |
defineVar :: Env -> String -> LispVal -> IOThrowsError LispVal | |
defineVar envRef var value = do | |
alreadyDefined <- E.liftIO $ isBound envRef var | |
if alreadyDefined | |
then setVar envRef var value >> return value | |
else E.liftIO $ do | |
valueRef <- R.newIORef value | |
env <- R.readIORef envRef | |
R.writeIORef envRef ((var, valueRef) : env) | |
return value | |
bindVars :: Env -> [(String, LispVal)] -> IO Env | |
bindVars envRef bindings = R.readIORef envRef >>= extendEnv bindings | |
>>= R.newIORef | |
where extendEnv bindings env = M.liftM (++ env) (mapM addBinding bindings) | |
addBinding (var, value) = do ref <- R.newIORef value | |
return (var, ref) | |
ioPrimitives :: [(String, [LispVal] -> IOThrowsError LispVal)] | |
ioPrimitives = [("apply", applyProc), | |
("open-input-file", makePort IO.ReadMode), | |
("open-output-file", makePort IO.WriteMode), | |
("close-input-port", closePort), | |
("close-output-port", closePort), | |
("read", readProc), | |
("write", writeProc), | |
("read-contents", readContents), | |
("read-all", readAll)] | |
applyProc :: [LispVal] -> IOThrowsError LispVal | |
applyProc [func, List args] = apply func args | |
applyProc (func : args) = apply func args | |
makePort :: IO.IOMode -> [LispVal] -> IOThrowsError LispVal | |
makePort mode [String filename] = M.liftM Port $ E.liftIO $ | |
IO.openFile filename mode | |
closePort :: [LispVal] -> IOThrowsError LispVal | |
closePort [Port port] = E.liftIO $ IO.hClose port >> (return $ Bool True) | |
closePort _ = return $ Bool False | |
readProc :: [LispVal] -> IOThrowsError LispVal | |
readProc [] = readProc [Port IO.stdin] | |
readProc [Port port] = (E.liftIO $ IO.hGetLine port) >>= liftThrows . readExpr | |
writeProc :: [LispVal] -> IOThrowsError LispVal | |
writeProc [obj] = writeProc [obj, Port IO.stdout] | |
writeProc [obj, Port port] = E.liftIO $ IO.hPrint port obj >> | |
(return $ Bool True) | |
readContents :: [LispVal] -> IOThrowsError LispVal | |
readContents [String filename] = M.liftM String $ E.liftIO $ readFile filename | |
load :: String -> IOThrowsError [LispVal] | |
load filename = (E.liftIO $ readFile filename) >>= liftThrows . readExprList | |
readAll :: [LispVal] -> IOThrowsError LispVal | |
readAll [String filename] = M.liftM List $ load filename |
This file contains hidden or 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
(define (not x) (if x #f #t)) | |
(define (null? obj) (if (eqv? obj '()) #t #f)) | |
(define (list . objs) objs) | |
(define (id obj) obj) | |
(define (flip func) (lambda (arg1 arg2) (func arg2 arg1))) | |
(define (curry func arg1) (lambda (arg) (apply func (cons arg1 (list arg))))) | |
(define (compose f g) (lambda (arg) (f (apply g arg)))) | |
(define zero? (curry = 0)) | |
(define positive? (curry < 0)) | |
(define negative? (curry > 0)) | |
(define (odd? num) (= (mod num 2) 1)) | |
(define (even? num) (= (mod num 2) 0)) | |
(define (foldr func end lst) | |
(if (null? lst) | |
end | |
(func (car lst) (foldr func end (cdr lst))))) | |
(define (foldl func accum lst) | |
(if (null? lst) | |
accum | |
(foldl func (func accum (car lst)) (cdr lst)))) | |
(define fold foldl) | |
(define reduce fold) | |
(define (unfold func init pred) | |
(if (pred init) | |
(cons init '()) | |
(cons init (unfold func (func init) pred)))) | |
(define (sum . lst) (fold + 0 lst)) | |
(define (product . lst) (fold * 1 lst)) | |
(define (and . lst) (fold && #t lst)) | |
(define (or . lst) (fold || #f lst)) | |
(define (max first . rest) (fold (lambda (old new) (if (> old new) old new)) first rest)) | |
(define (min first . rest) (fold (lambda (old new) (if (< old new) old new)) first rest)) | |
(define (length lst) (fold (lambda (x y) (+ x 1)) 0 lst)) | |
(define (reverse lst) (fold (flip cons) '() lst)) | |
(define (mem-helper pred op) (lambda (acc next) (if (and (not acc) (pred (op next))) next acc))) | |
(define (memq obj lst) (fold (mem-helper (curry eq? obj) id) #f lst)) | |
(define (memv obj lst) (fold (mem-helper (curry eqv? obj) id) #f lst)) | |
(define (member obj lst) (fold (mem-helper (curry equal? obj) id) #f lst)) | |
(define (assq obj alist) (fold (mem-helper (curry eq? obj) car) #f alist)) | |
(define (assv obj alist) (fold (mem-helper (curry eqv? obj) car) #f alist)) | |
(define (assoc obj alist) (fold (mem-helper (curry equal? obj) car) #f alist)) | |
(define (map func lst) (foldr (lambda (x y) (cons (func x) y)) '() lst)) | |
(define (filter pred lst) (foldr (lambda (x y) (if (pred x) (cons x y) y)) '() lst)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment