{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE PatternSynonyms      #-}
{-# LANGUAGE Trustworthy          #-}
{-# LANGUAGE TypeSynonymInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Language.Sexp.Located
  (
  -- * Parse and print
    decode
  , parseSexp
  , parseSexps
  , parseSexpWithPos
  , parseSexpsWithPos
  , encode
  , format
  -- * Type
  , Sexp
  , pattern Atom
  , pattern Number
  , pattern Symbol
  , pattern String
  , pattern ParenList
  , pattern BracketList
  , pattern BraceList
  , pattern Modified
  -- ** Internal types
  , SexpF (..)
  , Atom (..)
  , Prefix (..)
  , LocatedBy (..)
  , Position (..)
  , Compose (..)
  , Fix (..)
  , dummyPos
  -- * Conversion
  , fromSimple
  , toSimple
  ) where

import Data.ByteString.Lazy.Char8 (ByteString, unpack)
import Data.Functor.Compose
import Data.Scientific (Scientific)
import Data.Text (Text)

import Language.Sexp.Types
import Language.Sexp.Lexer (lexSexp)
import Language.Sexp.Parser (parseSexp_, parseSexps_)
import qualified Language.Sexp.Pretty as Internal
import qualified Language.Sexp.Encode as Internal

-- | S-expression type annotated with positions. Useful for further
-- parsing.
type Sexp = Fix (Compose (LocatedBy Position) SexpF)

instance {-# OVERLAPPING #-} Show Sexp where
  show = unpack . encode

-- | Deserialise a 'Sexp' from a string
decode :: ByteString -> Either String Sexp
decode = parseSexp "<string>"

-- | Serialise a 'Sexp' into a compact string
encode :: Sexp -> ByteString
encode = Internal.encode . stripLocation

-- | Serialise a 'Sexp' into a pretty-printed string
format :: Sexp -> ByteString
format = Internal.format . stripLocation

----------------------------------------------------------------------

fromSimple :: Fix SexpF -> Fix (Compose (LocatedBy Position) SexpF)
fromSimple = addLocation dummyPos

toSimple :: Fix (Compose (LocatedBy Position) SexpF) -> Fix SexpF
toSimple = stripLocation

----------------------------------------------------------------------

pattern Atom :: Atom -> Sexp
pattern Atom a <- Fix (Compose (_ :< AtomF a))
  where Atom a =  Fix (Compose (dummyPos :< AtomF a))

pattern Number :: Scientific -> Sexp
pattern Number a <- Fix (Compose (_ :< AtomF (AtomNumber a)))
  where Number a =  Fix (Compose (dummyPos :< AtomF (AtomNumber a)))

pattern Symbol :: Text -> Sexp
pattern Symbol a <- Fix (Compose (_ :< AtomF (AtomSymbol a)))
  where Symbol a =  Fix (Compose (dummyPos :< AtomF (AtomSymbol a)))

pattern String :: Text -> Sexp
pattern String a <- Fix (Compose (_ :< AtomF (AtomString a)))
  where String a =  Fix (Compose (dummyPos :< AtomF (AtomString a)))

pattern ParenList :: [Sexp] -> Sexp
pattern ParenList ls <- Fix (Compose (_ :< ParenListF ls))
  where ParenList ls =  Fix (Compose (dummyPos :< ParenListF ls))

pattern BracketList :: [Sexp] -> Sexp
pattern BracketList ls <- Fix (Compose (_ :< BracketListF ls))
  where BracketList ls =  Fix (Compose (dummyPos :< BracketListF ls))

pattern BraceList :: [Sexp] -> Sexp
pattern BraceList ls <- Fix (Compose (_ :< BraceListF ls))
  where BraceList ls =  Fix (Compose (dummyPos :< BraceListF ls))

pattern Modified :: Prefix -> Sexp -> Sexp
pattern Modified q s <- Fix (Compose (_ :< ModifiedF q s))
  where Modified q s =  Fix (Compose (dummyPos :< ModifiedF q s))

-- | Parse a 'Sexp' from a string.
parseSexp :: FilePath -> ByteString -> Either String Sexp
parseSexp fn inp = parseSexp_ (lexSexp (Position fn 1 0) inp)

-- | Parse multiple 'Sexp' from a string.
parseSexps :: FilePath -> ByteString -> Either String [Sexp]
parseSexps fn inp = parseSexps_ (lexSexp (Position fn 1 0) inp)

-- | Parse a 'Sexp' from a string, starting from a given
-- position. Useful for embedding into other parsers.
parseSexpWithPos :: Position -> ByteString -> Either String Sexp
parseSexpWithPos pos inp = parseSexp_ (lexSexp pos inp)

-- | Parse multiple 'Sexp' from a string, starting from a given
-- position. Useful for embedding into other parsers.
parseSexpsWithPos :: Position -> ByteString -> Either String [Sexp]
parseSexpsWithPos pos inp = parseSexps_ (lexSexp pos inp)
