{-| Module      :  LexerMonad
    License     :  GPL

    Maintainer  :  helium@cs.uu.nl
    Stability   :  experimental
    Portability :  portable
-}

module Helium.Parser.LexerMonad
    ( LexerMonad
    , getPos, incPos, nextPos, addPos
    , openBracket, closeBracket, checkBracketsAtEOF
    , lexerError, lexerWarning
    , runLexerMonad
    , getOpts
    ) where

import Helium.Main.Args
import Helium.Parser.LexerMessage
import Text.ParserCombinators.Parsec.Pos

type Bracket = (SourcePos, Char)

-- Output monad: [LexerWarning]
-- State monad: SourcePos and [Bracket]
newtype LexerMonad a = 
    LM ([Option] -> SourcePos -> [Bracket] -> 
        Either LexerError (a, [LexerWarning], SourcePos, [Bracket]))

unLM :: LexerMonad t -> [Option] -> SourcePos -> [Bracket]
          -> Either LexerError (t, [LexerWarning], SourcePos, [Bracket])
unLM (LM x) = x

bindLM :: LexerMonad a -> (a -> LexerMonad b) -> LexerMonad b
bindLM (LM f) g = 
    LM (\opts pos brackets -> 
        case f opts pos brackets of
            Left err -> Left err
            Right (a, warnings, pos2, brackets2) -> 
                case unLM (g a) opts pos2 brackets2 of
                    Left err -> Left err
                    Right (b, moreWarnings, pos3, brackets3) ->
                        Right (b, warnings ++ moreWarnings, pos3, brackets3))

returnLM :: a -> LexerMonad a
returnLM x = LM (\_ pos brackets -> Right (x, [], pos, brackets))

instance Functor LexerMonad where
    fmap f m = m >>= pure . f
    
instance Applicative LexerMonad where
    pure      = returnLM
    f1 <*> f2 = f1 >>= \v1 -> f2 >>= (pure . v1)
    
instance Monad LexerMonad where
    return = pure
    (>>=) = bindLM

runLexerMonad :: [Option] -> String -> LexerMonad a -> Either LexerError (a, [LexerWarning])
runLexerMonad opts fileName (LM f) = 
    case f opts (initialPos fileName) [] of
        Left err -> Left err
        Right (a, warnings, _, _) -> Right (a, keepOneTabWarning warnings)

getOpts :: LexerMonad [Option]
getOpts = LM (\opts pos brackets -> Right (opts, [], pos, brackets))

getPos :: LexerMonad SourcePos
getPos = LM (\_ pos brackets -> Right (pos, [], pos, brackets))

incPos :: Int -> LexerMonad ()
incPos i = LM (\_ pos brackets -> Right ((), [], addPos i pos, brackets))

nextPos :: Char -> LexerMonad ()
nextPos c = LM (\_ pos brackets -> Right ( (), [], updatePosChar pos c, brackets ))

lexerError :: LexerErrorInfo -> SourcePos -> LexerMonad a
lexerError err pos = 
    LM (\_ _ _ -> Left (LexerError pos err))

lexerWarning :: LexerWarningInfo -> SourcePos -> LexerMonad ()
lexerWarning warning warningPos = 
    LM (\_ pos brackets -> 
        Right ((), [LexerWarning warningPos warning], pos, brackets))
    
addPos :: Int -> SourcePos -> SourcePos
addPos i pos = incSourceColumn pos i

openBracket :: Char -> LexerMonad ()
openBracket c = LM (\_ pos brackets ->
    Right ( (), [], pos, (pos, c) : brackets ))

closeBracket :: Char -> LexerMonad ()
closeBracket c = LM (\_ pos brackets ->
    case brackets of
        [] -> Left (LexerError pos (TooManyClose c))
        (pos2, c2):rest
            | matchBracket c2 c ->
                Right ((), [], pos, rest)
            | otherwise ->
                Left (LexerError pos (UnexpectedClose c pos2 c2))        
    )
    
checkBracketsAtEOF :: LexerMonad ()
checkBracketsAtEOF = LM (\_ pos brackets ->
    case brackets of
        [] -> Right ((), [], pos, [])
        _  -> Left (LexerError pos (StillOpenAtEOF brackets))
    )
    
matchBracket :: Char -> Char -> Bool
matchBracket '(' ')' = True
matchBracket '[' ']' = True
matchBracket '{' '}' = True
matchBracket _ _ = False
