swearjure-1.0.0: src/Main.hs
{-# OPTIONS_GHC -Wall -Werror #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative ((<$>))
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Data.Foldable (toList)
import Data.Sequence
import qualified Data.Sequence as S
import qualified Data.Text.Lazy as B
import Filesystem hiding (readFile, writeFile)
import Filesystem.Path.CurrentOS
import Swearjure.AST (prStr, Val, EvalState, _nil)
import Swearjure.Errors
import Swearjure.Eval (initEnv, eval)
import Swearjure.Parser
import Swearjure.Reader
import System.Console.Readline (readline, addHistory)
import System.Environment
import System.IO (hIsTerminalDevice, stdin)
main :: IO ()
main = do args <- getArgs
case args of
["-h"] -> banner
["-?"] -> banner
["--help"] -> banner
[x] -> do contents <- readFile x
static contents
[] -> do isTerm <- hIsTerminalDevice stdin
if isTerm
then interactive
else do contents <- getContents
static contents
_ -> banner
banner :: IO ()
banner = putStrLn "Swearjure, version (*).(+).(+) (aka 1.0.0)"
-- Since attoparsec
static :: String -> IO ()
static input = staticRep 1 (readStatic $ B.pack input)
staticRep :: Int -> [Either SwjError PVal] -> IO ()
staticRep _ [] = return ()
staticRep n [x]
= do res <- staticEval n x
case res of
Left err -> putStrLn $ errString err
Right (v, _) | v == _nil -> return ()
Right (v, _) -> putStrLn (prStr v)
staticRep n (x : xs)
= do res <- staticEval n x
case res of
Left err -> putStrLn $ errString err
Right (_, n') -> staticRep n' xs
staticEval :: Int -> Either SwjError PVal -> IO (Either SwjError (Val, Int))
staticEval n (Right pval) = runExceptT (runStateT (runReaderT (doEval pval) initEnv) n)
staticEval _ (Left err) = return $ Left err
interactive :: IO ()
interactive = do hist <- readHistory
void $ runStateT (loop 1) hist
teardown :: StateT (Seq String) IO ()
teardown = get >>= liftIO . writeHistory >> return ()
loop :: Int -> StateT (Seq String) IO ()
loop gsymCount
= do input <- liftIO $ readline "swj> "
case input of
Nothing -> teardown
Just str ->
do newGsym <- feedLoop gsymCount (readAsts str)
case newGsym of
Nothing -> teardown
Just val -> loop val
-- returns nothing if we pass in C-d
feedLoop :: Int -> ParseResult -> StateT (Seq String) IO (Maybe Int)
feedLoop gsymCount (Results rseq str)
= do liftIO $ addHistory str
modify (|> str)
n' <- liftIO $ foldM rep gsymCount $ toList rseq
return $ Just n'
feedLoop gsymCount (Continuation rseq accum cont)
= do input <- liftIO $ readline "#_=> "
case input of
Nothing -> return Nothing
Just str -> feedLoop gsymCount $ feedCont str rseq accum cont
rep :: Int -> Either SwjError PVal -> IO Int
rep n (Left err) = do putStrLn $ errString err
return n
rep n (Right pval)
= do res <- runExceptT (runStateT (runReaderT (doEval pval) initEnv) n)
case res of
Left err -> do putStrLn $ errString err
return n
Right (x, n') -> do putStrLn $ prStr x
return n'
doEval :: PVal -> EvalState Val
doEval pval = readVal pval >>= eval
readHistory :: IO (Seq String)
readHistory = do hdir <- getAppDataDirectory "swearjure"
createTree hdir
let file = hdir </> fromText "history"
isF <- isFile file
if isF
then do ls <- lines0 <$> readFile (encodeString file)
mapM_ addHistory ls
return $ fromList ls
else return S.empty
writeHistory :: Seq String -> IO ()
writeHistory hist = do hdir <- getAppDataDirectory "swearjure"
createTree hdir
let fname = encodeString (hdir </> fromText "history")
let hist' = takeLast 1000 hist
writeFile fname (unlines0 $ toList hist')
-- | returns the last n elements in the seq as a seq, or the seq itself if it
-- contains less than n elements.
takeLast :: Int -> Seq a -> Seq a
takeLast n s = S.drop (S.length s - n) s
-- | like unlines, but separates with a null character. Removes the last line.
unlines0 :: [String] -> String
unlines0 = init . concatMap (++ "\0")
-- | line lines, but splits at null characters instead
lines0 :: String -> [String]
lines0 "" = []
lines0 s = cons (case break (== '\0') s of
(l, s') -> (l, case s' of
[] -> []
_:s'' -> lines0 s''))
where
cons ~(h, t) = h : t