{-| Module      :  LexerMessage
    License     :  GPL

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

module Helium.Parser.LexerMessage
    ( LexerError(..)
    , LexerErrorInfo(..)
    , LexerWarning(..)
    , LexerWarningInfo(..)
    , keepOneTabWarning
    , sourcePosToRange
    , isLooksLikeFloatWarningInfo
    ) where

import Text.ParserCombinators.Parsec.Pos
import Helium.Syntax.UHA_Syntax(Range(..), Position(..))
import Helium.StaticAnalysis.Messages.Messages
import qualified Helium.Utils.Texts as Texts

instance HasMessage LexerError where
    getRanges (LexerError _ (StillOpenAtEOF brackets)) =
        reverse (map (sourcePosToRange . fst) brackets)
    getRanges (LexerError pos (UnexpectedClose _ pos2 _)) =
        map sourcePosToRange [pos, pos2]
    getRanges (LexerError pos _) =
        [ sourcePosToRange pos ]
    getMessage (LexerError _ info) = 
        let (line:rest) = showLexerErrorInfo info
        in MessageOneLiner (MessageString line) :
            [ MessageHints Texts.hint [ MessageString s | s <- rest ] ]

sourcePosToRange :: SourcePos -> Range
sourcePosToRange pos = 
    let name = sourceName pos; line = sourceLine pos; col = sourceColumn pos
        position = Position_Position name line col
    in Range_Range position position
    
data LexerError =
    LexerError SourcePos LexerErrorInfo
    
data LexerErrorInfo
    = UnterminatedComment
    | MissingExponentDigits
    | UnexpectedChar Char

    | IllegalEscapeInChar
    | EmptyChar
    | IllegalCharInChar
    | NonTerminatedChar (Maybe String)
    | EOFInChar

    | EOFInString
    | IllegalEscapeInString
    | NewLineInString
    | IllegalCharInString

    | TooManyClose Char
        -- In UnexpectedClose, first char is the closing bracket we see, 
        -- second char is the closing bracket we would like to see first
        -- e.g. [(1,3]  =>  UnexpectedClose ']' ... ')'
    | UnexpectedClose Char SourcePos Char
    | StillOpenAtEOF [(SourcePos, Char)]

showLexerErrorInfo :: LexerErrorInfo -> [String]
showLexerErrorInfo info =
    case info of
        UnterminatedComment   -> [ Texts.lexerUnterminatedComment ]
        MissingExponentDigits -> [ Texts.lexerMissingExponentDigits 
                                 , correctFloats 
                                 ]
        UnexpectedChar c      -> [ Texts.lexerUnexpectedChar c ]
        
        IllegalEscapeInChar   -> [ Texts.lexerIllegalEscapeInChar, correctChars ]
        EmptyChar             -> [ Texts.lexerEmptyChar, correctChars ]
        IllegalCharInChar     -> [ Texts.lexerIllegalCharInChar, correctChars ]
        NonTerminatedChar mn -> 
            [ Texts.lexerNonTerminatedChar
            , correctChars
            ] ++ case mn of
                   Nothing -> []
                   Just name -> [ Texts.lexerInfixHint name ]
        EOFInChar               -> [ Texts.lexerEOFInChar, correctChars]
        
        EOFInString             -> [ Texts.lexerEOFInString,              correctStrings ]
        IllegalEscapeInString   -> [ Texts.lexerIllegalEscapeInString,    correctStrings ]
        NewLineInString         -> [ Texts.lexerNewLineInString,          correctStrings ]
        IllegalCharInString     -> [ Texts.lexerIllegalCharInString,      correctStrings]
                
        TooManyClose c          -> [ Texts.lexerTooManyClose c ]
        UnexpectedClose c1 _ c2 ->   Texts.lexerUnexpectedClose c1 c2
        StillOpenAtEOF [b]      -> [ Texts.lexerStillOpenAtEOF [ show (snd b) ] ]
        StillOpenAtEOF bs       -> [ Texts.lexerStillOpenAtEOF (reverse (map (show.snd) bs)) ]
            -- 'reverse' because positions will be sorted and brackets are
            -- reported in reversed order

correctFloats, correctChars, correctStrings :: String
correctFloats  = Texts.lexerCorrectFloats
correctChars   = Texts.lexerCorrectChars 
correctStrings = Texts.lexerCorrectStrings

instance HasMessage LexerWarning where
    getRanges (LexerWarning pos (NestedComment pos2)) =
       map sourcePosToRange [ pos, pos2 ]
    getRanges (LexerWarning pos _) =
        [ sourcePosToRange pos ]
    getMessage (LexerWarning _ info) = 
        let (line:rest) = showLexerWarningInfo info
        in MessageOneLiner (MessageString (Texts.warning ++ ": " ++ line)) :
            [ MessageHints Texts.hint [ MessageString s | s <- rest ] ]

data LexerWarning =
    LexerWarning SourcePos LexerWarningInfo

data LexerWarningInfo 
    = TabCharacter
    | LooksLikeFloatNoFraction String
    | LooksLikeFloatNoDigits String
    | NestedComment SourcePos
    | CommentOperator

showLexerWarningInfo :: LexerWarningInfo -> [String]
showLexerWarningInfo info = 
    case info of
        TabCharacter                    -> Texts.lexerTabCharacter
        LooksLikeFloatNoFraction digits -> Texts.lexerLooksLikeFloatNoFraction digits
        LooksLikeFloatNoDigits fraction -> Texts.lexerLooksLikeFloatNoDigits fraction
        NestedComment _                 -> Texts.lexerNestedComment
        CommentOperator                 -> Texts.lexerCommentOperator
            
      
keepOneTabWarning :: [LexerWarning] -> [LexerWarning]
keepOneTabWarning = keepOneTab True
  where
    keepOneTab isFirst (warning@(LexerWarning _ TabCharacter):rest)
        | isFirst   = warning : keepOneTab False rest
        | otherwise = keepOneTab isFirst rest
    keepOneTab isFirst (warning:rest) = 
        warning : keepOneTab isFirst rest
    keepOneTab _ [] = []

isLooksLikeFloatWarningInfo :: LexerWarningInfo -> Bool
isLooksLikeFloatWarningInfo warningInfo =
   case warningInfo of
      LooksLikeFloatNoFraction _ -> True
      LooksLikeFloatNoDigits _   -> True
      _                          -> False