{-# LANGUAGE OverloadedStrings #-}

module Oughta.Pos
  ( Pos(..)
  , incPos
  , Loc(..)
  , printLoc
  , Span(..)
  , startLoc
  , endLoc
  , printSpan
  ) where

import Data.List qualified as List
import Data.Maybe qualified as Maybe
import Data.Text qualified as Text
import Data.Text (Text)
import Prelude hiding (last, lines, span)

-- | Helper, not exported
tshow :: Show a => a -> Text
tshow :: forall a. Show a => a -> Text
tshow = String -> Text
Text.pack (String -> Text) -> (a -> String) -> a -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show

-- | A source position
data Pos
  = Pos
    { Pos -> Int
line :: {-# UNPACK #-} !Int
    , Pos -> Int
col :: {-# UNPACK #-} !Int
    }

-- | Move a position forward past a chunk of text.
incPos :: Pos -> Text -> Pos
incPos :: Pos -> Text -> Pos
incPos (Pos Int
l Int
c) Text
t =
  let newlines :: Int
newlines = HasCallStack => Text -> Text -> Int
Text -> Text -> Int
Text.count Text
"\n" Text
t in
  Int -> Int -> Pos
Pos (Int
l Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
newlines) (Int -> Pos) -> Int -> Pos
forall a b. (a -> b) -> a -> b
$
    if Text
"\n" Text -> Text -> Bool
`Text.isSuffixOf` Text
t
    then Int
1
    else
      case [Text] -> [Text]
forall a. [a] -> [a]
List.reverse (Text -> [Text]
Text.lines Text
t) of
        [] -> Int
c
        (Text
only : []) -> Int
c Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Text -> Int
Text.length Text
only
        (Text
last : [Text]
_) -> Text -> Int
Text.length Text
last

printPos :: Pos -> Text
printPos :: Pos -> Text
printPos Pos
p = Int -> Text
forall a. Show a => a -> Text
tshow (Pos -> Int
line Pos
p) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow (Pos -> Int
col Pos
p)

-- | A source location
data Loc
  = Loc
    { Loc -> Maybe String
path :: !(Maybe FilePath)
    , Loc -> Pos
pos :: {-# UNPACK #-} !Pos
    }

printLoc :: Loc -> Text
printLoc :: Loc -> Text
printLoc Loc
l =
  let p :: String
p = String -> Maybe String -> String
forall a. a -> Maybe a -> a
Maybe.fromMaybe String
"<unknown file>" (Loc -> Maybe String
path Loc
l) in
  String -> Text
Text.pack String
p Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Pos -> Text
printPos (Loc -> Pos
pos Loc
l)

-- | A source span
data Span
  = Span
  { Span -> Maybe String
spanPath :: !(Maybe FilePath)
  , Span -> Pos
spanStart :: {-# UNPACK #-} !Pos
  , Span -> Pos
spanEnd :: {-# UNPACK #-} !Pos
  }

startLoc :: Span -> Loc
startLoc :: Span -> Loc
startLoc Span
s = Maybe String -> Pos -> Loc
Loc (Span -> Maybe String
spanPath Span
s) (Span -> Pos
spanStart Span
s)

endLoc :: Span -> Loc
endLoc :: Span -> Loc
endLoc Span
s = Maybe String -> Pos -> Loc
Loc (Span -> Maybe String
spanPath Span
s) (Span -> Pos
spanEnd Span
s)

printSpan :: Span -> Text
printSpan :: Span -> Text
printSpan Span
s =
  let p :: String
p = String -> Maybe String -> String
forall a. a -> Maybe a -> a
Maybe.fromMaybe String
"<unknown file>" (Span -> Maybe String
spanPath Span
s) in
  String -> Text
Text.pack String
p Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Pos -> Text
printPos (Span -> Pos
spanStart Span
s) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Pos -> Text
printPos (Span -> Pos
spanEnd Span
s)