{- |
    Module      :  $Header$
    Description :  Spans in a source file
    Copyright   :  (c) 2016 Jan Tikovsky
                       2016 Finn Teegen
    License     :  BSD-3-clause

    Maintainer  :  jrt@informatik.uni-kiel.de
    Stability   :  experimental
    Portability :  portable

    This module implements a data type for span information in a source file and
    respective functions to operate on them. A source file span consists
    of a filename, a start position and an end position.

    In addition, the type 'SrcRef' identifies the path to an expression in
    the abstract syntax tree by argument positions, which is used for
    debugging purposes.
-}
{-# LANGUAGE CPP #-}
module Curry.Base.Span where

#if __GLASGOW_HASKELL__ >= 804
import Prelude hiding ((<>))
#endif

import System.FilePath

import Curry.Base.Position hiding (file)
import Curry.Base.Pretty

data Span
  -- |Normal source code span
  = Span
    { file     :: FilePath -- ^ 'FilePath' of the source file
    , start    :: Position -- ^ start position
    , end      :: Position -- ^ end position
    }
  -- |no span
  | NoSpan
    deriving (Eq, Ord, Read, Show)

instance Pretty Span where
  pPrint = ppSpan

instance HasPosition Span where
  setPosition p NoSpan       = Span "" p NoPos
  setPosition p (Span f _ e) = Span f p e

  getPosition NoSpan       = NoPos
  getPosition (Span _ p _) = p

-- |Show a 'Span' as a 'String'
showSpan :: Span -> String
showSpan = show . ppSpan

-- |Pretty print a 'Span'
ppSpan :: Span -> Doc
ppSpan s@(Span f _ _)
  | null f    = startEnd
  | otherwise = text (normalise f) <> comma <+> startEnd
  where startEnd = ppPositions s
ppSpan _ = empty

-- |Pretty print the start and end position of a 'Span'
ppPositions :: Span -> Doc
ppPositions (Span _ s e) =  text "startPos:" <+> ppLine s <> comma
                        <+> text "endPos:"   <+> ppLine e
ppPositions _            = empty

fstSpan :: FilePath -> Span
fstSpan fn = Span fn (first fn) (first fn)

-- |Compute the column of the start position of a 'Span'
startCol :: Span -> Int
startCol (Span _ p _) = column p
startCol _            = 0

nextSpan :: Span -> Span
nextSpan sp = incrSpan sp 1

incrSpan :: Span -> Int -> Span
incrSpan (Span fn s e) n = Span fn (incr s n) (incr e n)
incrSpan sp            _ = sp

-- TODO: Rename to tab and nl as soon as positions are completely replaced by spans

-- |Convert a span to a (start) position
-- TODO: This function should be removed as soon as positions are completely replaced by spans
-- in the frontend
span2Pos :: Span -> Position
span2Pos (Span _ p _) = p
span2Pos NoSpan       = NoPos

combineSpans :: Span -> Span -> Span
combineSpans sp1 sp2 = Span f s e
  where s = start sp1
        e = end sp2
        f = file sp1

-- |First position after the next tabulator
tabSpan :: Span -> Span
tabSpan (Span fn s e) = Span fn (tab s) (tab e)
tabSpan sp            = sp

-- |First position of the next line
nlSpan :: Span -> Span
nlSpan (Span fn s e) = Span fn (nl s) (nl e)
nlSpan sp            = sp

addSpan :: Span -> (a, [Span]) -> (a, [Span])
addSpan sp (a, ss) = (a, sp:ss)

-- |Distance of a span, i.e. the line and column distance between start
-- and end position
type Distance = (Int, Int)

-- |Set the distance of a span, i.e. update its end position
setDistance :: Span -> Distance -> Span
setDistance (Span fn p _) d = Span fn p (p `moveBy` d)
setDistance s             _ = s

-- |Move position by given distance
moveBy :: Position -> Distance -> Position
moveBy (Position fn l c) (ld, cd) = Position fn (l + ld) (c + cd)
moveBy p                 _        = p