module Main where
import qualified Data.Map as M
import Data.Foldable (foldlM)
import System
.IO (hFlush
, stdout
)
main = repl newEnvironment
flush = hFlush stdout
printErr env err
= putStrLnF err
>> return env
lisp env xs = do
case tokenize xs of
Left err -> printErr env err
Right tokens -> lisp' (parse tokens) env
lisp' :: ParseResult
-> Env
-> IO Env
lisp' (Left err) env = putStrLnF err >> return env
lisp' (Right
(Just cd
, rm
)) env
= either (printErr env
) (lisp
'' rm
) $ run cd env
lisp' (Right (Nothing, _)) env = putStrLnF "err?" >> return env
lisp'' :: [Token] -> (LispData, Env) -> IO Env
lisp'' [] (ld, env) = putStrLnF (toString ld) >> return env
lisp'' rm (ld, env) = putStrLnF (toString ld) >> lisp' (parse rm) env
repl env = do
putStrF ">>> "
case s of
"" -> repl env
":quit" -> putStrLnF "bye"
":reset" -> putStrLnF "reset" >> repl newEnvironment
":{" -> getMultilines "" >>= lisp env >>= repl
(':':_) -> putStrLnF "error (invalid command):" >> repl env
_ -> lisp env s >>= repl
getMultilines s = do
putStrF "==| "
if t == ":}" then
else
getMultilines $ s ++ " " ++ t
data Token = OpenBracketToken
| CloseBracketToken
| CommaToken
| CommaAtToken
| ConsToken
| QuoteToken
| BackQuoteToken
trueToken :: Token
trueToken = BooleanToken True
falseToken :: Token
falseToken = BooleanToken False
untoken ConsToken = "."
untoken CommaToken = ","
untoken CommaAtToken = ",@"
untoken QuoteToken = "'"
untoken BackQuoteToken = "`"
untoken OpenBracketToken = "("
untoken CloseBracketToken = ")"
untoken (WordToken s) = s
untoken (StringToken s) = "\"" ++ escape s ++ "\""
untoken (CommentToken s) = ';' : s
untoken (BooleanToken b) = if b then "#t" else "#f"
untoken
(IntegerToken i
) = show i
escape
= ($"") . foldl escapeChar
id
escapeChar f '\n' = f.('\\':).('n':)
escapeChar f '\r' = f.('\\':).('r':)
escapeChar f '\t' = f.('\\':).('t':)
escapeChar f '\\' = f.('\\':).('\\':)
escapeChar f '"' = f.('\\':).('"':)
escapeChar f ch = f.(ch:)
isDigit
= (`
elem`
"0123456789")
isWordChar
= not . (`
elem`
" ;()\"'`,\n\r\t")
isWhiteSpace
= (`
elem`
" \n\r\t")
tokenize
:: String -> TokenizeResult
tokenize [] = Right []
tokenize xs = scan tokenize xs
scan
:: ((String -> TokenizeResult
) -> String -> TokenizeResult
)scan = connect scanOpenBracket
. connect scanCloseBracket
. connect scanString
. connect scanComment
. connect scanQuote
. connect scanWord
connect
:: (String -> ScanResult
)connect f g = next g . f . ltrim
next
:: (String -> TokenizeResult
) -> ScanResult
-> TokenizeResult
next _ (Left err) = Left err
next g
(Right
(tk
, zs
)) = maybe id (:
) tk
<$> g zs
scanOpenBracket
:: String -> ScanResult
scanOpenBracket ('(':xs) = Right (Just OpenBracketToken, xs)
scanOpenBracket xs = Right (Nothing, xs)
scanCloseBracket
:: String -> ScanResult
scanCloseBracket (')':xs) = Right (Just CloseBracketToken, xs)
scanCloseBracket xs = Right (Nothing, xs)
scanString
:: String -> ScanResult
scanString
('"':xs
) = takeString
id xs
scanString xs = Right (Nothing, xs)
takeString f ('\\':ch:xs) = takeString (f.(transSpChar ch:)) xs
takeString f ('\\':_) = Left $ "string token error (illegal escape): " ++ f "\\"
takeString f ('"':xs) = Right (Just $ StringToken $ f "", xs)
takeString f (ch:xs) = takeString (f.(ch:)) xs
takeString f [] = Left $ "string token error (not closed): " ++ f ""
transSpChar 'n' = '\n'
transSpChar 'r' = '\r'
transSpChar 't' = '\t'
transSpChar ch = ch
scanComment
:: String -> ScanResult
scanComment
(';':xs
) = takeComment
$ break (== '\n') xs
scanComment xs = Right (Nothing, xs)
takeComment (msg, xs) = Right (Just $ CommentToken msg, xs)
scanQuote
:: String -> ScanResult
scanQuote (',':'@':xs) = takeQuote ",@" xs
scanQuote
(ch:xs
) | elem ch
"'`," = takeQuote
[ch
] xs
scanQuote xs = Right (Nothing, xs)
takeQuote sg xs | validQuoteSyntax xs
= Right (Just $ transQuoteToken sg, xs)
= Left
$ "quote token error (no quote target): " ++ sg
++ take 20 xs
transQuoteToken
:: String -> Token
transQuoteToken "'" = QuoteToken
transQuoteToken "`" = BackQuoteToken
transQuoteToken "," = CommaToken
transQuoteToken ",@" = CommaAtToken
validQuoteSyntax [] = False
validQuoteSyntax
(ch:
_) = not $ elem ch
"); \n\r\t"
scanWord
:: (String -> ScanResult
)scanWord
= takeWord
. span isWordChar
takeWord ("", ys) = Right (Nothing, ys)
takeWord (".", ys) = Right (Just ConsToken, ys)
takeWord ("#t", ys) = Right (Just trueToken, ys)
takeWord ("#f", ys) = Right (Just falseToken, ys)
takeWord (w@('-':ch:_), ys) | isDigit ch
= takeNumber w ys
takeWord (w@(ch:_), ys) | isDigit ch
= takeNumber w ys
takeWord (word, ys) = Right (Just $ WordToken word, ys)
takeNumber w
@('-':ls
) ys
| all isDigit ls
= Right
(Just
$ IntegerToken
$ read w
, ys
) takeNumber w ys
| all isDigit w
= Right
(Just
$ IntegerToken
$ read w
, ys
) = Left $ "token error (not number): " ++ w
data LispData = List [LispData]
| Cons [LispData] LispData
| Node Token
-- | Quote Token LispData
| Procedure LispData
| Syntax LispData
emptyList :: LispData
emptyList = List []
trueValue :: LispData
trueValue = Node trueToken
falseValue :: LispData
falseValue = Node falseToken
intZeroValue :: LispData
intZeroValue = Node $ IntegerToken 0
intOneValue :: LispData
intOneValue = Node $ IntegerToken 1
toString
:: LispData
-> StringtoString (List xs) = untoken OpenBracketToken
++ untoken CloseBracketToken
toString (Cons xs ld) = untoken OpenBracketToken
++ " "
++ untoken ConsToken
++ " "
++ toString ld
++ untoken CloseBracketToken
toString (Node tk) = untoken tk
-- toString (Quote qu ld) = untoken qu ++ toString ld
toString (Procedure (Node (WordToken s))) = "#<procedure " ++ s ++ ">"
toString (Procedure _) = "#<procedure (lambda)>"
toString (Syntax (Node (WordToken s))) = "#<syntax " ++ s ++ ">"
type ParseState
= (Maybe LispData
, [Token
])
parse :: [Token] -> ParseResult
parse [] = Right (Nothing, [])
parse xs = parseAll xs
parseAll :: ([Token] -> ParseResult)
parseAll = combine parseList
. combine parseNode
. combine parseQuote
$ const (Left
"parse error (parser bug or syntax error):")
combine :: ([Token] -> ParseResult)
-> ([Token] -> ParseResult)
-> ([Token] -> ParseResult)
combine f g = nextParse g . f . omit
nextParse :: ([Token] -> ParseResult) -> ParseResult -> ParseResult
nextParse p (Right (Nothing, xs)) = p xs
nextParse _ ret = ret
isOmitToken
:: Token
-> BoolisOmitToken (CommentToken _) = True
isOmitToken _ = False
omit :: ([Token] -> [Token])
parseList :: [Token] -> ParseResult
parseList
(OpenBracketToken:xs
) = takeList
id xs
parseList xs = Right (Nothing, xs)
takeList :: ([LispData] -> [LispData]) -> [Token] -> ParseResult
takeList f [] = Left "parse error (not closed):"
takeList f (CloseBracketToken:xs) = Right (Just $ makeListData $ f [], xs)
takeList f (ConsToken:xs) = parse xs >>= takeCons f
takeList f xs = parse xs >>= takeListItem f
makeListData :: [LispData] -> LispData
makeListData [] = emptyList
makeListData ls = List ls
takeListItem :: ([LispData] -> [LispData]) -> ParseState -> ParseResult
takeListItem f (Just ld, xs) = takeList (f.(ld:)) xs
takeListItem _ (Nothing, _) = Left "parse error (no node):"
takeCons :: ([LispData] -> [LispData]) -> ParseState -> ParseResult
takeCons f (Just ld, xs) = makeCons f (f []) ld (omit xs)
takeCons _ (Nothing, _) = Left "parse error (no cons data):"
makeCons :: ([LispData] -> [LispData]) -> [LispData] -> LispData -> [Token] -> ParseResult
makeCons _ [] _ _ = Left "parse error (invalid cons):"
makeCons f _ (List ls) (CloseBracketToken:xs) = Right (Just $ List (f ls), xs)
makeCons f _ (Cons ls ld) (CloseBracketToken:xs) = Right (Just $ Cons (f ls) ld, xs)
makeCons _ ls ld (CloseBracketToken:xs) = Right (Just $ Cons ls ld, xs)
makeCons _ _ _ _ = Left "parse error (invalid cons):"
isNodeToken
:: Token
-> BoolisNodeToken (WordToken _) = True
isNodeToken (StringToken _) = True
isNodeToken (BooleanToken _) = True
isNodeToken (IntegerToken _) = True
isNodeToken _ = False
parseNode :: [Token] -> ParseResult
parseNode (tk:xs) | isNodeToken tk
= Right (Just $ Node tk, xs)
parseNode xs = Right (Nothing, xs)
isQuoteToken
:: Token
-> BoolisQuoteToken QuoteToken = True
isQuoteToken BackQuoteToken = True
isQuoteToken CommaToken = True
isQuoteToken CommaAtToken = True
isQuoteToken _ = False
quoteToWord
:: Token
-> StringquoteToWord QuoteToken = "quote"
quoteToWord BackQuoteToken = "quasiquote"
quoteToWord CommaToken = "unquote"
quoteToWord CommaAtToken = "unquote-splicing"
quoteToNode :: (Token -> LispData)
quoteToNode = Node . WordToken . quoteToWord
parseQuote :: [Token] -> ParseResult
parseQuote (tk:xs) | isQuoteToken tk
= parse xs >>= takeQuoteItem tk
parseQuote xs = Right (Nothing, xs)
takeQuoteItem :: Token -> ParseState -> ParseResult
-- takeQuoteItem tk (Just ld, xs) = Right (Just $ Quote tk ld, xs)
takeQuoteItem tk (Just ld, xs) = Right (Just $ List [quoteToNode tk, ld], xs)
takeQuoteItem _ (Nothing, _) = Left "parse error (no quote item):"
data Stock
= Stock
{reserved
::Bool, value
::LispData
} deriving Show type RunState = (LispData, Env)
procedures
= map (\x
-> (x
, Stock True
$ Procedure
$ Node
$ WordToken x
)) $ ["car", "cdr", "cons" , "pair?", "eq?"
, "+", "*", "-"]
syntaxes
= map (\x
-> (x
, Stock True
$ Syntax
$ Node
$ WordToken x
)) $ ["quote", "define", "if", "lambda"]
newEnvironment :: Env
newEnvironment = M.fromList $ procedures ++ syntaxes
run :: LispData -> Env -> RunResult
run (List ls) env = runList ls env
run ld@(Node _) env = runNode ld env
run ld _ = Left $ "cannot run code: " ++ toString ld
runNode :: LispData -> Env -> RunResult
runNode
(Node
(WordToken wd
)) env
= takeValue env wd
$ M
.lookup wd env
runNode ld env = Right (ld, env)
takeValue _ ky Nothing = Left $ "not found: " ++ ky
takeValue env _ (Just st) = Right (value st, env)
runList :: [LispData] -> Env -> RunResult
runList [] _ = Left $ "cannot run emptylist:"
runList (ld:xs) env = run ld env >>= takeExecutor xs
takeExecutor :: [LispData] -> RunState -> RunResult
takeExecutor ls (Procedure ld, env) = execute ld ls env
takeExecutor ls (Syntax ld, env) = execute ld ls env
takeExecutor _ (ld, _) = Left $ "cannot run code: " ++ toString ld
execute :: LispData -> [LispData] -> Env -> RunResult
execute (Node (WordToken "quote")) ls env = callQuote ls env
execute (Node (WordToken "car")) ls env = callCar ls env
execute (Node (WordToken "cdr")) ls env = callCdr ls env
execute (Node (WordToken "cons")) ls env = callCons ls env
execute (Node (WordToken "pair?")) ls env = callPairQ ls env
execute (Node (WordToken "eq?")) ls env = callEqQ ls env
execute (Node (WordToken "if")) ls env = callIf ls env
execute (Node (WordToken "define")) ls env = callDefine ls env
execute (Node (WordToken "lambda")) ls env = callLambda ls env
execute (Node (WordToken "+")) ls env = callPlusOp ls env
execute (Node (WordToken "*")) ls env = callAstrOp ls env
execute (Node (WordToken "-")) ls env = callMinusOp ls env
execute (List (la:xs)) ls env = runAll ls env >>= doLambda la xs
execute ld _ _ = Left $ "cannot run code: " ++ toString ld
type ArgState = ([LispData], Env)
callQuote :: [LispData] -> Env -> RunResult
callQuote (ld:[]) env = Right (ld, env)
callQuote ls _ = Left $ "illigal arguments: " ++ toString (List ls)
takeAndRunArgs
:: Int -> (ArgState
-> RunResult
) -> [LispData
] -> Env
-> RunResult
takeAndRunArgs c f ls env
| length ls
== c
= takeAndRunAllArgs f ls env
| otherwise = Left
$ "illigal arguments: " ++ toString
(List ls
)
takeAndRunArgsWithMin
:: Int -> (ArgState
-> RunResult
) -> [LispData
] -> Env
-> RunResult
takeAndRunArgsWithMin c f ls env
| length ls
>= c
= takeAndRunAllArgs f ls env
| otherwise = Left
$ "illigal arguments: " ++ toString
(List ls
)
runAll :: [LispData] -> Env -> RunResult
runAll ls env
= (\
(List xs
, e
) -> (List
$ reverse xs
, e
)) <$> foldlM runArg
(emptyList
, env
) ls
runArg :: RunState -> LispData -> RunResult
runArg (List ls, env) ld = run ld env >>= takeArg ls
takeArg :: [LispData] -> RunState -> RunResult
takeArg ls (ld, env) = Right (List (ld:ls), env)
takeAndRunAllArgs :: (ArgState -> RunResult) -> [LispData] -> Env -> RunResult
takeAndRunAllArgs f ls env = f . unwrapList =<< runAll ls env
unwrapList :: RunState -> ArgState
unwrapList (List ls, env) = (ls, env)
callCar :: ([LispData] -> Env -> RunResult)
callCar = takeAndRunArgs 1 executeCar
executeCar :: ArgState -> RunResult
executeCar (List (ld:_) :_, env) = Right (ld, env)
executeCar (Cons (ld:_) _:_, env) = Right (ld, env)
executeCar (ld :_, _ ) = Left $ "illigal argument: " ++ toString ld
callCdr :: ([LispData] -> Env -> RunResult)
callCdr = takeAndRunArgs 1 executeCdr
executeCdr :: ArgState -> RunResult
executeCdr (List (_:[]) :_, env) = Right (emptyList, env)
executeCdr (List (_:ls) :_, env) = Right (List ls, env)
executeCdr (Cons (_:[]) ld:_, env) = Right (ld, env)
executeCdr (Cons (_:ls) ld:_, env) = Right (Cons ls ld, env)
executeCdr (ld :_, _ ) = Left $ "illigal argument: " ++ toString ld
callCons :: ([LispData] -> Env -> RunResult)
callCons = takeAndRunArgs 2 executeCons
executeCons :: ArgState -> RunResult
executeCons (ld:Cons ls la:_, env) = Right (Cons (ld:ls) la, env)
executeCons (ld:List ls :_, env) = Right (List (ld:ls), env)
executeCons (ld:la :_, env) = Right (Cons [ld] la, env)
callPairQ :: ([LispData] -> Env -> RunResult)
callPairQ = takeAndRunArgs 1 executePairQ
executePairQ :: ArgState -> RunResult
executePairQ (Cons _ _ :_, env) = Right (trueValue, env)
executePairQ (List (_:_):_, env) = Right (trueValue, env)
executePairQ (_ , env) = Right (falseValue, env)
callEqQ :: ([LispData] -> Env -> RunResult)
callEqQ = takeAndRunArgs 2 executeEqQ
executeEqQ :: ArgState -> RunResult
executeEqQ (ld1:ld2:_, env) | ld1 == ld2 = Right (trueValue, env)
callDefine :: [LispData] -> Env -> RunResult
callDefine (Node (WordToken wd):ld:[]) env
| canBind wd env
= bindValue wd <$> run ld env
= Left $ "reserved word: " ++ wd
callDefine ls _ = Left $ "illigal arguments: " ++ toString (List ls)
bindValue
:: String -> RunState
-> RunState
bindValue wd (ld, env) = (ld, M.insert wd (Stock False ld) env)
callIf :: [LispData] -> Env -> RunResult
callIf (test:tv:fv:[]) env = run test env >>= executeIf tv fv
callIf (test:tv:[]) env = run test env >>= executeIf tv falseValue
callIf ls _ = Left $ "invalid arguments: " ++ toString (List ls)
executeIf :: LispData -> LispData -> RunState -> RunResult
executeIf _ fv (Node (BooleanToken False), env) = run fv env
executeIf tv _ (_, env) = run tv env
callLambda :: [LispData] -> Env -> RunResult
callLambda ls@(Node (WordToken _):_:_) env = Right (Procedure (List ls), env)
callLambda ls@(List xs :_:_) env | checkArgsAllWord xs
= Right (Procedure (List ls), env)
callLambda ls@(Cons xs ld :_:_) env | checkArgsAllWord (ld:xs)
= Right (Procedure (List ls), env)
callLambda ls _ = Left $ "invalid arguments: " ++ toString (List ls)
checkArgsAllWord
:: ([LispData
] -> Bool)checkArgsAllWord
= all isWordTokenNode
isWordTokenNode (Node (WordToken _)) = True
isWordTokenNode _ = False
doLambda :: LispData -> [LispData] -> RunState -> RunResult
doLambda (Node (WordToken wd)) xs st@(_, env) = bindOneLArg wd st >>= evalLambda env xs
doLambda (List ls) xs st@(_, env) = bindListLArg ls st >>= evalLambda env xs
doLambda (Cons ls ed) xs st@(_, env) = bindConsLArg ls ed st >>= evalLambda env xs
bindOneLArg
:: String -> RunState
-> RunResult
bindOneLArg wd st@(_, env) | canBind wd env
= Right $ bindValue wd st
= Left $ "reserved word: " ++ wd
bindListLArg :: [LispData] -> RunState -> RunResult
bindListLArg ls st
@(List args
, env
) | length args
== length ls
= foldlM fListBind st
$ zip ls args
= Left $ "invalid number of arguments: "
fListBind :: RunState -> (LispData, LispData) -> RunResult
fListBind (_, env) (Node (WordToken wd), ld) | canBind wd env
= Right $ bindValue wd (ld, env)
= Left $ "reserved word: " ++ wd
bindConsLArg :: [LispData] -> LispData -> RunState -> RunResult
bindConsLArg ls (Node (WordToken wd)) (List args, env)
= Left $ "invalid number of arguments: "
fConsBind
:: [LispData
] -> String -> Env
-> ([LispData
], [LispData
]) -> RunResult
fConsBind ls wd env (args, []) = bindListLArg ls (List args, env) >>= \(_, e) -> bindOneLArg wd (emptyList, e)
fConsBind ls wd env (args, rm) = bindListLArg ls (List args, env) >>= \(_, e) -> bindOneLArg wd (List rm, e)
evalLambda :: Env -> [LispData] -> RunState -> RunResult
evalLambda env xs st = (\(res,_) -> (res, env)) <$> foldlM fLambda st xs
fLambda :: RunState -> LispData -> RunResult
fLambda (_, env) code = run code env
callPlusOp :: ([LispData] -> Env -> RunResult)
callPlusOp = takeAndRunAllArgs executePlusOp
executePlusOp :: ArgState -> RunResult
executePlusOp (ls, env) = foldlM sumNums (intZeroValue, env) ls
sumNums :: RunState -> LispData -> RunResult
sumNums (Node (IntegerToken v1), env) (Node (IntegerToken v2)) = Right (Node $ IntegerToken $ v1 + v2, env)
sumNums _ ld = Left $ "invalid arguments: " ++ toString ld
callAstrOp :: ([LispData] -> Env -> RunResult)
callAstrOp = takeAndRunAllArgs executeAstrOp
executeAstrOp :: ArgState -> RunResult
executeAstrOp (ls, env) = foldlM mulNums (intOneValue, env) ls
mulNums :: RunState -> LispData -> RunResult
mulNums (Node (IntegerToken v1), env) (Node (IntegerToken v2)) = Right (Node $ IntegerToken $ v1 * v2, env)
mulNums _ ld = Left $ "invalid arguments: " ++ toString ld
callMinusOp :: ([LispData] -> Env -> RunResult)
callMinusOp = takeAndRunArgsWithMin 1 executeMinusOp
executeMinusOp :: ArgState -> RunResult
executeMinusOp (Node (IntegerToken v):[], env) = Right (Node $ IntegerToken (0 - v), env)
executeMinusOp (fv:ls, env) = foldlM subNums (fv, env) ls
subNums :: RunState -> LispData -> RunResult
subNums (Node (IntegerToken v1), env) (Node (IntegerToken v2)) = Right (Node $ IntegerToken $ v1 - v2, env)
subNums _ ld = Left $ "invalid arguments: " ++ toString ld