content: 
  module DDC.Driver.Interface.Input
          ( InputInterface (..)
          , InputState     (..)
          , Input          (..)
          , readInput
          , inputLine)
  where
  import DDC.Driver.Interface.Source
  import DDC.Data.ListUtils
  import System.Directory
  import Data.List
  import Data.Char
  
  
  -- InputState -----------------------------------------------------------------
  -- Interpreter input state
  data InputState command
          = InputState
          { -- Function to parse commands.
            inputParseCommand :: String -> Maybe (command, String)
  
            -- Input mode.
          , inputMode         :: Input
  
            -- Command that we're still receiving input for,
            -- along with the line number it started on.
          , inputCommand      :: Maybe (Maybe command, Int)
  
  
            -- The current line number in the command stream.
          , inputLineNumber   :: Int
  
            -- Accumulation of current input buffer.
          , inputAcc         :: String }
  
  
  -- InputInterface -------------------------------------------------------------
  -- | What interface is being used.
  data InputInterface
          -- | Read commands from unix command-line args.
          = InputInterfaceArgs
  
          -- | Read commands interactively from the console.
          | InputInterfaceConsole
  
          -- | Read commands from the file with this name.
          | InputInterfaceBatch    FilePath
          deriving (Eq, Show)
  
  
  -- Input ----------------------------------------------------------------------
  -- | How we're reading the current expression.
  data Input
          -- | Read input line-by-line, using a backslash at the end of the
          --   line to continue to the next.
          = InputLine
  
          -- | Read input as a block terminated by a double semicolon (;;)
          | InputBlock
  
          -- | Read input from a file specified on the prompt
          | InputFile     FilePath
          deriving (Eq, Show)
  
  
  -- | Read the input mode from the front of a string.
  readInput :: String -> (Input, String)
  readInput ss
          | isPrefixOf ".." ss
          = (InputBlock, drop 2 ss)
  
          | isPrefixOf "<" ss
          , filePath      <- dropWhile isSpace (drop 1 ss)
          = (InputFile filePath, drop (length filePath) ss)
  
          | otherwise
          = (InputLine, ss)
  
  -------------------------------------------------------------------------------
  inputLine 
          :: InputInterface
          -> InputState c 
          -> String
          -> IO ( InputState c
                , Maybe (Source, Maybe c, String))
                          -- Just for complete command.
                          --  .. then Maybe for the default command.
  
  inputLine interface inputState chunk
   | InputState readCmd mode mCommand lineNumber acc <- inputState
   = do   
          -- If this is the first line then try to read the command and
          --  input mode from the front so we know how to continue.
          -- If we can't read an explicit command then assume this is 
          --  an expression to evaluate.
          let (cmd, lineStart, (input, rest))
               = case mCommand of
                  -- We haven't started a command yet.
                  Nothing
                   -> case readCmd chunk of
                       Just (cmd', rest') -> (Just cmd', lineNumber, readInput rest')
                       Nothing            -> (Nothing,   lineNumber, (InputLine, chunk))
                  
                  -- We've already started a command, and this is more input for it.
                  Just (cmd', lineStart')
                   -> (cmd', lineStart', (mode, chunk))
  
          let source 
                  -- We were instructed to read the program from a file.
                  -- Report this file as the source location, independent
                  -- of how we were instructed to read it.
                  | InputFile filePath    <- input
                  = SourceFile filePath
  
                  -- The program was embedded in the command stream.
                  | otherwise
                  = case interface of
                          InputInterfaceArgs        -> SourceArgs
                          InputInterfaceConsole     -> SourceConsole lineStart
                          InputInterfaceBatch file  -> SourceBatch   file lineStart
  
  
          case input of
           -- For line-by-line mode, if the line ends with backslash then keep
           -- reading, otherwise run the command.
           -- We also convert the backslash to a newline so the source
           -- position comes out right in parser error messages.
           InputLine
            | not $ null rest
            , last rest == '\\'
            , Just initRest       <- takeInit rest
            -> return     ( inputState
                                  { inputCommand    =  (Just (cmd, lineStart))
                                  , inputLineNumber = lineNumber + 1
                                  , inputAcc        = acc ++ initRest ++ "\n" }
                          , Nothing)
  
            | otherwise
            -> return     ( inputState
                                  { inputMode       = InputLine
                                  , inputCommand    = Nothing
                                  , inputLineNumber = lineNumber + 1
                                  , inputAcc        = [] }
                          , Just (source, cmd, acc ++ rest))
  
  
           -- For block mode, if the line ends with ';;' then run the command,
           -- otherwise keep reading.
           InputBlock
            | isSuffixOf ";;" rest
            -> do let rest' = take (length rest - 2) rest
                  return  ( inputState
                                  { inputMode       = InputLine
                                  , inputCommand    = Nothing
                                  , inputLineNumber = lineNumber + 1
                                  , inputAcc        = [] }
                         , Just (source, cmd, acc ++ rest'))
  
            | otherwise
            ->    return ( inputState
                                  { inputMode       = input
                                  , inputCommand    = Just (cmd, lineStart)
                                  , inputLineNumber = lineNumber + 1
                                  , inputAcc        = acc ++ rest ++ "\n" }
                         , Nothing)
  
           -- Read input from a file
           InputFile filePath
            -> do exists          <- doesFileExist filePath
                  if exists 
                   then do        
                          contents  <- readFile filePath
                          return  ( inputState
                                          { inputMode     = InputLine
                                          , inputCommand  = Nothing
                                          , inputLineNumber = lineNumber + 1
                                          , inputAcc      = [] }
                                  , Just (source, cmd, contents))
                   else do
                          putStrLn "No such file."
                          return  ( inputState
                                          { inputMode     = InputLine
                                          , inputCommand  = Nothing
                                          , inputLineNumber = lineNumber + 1
                                          , inputAcc      = [] }
                                  , Nothing)
  
  
  