packages feed

Delta-Lambda-0.2.0.0: dl.hs

module Main( main ) where

{- TODO:
  profiling
  write tests
-}


--import           Paths_Delta-Lambda (version)
import           Data.Char                   (isSpace, isNumber)
import           System.FilePath             hiding ((</>))
import           Control.Applicative
import           Control.Monad.Except
import           Control.Monad.Reader
import           Control.Parallel
import           Data.ByteString             (writeFile, readFile)
import           Data.Text                   (Text, pack, unpack, length)
import           Data.Text.Encoding
import           Data.Text.IO
import           Data.Version
import           Data.Word                   (Word64)
import           GHC.Generics
import           Prelude                     hiding (getContents, putStrLn)
import           System.IO                   (Handle, IOMode (ReadMode, ReadWriteMode), withFile)

import           Text.Megaparsec             hiding (space)
import qualified Text.Megaparsec.Lexer       as Lexer
import           Text.Megaparsec.Text

import           Options

import           Text.PrettyPrint.Leijen     hiding ((<$>))

import           Language.Preprocessor.Cpphs

import           System.Console.Haskeline

import           Data.Serialize

-- represent a term with variable name type v, and variable binding index type i
-- that has metadata m associated to each node and leaf
data Term m v i = Variable v i m
                | Abstraction v m (Term m v i) (Term m v i)
                | Application m (Term m v i) (Term m v i)
                | Type m
                deriving (Generic)

data Kind m v i = Function v m (Term m v i) (Kind m v i)
                | Kind m
                deriving (Generic)

data DecompiledFileHeader = DecompiledFileHeader
  { compiledFileName   :: Text
  , interpreterVersion :: Version
  , extensionsUsed     :: [Text]
  }

data DecompiledFile m v i = DecompiledFile
  { decompiledFileHeader :: DecompiledFileHeader
  , decompiledTerm       :: Term m v i
  }

instance (Serialize m, Serialize v, Serialize i) => Serialize (Term m v i) where

instance (Serialize m, Serialize v, Serialize i) => Serialize (Kind m v i) where

  -- some orphan instances
instance Serialize Version where
  put = \vers -> putListOf (put :: Putter Int) (versionBranch vers)
  get = do
    branchList :: [Int] <- getListOf get
    return Version { versionBranch = branchList
                   , versionTags = []
                   }

instance Serialize Text where
  put = \text' -> do
    putWord64le (fromIntegral (Data.Text.length text') :: Word64)
    put (encodeUtf8 text')
  get = do
    leng <- getWord64le
    bs <- getByteString (fromIntegral leng :: Int)
    return (decodeUtf8 bs)

instance Serialize SourcePos where
  put = \srcPos -> do
    put (sourceName srcPos)
    put (sourceLine srcPos)
    put (sourceColumn srcPos)
  get = do str :: String <- get
           pos1 :: Pos <- get
           pos2 :: Pos <- get
           return $ SourcePos str pos1 pos2

instance Serialize Pos where
  put = \pos -> putWord64le (fromIntegral (unPos pos) :: Word64)
  get = getWord64le >>= return . unsafePos . fromIntegral

instance Serialize DecompiledFileHeader where
  put = \decompHeader -> do
    put (compiledFileName decompHeader)
    put (interpreterVersion decompHeader)
    put (extensionsUsed decompHeader)
  get = do
    fname :: Text <- get
    iversion :: Version <- get
    xtensions :: [Text] <- get
    return DecompiledFileHeader
      { compiledFileName = fname
      , interpreterVersion = iversion
      , extensionsUsed = xtensions
      }

instance (Serialize m, Serialize v, Serialize i) => Serialize (DecompiledFile m v i) where
  put = \decomp -> do
    put (decompiledFileHeader decomp)
    put (decompiledTerm decomp)
  get = do
    fileHeader :: DecompiledFileHeader <- get
    fileBody :: Term m v i <- get
    return DecompiledFile
      { decompiledFileHeader = fileHeader
      , decompiledTerm = fileBody
      }

transpile :: (m -> m') -> (v -> v') -> (i -> i') -> DecompiledFile m v i -> DecompiledFile m' v' i'
transpile fm fv fi decompFile@DecompiledFile{ decompiledTerm = fileBody } =
  let transpile' t = case t of
                    Type m -> Type (fm m)
                    Variable v i m -> Variable (fv v) (fi i) (fm m)
                    Application m a f ->
                      Application (fm m) (transpile' a) (transpile' f)
                    Abstraction v m t b ->
                      Abstraction (fv v) (fm m) (transpile' t) (transpile' b)
  in decompFile { decompiledTerm = transpile' fileBody }





bindTerm :: (Eq v, Eq i, Enum i) => v -> i -> Term m v i -> Term m v i
bindTerm variable index subject = case subject of
  Type metadata -> Type metadata
  Application metadata argument function ->
    Application metadata (bindTerm variable index argument) (bindTerm variable index function)
  Abstraction variable' metadata parameter body ->
    Abstraction variable' metadata (bindTerm variable index parameter)
                                  (bindTerm variable (succ index) body)
  Variable variable' index' metadata
    | variable == variable' && index' == toEnum 0 -> Variable variable' index metadata
    | otherwise -> subject

bindContext :: (Eq i, Enum i, Show i) => [Term m Text i] -> i -> Term m Text i -> Term m Text i
bindContext context index term = case context of
  [] -> term
  _:rest -> bindContext rest (succ index) (bindTerm (pack ("$" ++ show index)) index term)

updateTerm :: (Ord i, Enum i) => i -> i -> Term m v i -> Term m v i
updateTerm i k subject = case subject of
  Type metadata -> Type metadata
  Application metadata argument function ->
    Application metadata (updateTerm i k argument) (updateTerm i k function)
  Abstraction variable metadata parameter body ->
    Abstraction variable metadata (updateTerm i k parameter) (updateTerm i (succ k) body)
  Variable variable index metadata
    | index > k -> Variable variable (toEnum $ fromEnum index + fromEnum i - 1) metadata
    | otherwise -> subject

substituteTerm :: (Ord i, Enum i) => i -> Term m v i -> Term m v i -> Term m v i
substituteTerm index replacement' subject =
  case subject of
    Type metadata -> Type metadata
    Application metadata argument function ->
      Application metadata (substituteTerm index replacement' argument)
      (substituteTerm index replacement' function)
    Abstraction variable metadata parameter body ->
      Abstraction variable metadata (substituteTerm index replacement' parameter)
      (substituteTerm (succ index) replacement' body)
    Variable variable index' metadata
      | index' >  index -> Variable variable (pred index') metadata
      | index' == index -> updateTerm index (toEnum 0) replacement'
      | index' <  index -> subject

substituteKind :: (Ord i, Enum i) => i -> Term m v i -> Kind m v i -> Kind m v i
substituteKind index replacement' subject = case subject of
  Kind metadata -> Kind metadata
  Function variable metadata parameter body ->
    Function variable metadata (substituteTerm index replacement' parameter)
                                           (substituteKind (succ index) replacement' body)

whnf :: (Ord i, Enum i) => Term m v i -> Term m v i
whnf reduct =
  case reduct of
    Application metadata argument function ->
      case whnf function of
        Abstraction _ _ _ body ->
          whnf $ substituteTerm (toEnum 1) argument body
        f@_ -> Application metadata argument f
    reduct'@_ -> reduct'

nfTerm :: (Enum i, Ord i) => Term m v i -> Term m v i
nfTerm reduct =
  case reduct of
    Abstraction variable metadata parameter body ->
      let parameter' = nfTerm parameter
          body' = parameter' `par` nfTerm body
      in Abstraction variable metadata parameter' body'
    Application metadata argument function ->
      case whnf function of
        Abstraction _ _ _ body ->
          let arg = nfTerm argument
              bod = arg `seq` substituteTerm (toEnum 1) arg (nfTerm body)
          in nfTerm bod
        f@_ -> Application metadata (nfTerm argument) (nfTerm f)
    _ -> reduct

nfKind :: (Enum i, Ord i) => Kind m v i -> Kind m v i
nfKind reduct =
  case reduct of
    Kind metadata -> Kind metadata
    Function variable metadata parameter body ->
      let param = nfTerm parameter
          body' = param `par` nfKind body
      in Function variable metadata param body'

instance (Enum i, Ord i, Eq v) => Eq (Term m v i) where
  Type _ == Type _ = True
  Variable variable index _ == Variable variable' index' _ = variable == variable' && index == index'
  Application _ argument function == Application _ argument' function' =
    let ftest = function == function'
        atest = ftest `par` argument == argument'
    in ftest && atest
  Abstraction variable metadata parameter body == Abstraction _ _ parameter' body' =
    let ptest = parameter == parameter'
        var   = Variable variable (toEnum 1) metadata
        btest = ptest `par` body == substituteTerm (toEnum 1) var body'
    in ptest && btest
  _ == _ = False

instance (Enum i, Ord i, Eq v, Eq i) => Eq (Kind m v i) where
  Kind _ == Kind _ = True
  Function variable metadata parameter body == Function _ _ parameter' body' =
    let ptest = parameter == parameter'
        var   = Variable variable (toEnum 1) metadata
        btest = ptest `par` body == substituteKind (toEnum 1) var body'
    in ptest && btest
  _ == _ = False

infix 4 ===
class (Enum i, Ord i, Eq i, Eq v) => BetaEq t v i where
  (===) :: (Enum i, Ord i, Eq i, Eq v) => t v i -> t v i -> Bool

instance (Enum i, Ord i, Eq i, Eq v) => BetaEq (Term m) v i where
  a === b = nfTerm a == nfTerm b

instance (Enum i, Ord i, Eq i, Eq v) => BetaEq (Kind m) v i where
  a === b = nfKind a == nfKind b

instance (Enum i, Ord i, Eq i, Eq v) => BetaEq (PseudoTerm m) v i where
  Term a === Term b = nfTerm a == nfTerm b
  Kind' a === Kind' b = nfKind a == nfKind b
  _ === _ = False




data PseudoTerm m v i = Term (Term m v i)
                    | Kind' (Kind m v i)

type Context m v i = [Term m v i]

type Judgement m v i a = ReaderT (Context m v i) (Either String) a

typeSynth :: (Show v, Enum i) => Term SourcePos v i -> Judgement SourcePos v i (PseudoTerm SourcePos v i)
typeSynth term' =
  case term' of
    Type metadata -> return $ Kind' (Kind metadata)
    Variable variable index position -> do
      context <- ask
      if Prelude.length context <= fromEnum index - 1 then
        throwError $ "invalid variable encountered during type synthesis: "
          ++ show variable ++ "\nat " ++ show position
        else return $ Term $ context !! (fromEnum index - 1)
    Application position argument function -> do
      function' <- typeSynth function
      case function' of
        Term f -> return $ Term $ Application position argument f
        Kind' _ -> throwError $ "invalid kind constructed during type synthesis"
          ++ "\nat "  ++ show position
    Abstraction variable position parameter body -> do
      body' <- local (parameter:) (typeSynth body)
      case body' of
        Term b -> return $ Term $ Abstraction variable position parameter b
        Kind' b -> return $ Kind' $ Function variable position parameter b

fTypeSynth :: (Show v, Enum i) => Term SourcePos v i -> Judgement SourcePos v i (PseudoTerm SourcePos v i)
fTypeSynth t = do
  typ <- typeSynth t
  case typ of
    Term t' -> fTypeSynth t'
    Kind' _ -> return typ

termCorrect :: (Enum i, Show v, BetaEq (PseudoTerm SourcePos) v i) => Term SourcePos v i -> Judgement SourcePos v i ()
termCorrect t = case t of
  Type _ -> return ()
  Variable{} ->
    void (typeSynth t)
  Abstraction _ _ parameter body -> do
    termCorrect parameter
    local (parameter:) (termCorrect body)
  Application pos argument function -> do
    argType <- typeSynth argument
    termCorrect argument
    Term funType <- fTypeSynth function
    case nfTerm funType of
      Abstraction _ _ parameter body ->
        if Term parameter === argType then
          termCorrect (nfTerm $ substituteTerm (toEnum 1) argument body)
        else
          let argPosition = show $ getNodeMetadata (Term argument)
              parPosition = show $ getNodeMetadata (Term parameter)
          in throwError $ "argument type and function parameter do not match: \n"
                ++ "at " ++ argPosition ++ " with argument type of:   " ++ show argType ++ "\n"
                ++ "at " ++ parPosition ++ " with parameter value of: " ++ show parameter ++ "\n"
      e@_ -> throwError $ "attempted application onto non function type at "
                ++ show pos ++ "\nwith type: " ++ show e





lineComment, blockComment, spaceToken :: Parser ()
lineComment = Lexer.skipLineComment "--" <|> Lexer.skipLineComment "#"
blockComment = Lexer.skipBlockComment "{-" "-}"
spaceToken = Lexer.space (void spaceChar) lineComment blockComment

lexeme :: Parser a -> Parser a
lexeme = Lexer.lexeme spaceToken

symbol :: String -> Parser String
symbol = Lexer.symbol spaceToken

variableToken :: Parser Text
variableToken = lexeme =<<
  return . pack <$> ((:) <$> (letterChar <|> Text.Megaparsec.char '$') <*> many alphaNumChar)

commaToken, colonToken, typeToken :: Parser ()
commaToken = void (symbol ",")
colonToken = void (symbol ":")
typeToken  = void (symbol "type")

parensToken, bracketsToken :: Parser a -> Parser a
parensToken   = between (symbol "(") (symbol ")")
bracketsToken = between (symbol "[") (symbol "]")

vector :: Parser [Term SourcePos Text Word64]
vector = sepBy1 term commaToken

telescope :: Parser [([(Text, SourcePos)], Term SourcePos Text Word64)]
telescope = sepBy1 ((,) <$> sepBy ((,) <$> variableToken <*> getPosition) spaceToken <*> (colonToken >> term)) commaToken

term :: Parser (Term SourcePos Text Word64)
term =   (Type <$> (typeToken >> getPosition) <?> "type")
     <|> try (Variable <$> variableToken <*> return 0 <*> getPosition <?> "variable")
     <|> (apply <$> parensToken vector <*> term <?> "application")
     <|> (funct <$> bracketsToken telescope <*> term <?> "abstraction")

apply :: [Term SourcePos v i] -> Term SourcePos v i -> Term SourcePos v i
apply = flip $ foldl (\f a -> Application (getNodeMetadata (Term a)) a f)

funct :: [([(Text, SourcePos)], Term SourcePos Text Word64)] -> Term SourcePos Text Word64 -> Term SourcePos Text Word64
funct = flip $ foldr $ \(ns, typ) bod -> foldr (\(n, p) -> Abstraction n p typ . bindTerm n 1) bod ns

parseString :: String -> Text -> Either String (Term SourcePos Text Word64)
parseString filename' input = case parse term filename' input of
  Left err -> Left $ show err
  Right val -> Right val

parseInput :: Text -> Either String (Term SourcePos Text Word64)
parseInput = parseString "stdin"





getNodeMetadata :: PseudoTerm m v i -> m
getNodeMetadata node = case node of
  Term term' -> case term' of
    Type metadata -> metadata
    Variable _ _ metadata -> metadata
    Abstraction _ metadata _ _ -> metadata
    Application metadata _ _ -> metadata
  Kind' kind -> case kind of
    Kind metadata -> metadata
    Function _ metadata _ _ -> metadata

prettyKind :: Show v => Kind m v i -> Doc
prettyKind kind = case kind of
  Kind _ -> text "kind"
  Function variable _ parameter body ->
    let (args, bod) = collectAbstr [(variable, parameter)] body
        argsDoc = list $ fmap (\(n, t) -> text (show n) <> colon <+> prettyTerm t) args
        funDoc = argsDoc `par` prettyKind bod

        collectAbstr args' (Function v _ t b) = collectAbstr (args' ++ [(v, t)]) b
        collectAbstr args' fun = (args', fun)
    in argsDoc </> funDoc

prettyTerm :: Show v => Term m v i -> Doc
prettyTerm term' = case term' of
  Type _ -> text "type"
  Variable variable _ _ -> text $ show variable
  Application _ arg fun ->
    let (args, fun') = collectApply [arg] fun
        argsDoc = tupled $ map prettyTerm args
        funDoc = argsDoc `par` prettyTerm fun'

        collectApply args' (Application _ arg' fun'') = collectApply (args' ++ [arg']) fun''
        collectApply args' fun'' = (args', fun'')
    in argsDoc </> funDoc
  Abstraction var _ typ bod ->
    let (args, bod') = collectAbstr [(var, typ)] bod
        argsDoc = list $ fmap (\(n, t) -> text (show n) <> colon <+> prettyTerm t) args
        funDoc = argsDoc `par` prettyTerm bod'

        collectAbstr args' (Abstraction v _ t b) = collectAbstr (args' ++ [(v, t)]) b
        collectAbstr args' fun = (args', fun)
    in argsDoc </> funDoc

instance Show v => Show (Term m v i) where
  showsPrec _ term' = displayS $ renderPretty 0.4 100 $ prettyTerm term'

instance Show v => Show (Kind m v i) where
  showsPrec _ kind = displayS $ renderPretty 0.4 100 $ prettyKind kind

instance (Show v) => Show (PseudoTerm m v i) where
  show p = case p of
    Term t -> show t
    Kind' k -> show k



data ReplOptions = ReplOptions
  { interactive  :: Bool
  , writeHistory :: Bool
  , historyFileN :: String
  , noHelpPrompt :: Bool
  }

data FileOptions = FileOptions
  { usePreprocessor     :: Bool
  , compileToByteCode   :: Bool
  , saveMetadata        :: Bool
  , preprocessorOptions :: [String]
  }

data MainOptions = MainOptions
  { inFile              :: String
  , echoType            :: Bool
  , useExtension        :: [String]
  , replOptions         :: ReplOptions
  , fileOptions         :: FileOptions
  }

fileGroup :: Maybe Group
fileGroup = Just $ Options.group "file" "file commands" "commands that only affect files"

replGroup :: Maybe Group
replGroup = Just $ Options.group "repl" "repl commands" "commands that only affect the repl"

fileAndReplGroup :: Maybe Group
fileAndReplGroup = Just $ Options.group "file-and-repl" "file and repl commands"
  "commands that both the file compiler and the repl interpreter share"

instance Options ReplOptions where
  defineOptions = pure ReplOptions
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["interactive"]
          , optionDefault = False
          , optionShortFlags = ['n']
          , optionDescription = "start repl"
          , optionGroup = replGroup
          })
    
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["write-history"]
          , optionDefault = True
          , optionShortFlags = ['w']
          , optionDescription = "enable writing to the history file"
          , optionGroup = replGroup
          })
    
    <*> defineOption optionType_string (\o -> o
          { optionLongFlags = ["history-file"]
          , optionDefault = "~/.delta-lambda"
          , optionShortFlags = ['f']
          , optionDescription = "set the history file name and location"
          , optionGroup = replGroup
          })

    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["no-help-prompt"]
          , optionDefault = False
          , optionShortFlags = ['k']
          , optionDescription = "dont issue help prompt on initial repl load"
          , optionGroup = replGroup
          })

instance Options FileOptions where
  defineOptions = pure FileOptions
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["use-preprocessor"]
          , optionDefault = True
          , optionShortFlags = ['p']
          , optionDescription = "enable the preprocessor"
          , optionGroup = fileGroup
          })
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["compile"]
          , optionDefault = False
          , optionShortFlags = ['o']
          , optionDescription = "enable compilation to bytecode"
          , optionGroup = fileGroup
          })
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["without-metadata"]
          , optionDefault = False
          , optionShortFlags = ['s']
          , optionDescription = "save metadata during compilation to bytecode"
          , optionGroup = fileGroup
          })
    <*> defineOption (optionType_list ',' optionType_string) (\o -> o
          { optionLongFlags = ["preprocessor-flags"]
          , optionDefault = []
          , optionShortFlags = ['c']
          , optionDescription = "passes the proceeding flags to the preprocessor"
          , optionGroup = fileGroup
          })

instance Options MainOptions where
  defineOptions = pure MainOptions
    <*> defineOption optionType_string (\o -> o
          { optionLongFlags = ["in-file"]
          , optionDefault = "stdin"
          , optionShortFlags = ['i']
          , optionDescription =
              "input file usable extension must be one of "
              ++ "(*.dl, *.cdl). note that if the \"-n\""
              ++ " or \"--interactive\" flags are not used"
              ++ " in combination with this option, the compiler"
              ++ " will not load the file."
          , optionGroup = fileAndReplGroup
          })
    <*> defineOption optionType_bool (\o -> o
          { optionLongFlags = ["echo-type"]
          , optionDefault = True
          , optionShortFlags = ['e']
          , optionDescription = "enable echoing the type of a term after checking"
          , optionGroup = fileAndReplGroup
          })
    <*> defineOption (optionType_list ',' optionType_string) (\o -> o
          { optionLongFlags = ["use-extensions"]
          , optionDefault = []
          , optionShortFlags = ['x']
          , optionDescription =
              "enables a set of extensions"
          , optionGroup = fileAndReplGroup
          })
    <*> defineOptions
    <*> defineOptions
    
version :: Version
version = Version
  { versionBranch = [0,1,0,0]
  , versionTags = [] -- depreciated, but showVersion throws an exception without it
  }

replPrompt :: Version -> String
replPrompt vers = Prelude.unlines
  [ "    /\\         /\\"
  , "   /  \\       /  \\"
  , "  / /\\ \\     / /\\ \\"
  , " / /__\\ \\   / /  \\ \\"
  , "/________\\ /_/    \\_\\"
  , "Welcome to Delta-Lambda, the 1000 line[s of haskell] theorem prover!"
  , "This is apha level software! no documentation currently, and dragons galore!"
  , "version: " ++ showVersion vers
  ]

helpPrompt :: String
helpPrompt = Prelude.unlines
  [ "/-----------------------------------------------------------------------------\\"
  , "| command | parameters | description                                          |"
  , "| :c      | input term | check that the input term is correct                 |"
  , "| :e      | posn, term | add a term to the context at the position            |"
  , "| :f      | input term | provide the final type of the input term             |"
  , "| :g      | position   | get the type of a term name (or position) in context |"
  , "| :h      |            | display this table                                   |"
  , "| :o      | file name  | open a file and load it                              |"
  , "| :q      |            | quit this repl                                       |"
  , "| :r      | input term | provide the normal form of the input term            |"
  , "| :t      | input term | provide the type of the input term                   |"
  , "| :s      | input flag | set a flag, restart the repl                         |"
  , "| :?      |            | syntax and semantics tutorial (not implemented)      |"
  , "\\-----------------------------------------------------------------------------/"
  ]

data ReplState = ReplState
  { replContext :: Context SourcePos Text Word64
  , replTerm    :: Maybe (Term SourcePos Text Word64)
  , replOpts    :: MainOptions
  }

main :: IO ()
main = runCommand $ \opts _ ->
  if interactive $ replOptions opts then
    repl opts
  else
    if takeExtension (inFile opts) == ".dl" then
      withFile (inFile opts) ReadMode $ parseCheckAndType opts
    else
      if takeExtension (inFile opts) == ".cdl" then
        putStrLn . pack $ "cannot open binary file in non interactive mode!"
      else
        putStrLn . pack $ "extension not recognized: " ++ takeExtension (inFile opts)

preprocess :: MainOptions -> Text -> IO (Either String Text)
preprocess options fileContents =
  if usePreprocessor $ fileOptions options then
    case Language.Preprocessor.Cpphs.parseOptions (preprocessorOptions $ fileOptions options) of
      Left err -> return $ Left err
      Right cpphsOpts -> do
        fileContents' <- runCpphs cpphsOpts (inFile options) (unpack fileContents)
        return $ Right $ pack fileContents'
  else return $ Right fileContents

compile :: (Serialize m , Serialize v, Serialize i) => Term m v i -> MainOptions -> IO ()
compile val options
  | not (saveMetadata $ fileOptions options) = Data.ByteString.writeFile compiledFilename (encode file)
  | otherwise =  Data.ByteString.writeFile compiledFilename (encode cleaned)
  where header = DecompiledFileHeader
          { compiledFileName = pack $ inFile options
          , interpreterVersion = version
          , extensionsUsed = map pack $ useExtension options
          }
        
        file = DecompiledFile
          { decompiledFileHeader = header
          , decompiledTerm = val
          }

        cleaned = transpile (const ()) id id file
        
        compiledFilename = dropExtension (inFile options) -<.> ".cdl"
      

checkAndType :: (Enum i, Show v, BetaEq (PseudoTerm SourcePos) v i) => Term SourcePos v i -> MainOptions -> Context SourcePos v i -> IO (Maybe ())
checkAndType term options context =
  case runReaderT (termCorrect term) context of
    Left err -> do
      putStrLn $ pack err
      return Nothing
    Right _
      | echoType options ->
        case runReaderT (typeSynth term) context of
          Left err -> do
            putStrLn $ pack err
            return Nothing
          Right val -> do
            putStrLn . pack $ show val
            return $ Just ()
      | otherwise -> do
          return $ Just ()

parseCheckAndType :: MainOptions -> Handle -> IO ()
parseCheckAndType options file = do
  input <- Data.Text.IO.hGetContents file
  input' <- preprocess options input
  case input' of
    Left pperr -> putStrLn $ pack pperr
    Right ppval ->
      case parseInput ppval of
        Left perr -> putStrLn $ pack perr
        Right pval -> do
          checked <- checkAndType pval options []
          case checked of
            Nothing -> return ()
            Just _ -> 
              if compileToByteCode $ fileOptions options then
                compile pval options
              else
                return ()

repl :: MainOptions -> IO ()
repl options = do
  Data.Text.IO.putStr $ pack (replPrompt version)
  when (not (noHelpPrompt (replOptions options))) $ Data.Text.IO.putStr (pack helpPrompt)
  if inFile options == "stdin" then
    runInputT replSettings $ loop (initReplState Nothing options)
    else
    if takeExtension (inFile options) == ".dl" then do
      withFile (inFile options) ReadWriteMode $ \h -> do
        input <- Data.Text.IO.hGetContents h
        input' <- preprocess options input
        case input' of
          Left pperr -> putStrLn $ pack pperr
          Right ppval ->
            case parseInput ppval of
              Left perr -> putStrLn $ pack perr
              Right pval -> do
                checked <- checkAndType pval options []
                case checked of
                  Nothing -> return ()
                  Just _ -> runInputT replSettings $ loop (initReplState (Just pval) options)
    else
      if takeExtension (inFile options) == ".cdl" then do
        fileCont <- Data.ByteString.readFile (inFile options)
        case decode fileCont :: Either String (DecompiledFile SourcePos Text Word64) of
          Left perr -> putStrLn $ pack perr
          Right pval -> do
            let fvers = interpreterVersion (decompiledFileHeader pval)
            if fvers < version then
              putStrLn . pack $ "cannot load file \"" ++ inFile options ++ "\n" ++
              "compiler version of file:   " ++ showVersion fvers ++ "\n" ++
              "compiler version of system: " ++ showVersion version
              else do
                let val = decompiledTerm pval
                runInputT replSettings $ loop (initReplState (Just val) options)
      else
        putStrLn . pack $ "extension not recognized: " ++ takeExtension (inFile options)
              
  where replSettings = defaultSettings
          { historyFile = Just $ historyFileN (replOptions options)
          , autoAddHistory = writeHistory (replOptions options)
          }

initReplState term options = ReplState
  { replContext = []
  , replTerm = term
  , replOpts = options
  }

loop :: ReplState -> InputT IO ()
loop state = do
  let count = Prelude.length (replContext state)
  input <- getInputLine $ if count == 0 then "|- " else show count ++ " |- "
  case input of
    Nothing -> loop state
    Just (':':'c':' ':rest) -> do
      case parseInput (pack rest) of
        Left perr -> do
          outputStrLn perr
          loop state
        Right pval -> do
          lift $ checkAndType pval (replOpts state) (replContext state)
          loop state
    Just (':':'e':' ':rest) -> do
      let positionStr = takeWhile (\x -> not (isSpace x) && isNumber x) rest
      let termStr = dropWhile (\x -> not (isSpace x) && isNumber x) rest
      if null positionStr then do
        outputStrLn "please enter a non-negative number"
        loop state
        else do
          let position :: Int = read positionStr
          case parseInput $ pack termStr of
            Left perr -> do
              outputStrLn perr
              loop state
            Right pval -> do
              checked <- lift $ checkAndType pval (replOpts state) (replContext state)
              case checked of
                Nothing -> loop state
                Just _ -> do
                  let (before, after) = splitAt position (replContext state)
                  let pval' = bindContext (replContext state) 1 pval
                  loop (state { replContext = before ++ [pval'] ++ after })
              
    Just (':':'f':' ':rest) ->
      case parseInput (pack rest) of
        Left perr -> do
          outputStrLn perr
          loop state
        Right pval -> do
          checked <- lift $ checkAndType pval (replOpts state) (replContext state)
          case checked of
            Nothing -> loop state
            Just _ ->
              case runReaderT (fTypeSynth pval) (replContext state) of
                Left err -> do
                  outputStrLn err
                  loop state
                Right val -> do
                  outputStrLn $ show val
                  loop state
    Just (':':'g':' ':rest) -> do
      let positionStr = takeWhile (\x -> not (isSpace x) && isNumber x) rest
      if null positionStr then do
        outputStrLn "please enter a non-negative number"
        loop state
        else do
          let position :: Int = read positionStr
          if position > Prelude.length (replContext state) then do
            outputStrLn "position of term in context cannot be outside of context"
            loop state
            else do
            let term = (replContext state) !! (position - 1)
            outputStrLn $ show term
            loop state
    Just (':':'h':       _) -> do
      outputStrLn helpPrompt
      loop state
    Just (':':'o':' ':rest) -> do
      if takeExtension rest == ".dl" then do
        loaded <- lift $ withFile rest ReadWriteMode $ \h -> do
          input <- Data.Text.IO.hGetContents h
          input' <- preprocess (replOpts state) input
          case input' of
            Left pperr -> do
              putStrLn $ pack pperr
              return Nothing
            Right ppval ->
              case parseInput ppval of
                Left perr -> do
                  putStrLn $ pack perr
                  return Nothing
                Right pval -> do
                  checked <- checkAndType pval (replOpts state) []
                  case checked of
                    Nothing -> return Nothing
                    Just _ -> return $ Just pval
        case loaded of
          Nothing -> loop state
          Just val -> loop (state{ replTerm = Just val, replContext = [] })
        else
        if takeExtension rest == ".cdl" then do
          fileCont <- lift $ Data.ByteString.readFile rest
          case decode fileCont :: Either String (DecompiledFile SourcePos Text Word64) of
            Left perr -> do
              outputStrLn perr
              loop state
            Right pval -> do
              let fvers = interpreterVersion (decompiledFileHeader pval)
              if fvers < version then do
                outputStrLn $ "cannot load file \"" ++ rest ++ "\n" ++
                  "compiler version of file:   " ++ showVersion fvers ++ "\n" ++
                  "compiler version of system: " ++ showVersion version
                loop state
                else do
                  let val = decompiledTerm pval
                  loop (state { replTerm = Just val, replContext = [] })
        else do
          outputStrLn $ "extension not recognized: " ++ rest
          loop state
    Just (':':'q':       _) -> return ()    
    Just (':':'r':' ':rest) ->
      case parseInput (pack rest) of
        Left perr -> do
          outputStrLn perr
          loop state
        Right pval -> do
          checked <- lift $ checkAndType pval (replOpts state) (replContext state)
          case checked of
            Nothing -> loop state
            Just _ -> do
              outputStrLn $ show . nfTerm $ pval
              loop state
    Just (':':'t':' ':rest) -> 
      case parseInput (pack rest) of
        Left perr -> do
          outputStrLn perr
          loop state
        Right pval -> do
          checked <- lift $ checkAndType pval (replOpts state) (replContext state)
          case checked of
            Nothing -> loop state
            Just _ ->
              case runReaderT (typeSynth pval) (replContext state) of
                Left err -> do
                  outputStrLn err
                  loop state
                Right val -> do
                  outputStrLn $ show val
                  loop state
    Just (':':'s':' ':rest) -> do
      let pOpts = Options.parseOptions (split rest " ")
      case parsedOptions pOpts of
        Nothing -> do
          case parsedError pOpts of
            Nothing -> do
              outputStrLn $ "an unknow error occured during proccessing of the options: "
                ++ "\n" ++ rest
              loop state
            Just err -> do
              outputStrLn err
              loop state
        Just opts -> loop (state {replOpts = opts})
    Just (':':'?':_) -> do
      outputStrLn "the turorial is under construction!"
      loop state
    Just input -> do
      outputStrLn $ "input : " ++ input ++ "\n not recognised"
      loop state

split :: String -> String -> [String]
split str pat = helper str pat [] [] where 
    helper :: String -> String -> String -> String -> [String]
    helper [] _  n _ = [n] ++ []
    helper xs [] n _ = [n] ++ (split xs pat)
    helper (x:xs) (y:ys) n m
        | x /= y = helper (xs) pat ((n++m)++[x]) m
        | otherwise = helper xs ys n (m++[y])