{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE Safe              #-}

-- |
-- Copyright: © Herbert Valerio Riedel 2015-2018
-- SPDX-License-Identifier: GPL-2.0-or-later
--
module Data.YAML.Pos
    ( Pos(..)
    , prettyPosWithSource
    ) where

import qualified Data.ByteString.Lazy     as BL
import qualified Data.YAML.Token.Encoding as Enc
import           Util

-- | Position in parsed YAML source
--
-- See also 'prettyPosWithSource'.
--
-- __NOTE__: if 'posCharOffset' is negative the 'Pos' value doesn't refer to a proper location; this may be emitted in corner cases when no proper location can be inferred.
data Pos = Pos
    { Pos -> Int
posByteOffset :: !Int -- ^ 0-based byte offset
    , Pos -> Int
posCharOffset :: !Int -- ^ 0-based character (Unicode code-point) offset
    , Pos -> Int
posLine       :: !Int -- ^ 1-based line number
    , Pos -> Int
posColumn     :: !Int -- ^ 0-based character (Unicode code-point) column number
    } deriving (Pos -> Pos -> Bool
(Pos -> Pos -> Bool) -> (Pos -> Pos -> Bool) -> Eq Pos
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Pos -> Pos -> Bool
== :: Pos -> Pos -> Bool
$c/= :: Pos -> Pos -> Bool
/= :: Pos -> Pos -> Bool
Eq, Int -> Pos -> ShowS
[Pos] -> ShowS
Pos -> [Char]
(Int -> Pos -> ShowS)
-> (Pos -> [Char]) -> ([Pos] -> ShowS) -> Show Pos
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Pos -> ShowS
showsPrec :: Int -> Pos -> ShowS
$cshow :: Pos -> [Char]
show :: Pos -> [Char]
$cshowList :: [Pos] -> ShowS
showList :: [Pos] -> ShowS
Show, (forall x. Pos -> Rep Pos x)
-> (forall x. Rep Pos x -> Pos) -> Generic Pos
forall x. Rep Pos x -> Pos
forall x. Pos -> Rep Pos x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Pos -> Rep Pos x
from :: forall x. Pos -> Rep Pos x
$cto :: forall x. Rep Pos x -> Pos
to :: forall x. Rep Pos x -> Pos
Generic)

-- | @since 0.2.0
instance NFData Pos where rnf :: Pos -> ()
rnf !Pos
_ = ()

-- | Pretty prints a 'Pos' together with the line the 'Pos' refers and the column position.
--
-- The input 'BL.ByteString' must be the same that was passed to the
-- YAML decoding function that produced the 'Pos' value. The 'String'
-- argument is inserted right after the @<line>:<column>:@ in the
-- first line. The pretty-printed position result 'String' will be
-- terminated by a trailing newline.
--
-- For instance,
--
-- @
-- 'prettyPosWithSource' somePos someInput " error" ++ "unexpected character\\n"
-- @ results in
--
-- > 11:7: error
-- >     |
-- >  11 | foo: | bar
-- >     |        ^
-- > unexpected character
--
-- @since 0.2.1
prettyPosWithSource :: Pos -> BL.ByteString -> String -> String
prettyPosWithSource :: Pos -> ByteString -> ShowS
prettyPosWithSource Pos{Int
posCharOffset :: Pos -> Int
posByteOffset :: Pos -> Int
posLine :: Pos -> Int
posColumn :: Pos -> Int
posByteOffset :: Int
posCharOffset :: Int
posLine :: Int
posColumn :: Int
..} ByteString
source [Char]
msg
  | Int
posCharOffset Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 Bool -> Bool -> Bool
|| Int
posByteOffset Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 = [Char]
"0:0:" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
msg [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"\n" -- unproper location
  | Bool
otherwise = [[Char]] -> [Char]
unlines
    [ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
posLine [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
":" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
posColumn [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
":" [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
msg
    , [Char]
lpfx
    , [Char]
lnostr [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"| " [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
line
    , [Char]
lpfx [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> Char -> [Char]
forall a. Int -> a -> [a]
replicate Int
posColumn Char
' ' [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"^"
    ]

  where
    lnostr :: [Char]
lnostr = [Char]
" " [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
posLine [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
" "
    lpfx :: [Char]
lpfx   = (Char
' ' Char -> ShowS
forall a b. a -> [b] -> [a]
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ [Char]
lnostr) [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ [Char]
"| "

    (Int
_,[(Int, Char)]
lstart) = Int -> ByteString -> (Int, [(Int, Char)])
findLineStartByByteOffset Int
posByteOffset ByteString
source
    line :: [Char]
line = ((Int, Char) -> Char) -> [(Int, Char)] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map (Int, Char) -> Char
forall a b. (a, b) -> b
snd ([(Int, Char)] -> [Char]) -> [(Int, Char)] -> [Char]
forall a b. (a -> b) -> a -> b
$ ((Int, Char) -> Bool) -> [(Int, Char)] -> [(Int, Char)]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Bool -> Bool
not (Bool -> Bool) -> ((Int, Char) -> Bool) -> (Int, Char) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isNL (Char -> Bool) -> ((Int, Char) -> Char) -> (Int, Char) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, Char) -> Char
forall a b. (a, b) -> b
snd) [(Int, Char)]
lstart

    isNL :: Char -> Bool
isNL Char
c = Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\r' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\n'

findLineStartByByteOffset :: Int -> BL.ByteString -> (Int,[(Int,Char)])
findLineStartByByteOffset :: Int -> ByteString -> (Int, [(Int, Char)])
findLineStartByByteOffset Int
bofs0 ByteString
input = Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
0 [(Int, Char)]
inputChars [(Int, Char)]
inputChars
  where
    (Encoding
_,[(Int, Char)]
inputChars) = ByteString -> (Encoding, [(Int, Char)])
Enc.decode ByteString
input

    go :: Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
lsOfs [(Int, Char)]
lsChars [] = (Int
lsOfs,[(Int, Char)]
lsChars)
    go Int
lsOfs [(Int, Char)]
lsChars ((Int
ofs',Char
_):[(Int, Char)]
_)
      | Int
bofs0 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
ofs' = (Int
lsOfs,[(Int, Char)]
lsChars)

    go Int
_ [(Int, Char)]
_ ((Int
_,Char
'\r'):(Int
ofs',Char
'\n'):[(Int, Char)]
rest) = Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
ofs' [(Int, Char)]
rest [(Int, Char)]
rest
    go Int
_ [(Int, Char)]
_ ((Int
ofs',Char
'\r'):[(Int, Char)]
rest) = Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
ofs' [(Int, Char)]
rest [(Int, Char)]
rest
    go Int
_ [(Int, Char)]
_ ((Int
ofs',Char
'\n'):[(Int, Char)]
rest) = Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
ofs' [(Int, Char)]
rest [(Int, Char)]
rest
    go Int
lsOfs [(Int, Char)]
lsChars ((Int, Char)
_:[(Int, Char)]
rest) = Int -> [(Int, Char)] -> [(Int, Char)] -> (Int, [(Int, Char)])
go Int
lsOfs [(Int, Char)]
lsChars [(Int, Char)]
rest