{-# LANGUAGE CPP        #-}
{-# LANGUAGE RankNTypes #-}

{- |

Write your grammar once and get both parser and pretty-printer, for
free.

> data Person = Person
>   { pName    :: String
>   , pAddress :: String
>   , pAge     :: Maybe Int
>   } deriving (Show)
>
> personGrammar :: SexpG Person
> personGrammar =
>   $(grammarFor 'Person) .               -- construct Person from
>     list (                              -- a list with
>       el (sym "person") >>>             -- symbol "person",
>       el string'        >>>             -- some string,
>       props (                           -- and properties
>         Kw "address" .:  string' >>>    -- :address with string value,
>         Kw "age"     .:? int ))         -- and optional :age int proprety

So now we can use @personGrammar@ to parse S-expessions to @Person@
record and pretty-print any @Person@ back to S-expression.

> (person "John Doe" :address "42 Whatever str." :age 25)

will parse into:

> Person {pName = "John Doe", pAddress = "42 Whatever str.", pAge = Just 25}

and the record will pretty-print back into:

> (person
>  "John Doe"
>  :address
>  "42 Whatever str."
>  :age
>  25)

Grammar types diagram:

>     --------------------------------------
>     |              AtomGrammar           |
>     --------------------------------------
>         ^
>         |  atomic grammar combinators
>         v
> ------------------------------------------------------
> |                      SexpGrammar                   |
> ------------------------------------------------------
>         | list, vect     ^              ^
>         v                | el, rest     |
>     ----------------------------------  |
>     |           SeqGrammar           |  |
>     ----------------------------------  | (.:)
>              | props                    | (.:?)
>              v                          |
>          -------------------------------------
>          |             PropGrammar           |
>          -------------------------------------

-}

module Language.SexpGrammar
  ( Grammar
  , SexpG
  , SexpG_
  -- * Combinators
  -- ** Primitive grammars
  , iso
  , embedPrism
  , embedParsePrism
  , push
  , pushForget
  , module Language.SexpGrammar.Combinators
  -- * TemplateHaskell helpers
  , grammarFor
  -- * Grammar types
  , SexpGrammar
  , AtomGrammar
  , SeqGrammar
  , PropGrammar
  -- * Parsing and printing
  , parseFromString
  , parseFromFile
  , prettyToText
  , prettyToFile
  -- ** Low-level printing and parsing
  , parse
  , gen
  -- * Typeclass for Sexp grammars
  , SexpIso (..)
  -- * Re-exported from stack-prism
  , StackPrism
  , (:-) (..)
  ) where

#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Data.StackPrism
import Data.InvertibleGrammar
import Data.InvertibleGrammar.TH
import Language.SexpGrammar.Base
import Language.SexpGrammar.Combinators
import Language.SexpGrammar.Class

import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy.IO as T
import Language.Sexp (parseSexp, printSexp)

parseFromString :: SexpG a -> String -> Either String a
parseFromString g input =
  parseSexp "<string>" input >>= parse g

parseFromFile :: SexpG a -> FilePath -> IO (Either String a)
parseFromFile g fn = do
  str <- readFile fn
  return $ parseSexp fn str >>= parse g

prettyToText :: SexpG a -> a -> Either String Text
prettyToText g =
  fmap printSexp . gen g

prettyToFile :: FilePath -> SexpG a -> a -> IO (Either String ())
prettyToFile fn g a = do
  case gen g a of
    Left msg -> return $ Left msg
    Right s  -> Right <$> T.writeFile fn (printSexp s)