{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Generics.Text
-- Copyright   :  (c) The University of Glasgow, CWI 2001--2003
-- License     :  BSD-style (see the LICENSE file)
-- 
-- Maintainer  :  generics@haskell.org
-- Stability   :  experimental
-- Portability :  non-portable (uses Data.Generics.Basics)
--
-- \"Scrap your boilerplate\" --- Generic programming in Haskell 
-- See <http://www.cs.uu.nl/wiki/GenericProgramming/SYB>. The present module
-- provides generic operations for text serialisation of terms.
--
-----------------------------------------------------------------------------

module Data.Generics.Text (

    -- * Generic show
    gshow, gshows,

    -- * Generic read
    gread

 ) where

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

#ifdef __HADDOCK__
import Prelude
#endif
import Control.Monad
import Data.Data
import Data.Generics.Aliases
import Text.ParserCombinators.ReadP
import Text.Read.Lex

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


-- | Generic 'show': an alternative to @deriving@ 'Show'.
--
-- @since 0.1.0.0
gshow :: Data a => a -> String
gshow x = gshows x ""

-- | Generic 'shows'.
--
-- @since 0.2
gshows :: Data a => a -> ShowS

-- This is a prefix-show using surrounding "(" and ")",
-- where we recurse into subterms with gmapQ.
gshows = ( \t ->
                showChar '('
              . (showString . showConstr . toConstr $ t)
              . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t)
              . showChar ')'
         ) `extQ` (shows :: String -> ShowS)


-- | Generic 'reads' (not 'read'): an alternative to @deriving@ 'Read'.
--
-- @since 0.1.0.0
gread :: Data a => ReadS a

{-

This is a read operation which insists on prefix notation.  (The
Haskell 98 read deals with infix operators subject to associativity
and precedence as well.) We use fromConstrM to "parse" the input. To be
precise, fromConstrM is used for all types except String. The
type-specific case for String uses basic String read.

-}

gread = readP_to_S gread'

 where

  -- Helper for recursive read
  gread' :: Data a' => ReadP a'
  gread' = allButString `extR` stringCase

   where

    -- A specific case for strings
    stringCase :: ReadP String
    stringCase = readS_to_P reads

    -- Determine result type
    myDataType = dataTypeOf (getArg allButString)
     where
      getArg :: ReadP a'' -> a''
      getArg = undefined

    -- The generic default for gread
    allButString =
      do
                -- Drop "  (  "
         skipSpaces                     -- Discard leading space
         _ <- char '('                  -- Parse '('
         skipSpaces                     -- Discard following space

                -- Do the real work
         str  <- parseConstr            -- Get a lexeme for the constructor
         con  <- str2con str            -- Convert it to a Constr (may fail)
         x    <- fromConstrM gread' con -- Read the children

                -- Drop "  )  "
         skipSpaces                     -- Discard leading space
         _ <- char ')'                  -- Parse ')'
         skipSpaces                     -- Discard following space

         return x

    -- Turn string into constructor driven by the requested result type,
    -- failing in the monad if it isn't a constructor of this data type
    str2con :: String -> ReadP Constr
    str2con = maybe mzero return
            . readConstr myDataType

    -- Get a Constr's string at the front of an input string
    parseConstr :: ReadP String
    parseConstr =
               string "[]"     -- Compound lexeme "[]"
          <++  string "()"     -- singleton "()"
          <++  infixOp         -- Infix operator in parantheses
          <++  negativeNumber  -- prefix "-" and number literal
          <++  hsLex           -- Ordinary constructors and literals

    -- Handle infix operators such as (:)
    infixOp :: ReadP String
    infixOp = do c1  <- char '('
                 str <- munch1 (not . (==) ')')
                 c2  <- char ')'
                 return $ [c1] ++ str ++ [c2]

    -- Handle negative number literals
    negativeNumber :: ReadP String
    negativeNumber = do c1 <- char '-'
                        str <- hsLex
                        return $ c1 : str
