content: -- | The shell of the interpreter.  It processes command-line parameters,
  -- and calls the line scanner, tokenizer, parser, and interpreter.
  
  module Language.VintageBasic.Executer where
  
  import Control.Monad.CPST.DurableTraps(die)
  import Control.Monad.State(get)
  import Control.Monad.Trans(liftIO)
  import Data.List(deleteFirstsBy,nubBy,sortBy)
  import Language.VintageBasic.Interpreter(interpLines)
  import Language.VintageBasic.LexCommon(Tagged(..))
  import Language.VintageBasic.LineScanner(RawLine,rawLinesP)
  import Language.VintageBasic.BasicMonad(BasicState(..),Code,printString,runProgram)
  import Language.VintageBasic.Parser(statementListP)
  import Language.VintageBasic.Result(Result(..))
  import Language.VintageBasic.Syntax(Line(..))
  import Language.VintageBasic.Tokenizer(TokenizedLine,taggedTokensP)
  import Text.ParserCombinators.Parsec(parse,setPosition,sourceLine)
  
  -- | Given a file path, loads and executes the BASIC code.
  executeFile :: FilePath -> Code ()
  executeFile fileName = do
      text <- liftIO $ readFile fileName
      execute fileName text
  
  -- | Executes BASIC code from a string. The file path is provided only for error reporting.
  execute :: FilePath -> String -> Code ()
  execute fileName text = do
      rawLines <- scanLines fileName text
      tokenizedLines <- sequence [tokenizeLine rawLine | rawLine <- rawLines]
      parsedLines <- sequence [parseLine tokenizedLine | tokenizedLine <- tokenizedLines]
      state <- get
      liftIO $ runProgram (inputStream state) (outputStream state) $ interpLines parsedLines
  
  -- | Transforms the BASIC source into a series of 'RawLine's using the 'rawLinesP' LineScanner.
  scanLines :: String -> String -> Code [RawLine]
  scanLines fileName text =
      case parse rawLinesP fileName text of
          (Left parseError) -> die (ScanError parseError)
          (Right rawLines)  -> sortNubLines rawLines
  
  -- | Tokenizes a 'RawLine' into a 'TokenizedLine' using the 'taggedTokensP' Tokenizer.
  tokenizeLine :: RawLine -> Code TokenizedLine
  tokenizeLine (Tagged pos text) =
      case parse (setPosition pos >> taggedTokensP) "" text of
          (Left parseError)    -> die (SyntaxError parseError)
          (Right taggedTokens) -> return (Tagged pos taggedTokens)
  
  -- | Parses a 'TokenizedLine' to yield a 'Line', using the 'statementListP' Parser.
  parseLine :: TokenizedLine -> Code Line
  parseLine (Tagged pos taggedTokens) = do
      case parse (setPosition pos >> statementListP) "" taggedTokens of
          (Left parseError)     -> die (SyntaxError parseError)
          (Right statementList) -> return (Line (sourceLine pos) statementList)
  
  -- | Specifies an ordering for 'RawLine's so they can be sorted.
  rawLineOrdering :: RawLine -> RawLine -> Ordering
  rawLineOrdering (Tagged pos1 _) (Tagged pos2 _) = compare (sourceLine pos1) (sourceLine pos2)
  
  rawLinesEq :: RawLine -> RawLine -> Bool
  rawLinesEq l1 l2 = rawLineOrdering l1 l2 == EQ
  
  -- | This function reverses before nubbing so that later lines take precedence.
  sortNubLines :: [RawLine] -> Code [RawLine]
  sortNubLines lineList = do
      let sortedLines = sortBy rawLineOrdering lineList
          reversedSortedLines = reverse sortedLines
          reversedNubbedLines = nubBy rawLinesEq reversedSortedLines
          nubbedLines = reverse reversedNubbedLines
          duplicateLines = deleteFirstsBy rawLinesEq lineList nubbedLines
      sequence_ [printString ("!SUPERSEDING PREVIOUS LINE " ++ show (sourceLine pos) ++ "\n")
          | (Tagged pos _) <- duplicateLines]
      return nubbedLines
  