{-# OPTIONS  #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Language.JavaScript.ParserMonad 
-- Based on language-python version by Bernie Pope
-- Copyright   : (c) 2009 Bernie Pope 
-- License     : BSD-style
-- Stability   : experimental
-- Portability : ghc
--
-- Monad support for JavaScript parser and lexer. 
-----------------------------------------------------------------------------

module Language.JavaScript.Parser.ParserMonad 
   ( P
   , execParser
   , execParserKeepComments
   , runParser
   , thenP
   , returnP
   , alexSetInput  
   , alexGetInput  
   , setLocation
   , getLocation
   , getInput
   , setInput
   , getLastToken
   , setLastToken
   -- , setLastEOL
   -- , getLastEOL
   , ParseError (..)
   , ParseState (..)
   , initialState
   , addComment
   , getComments
   , spanError
   , AlexInput 
   , Byte  
   ) where

import Control.Applicative ((<$>))
import Control.Monad.Error as Error
import Control.Monad.State.Class
import Control.Monad.State.Strict as State
import Language.JavaScript.Parser.ParseError (ParseError (..))
import Language.JavaScript.Parser.SrcLocation (AlexPosn (..), alexStartPos, alexSpanEmpty, Span (..))
import Language.JavaScript.Parser.Token (Token (..))
import Prelude hiding (span)
import Data.Word (Word8)

internalError :: String -> P a 
internalError = throwError . StrError 

spanError :: Span a => a -> String -> P b 
--spanError x str = throwError $ StrError $ unwords [prettyText $ getSpan x, str]
spanError x str = throwError $ StrError $ show ([show (getSpan x), str])

type Byte = Word8

type AlexInput = (AlexPosn,     -- current position,
                  Char,         -- previous char
                  [Byte],       -- pending bytes on current char
                  String)       -- current input string


data ParseState = ParseState {
        alex_pos :: !AlexPosn,  -- position at current input location
        alex_inp :: String,     -- the current input
        alex_chr :: !Char,      -- the character before the input
        alex_bytes :: [Byte],
        alex_scd :: !Int        -- the current startcode
        
    , previousToken :: !Token  -- the previous token
    , comments :: [Token]      -- accumulated comments 
    }

initialState :: String -> ParseState
initialState inp 
   = ParseState 
   { alex_pos = alexStartPos
   , alex_inp = inp
   , alex_chr = '\n'          
   , alex_bytes = []             
   , alex_scd = 0               
   , previousToken = initToken
   , comments = []
   }
{-
data ParseState = 
   ParseState 
   { location :: !SrcLocation -- position at current input location
   -- , input :: !String         -- the current input
   , input :: !AlexInput      -- the current input     
   , previousToken :: !Token  -- the previous token
   -- , lastEOL :: !SrcSpan      -- location of the most recent end-of-line encountered
   , comments :: [Token]      -- accumulated comments 
   }
   deriving Show
-}

initToken :: Token
--initToken = NewlineToken SpanEmpty 
initToken = CommentToken alexSpanEmpty ""



type P a = StateT ParseState (Either ParseError) a

execParser :: P a -> ParseState -> Either ParseError a
execParser = evalStateT 

execParserKeepComments :: P a -> ParseState -> Either ParseError (a, [Token])
execParserKeepComments parser state = 
   evalStateT (parser >>= \x -> getComments >>= \c -> return (x, c)) state

runParser :: P a -> ParseState -> Either ParseError (a, ParseState)
runParser = runStateT 

{-# INLINE returnP #-}
returnP :: a -> P a
returnP = return 

{-# INLINE thenP #-}
thenP :: P a -> (a -> P b) -> P b
thenP = (>>=)

{-
failP :: SrcSpan -> [String] -> P a
failP span strs = throwError (prettyText span ++ ": " ++ unwords strs) 
-}
{-
setLastEOL :: SrcSpan -> P ()
setLastEOL span = modify $ \s -> s { lastEOL = span }

getLastEOL :: P SrcSpan
getLastEOL = gets lastEOL
-}

alexGetInput :: P AlexInput
alexGetInput
 -- = P $ \s@ParseState{alex_pos=pos,alex_chr=c,alex_bytes=bs,alex_inp=inp} -> 
 --        Right (s, (pos,c,bs,inp))
 = do
   pos <- gets alex_pos
   c   <- gets alex_chr
   bs  <- gets alex_bytes
   inp <- gets alex_inp
   return (pos,c,bs,inp)

alexSetInput :: AlexInput -> P ()
alexSetInput (pos,c,bs,inp)
 -- = P $ \s -> case s{alex_pos=pos,alex_chr=c,alex_bytes=bs,alex_inp=inp} of
 --                  s@(ParseState{}) -> Right (s, ())
 = modify $ \s -> s{alex_pos=pos,alex_chr=c,alex_bytes=bs,alex_inp=inp}


setLocation :: AlexPosn -> P ()
setLocation loc = modify $ \s -> s { alex_pos = loc } 

getLocation :: P AlexPosn
getLocation = gets alex_pos 

getInput :: P String 
getInput = gets alex_inp 

setInput :: String -> P ()
setInput inp = modify $ \s -> s { alex_inp = inp }

getLastToken :: P Token
getLastToken = gets previousToken 

setLastToken :: Token -> P ()
setLastToken tok = modify $ \s -> s { previousToken = tok } 

addComment :: Token -> P ()
addComment c = do
   oldComments <- gets comments
   modify $ \s -> s { comments = c : oldComments }

getComments :: P [Token]
getComments = reverse <$> gets comments