{-# 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 property 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 ( Sexp (..) , Sexp.Atom (..) , Sexp.Kw (..) , Grammar , SexpG , SexpG_ , (:-) (..) -- * Combinators -- ** Primitive grammars , iso , osi , partialIso , partialOsi , push , pushForget , module Language.SexpGrammar.Combinators -- * Grammar types , SexpGrammar , AtomGrammar , SeqGrammar , PropGrammar -- * Decoding and encoding (machine-oriented) , decode , decodeWith , encode , encodeWith -- * Parsing and printing (human-oriented) , decodeNamed , decodeNamedWith , encodePretty , encodePrettyWith -- * Parsing and encoding to Sexp , parseSexp , genSexp , Mismatch , expected , unexpected -- * Typeclass for Sexp grammars , SexpIso (..) ) where import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.Text.Lazy as TL import Data.InvertibleGrammar import Data.InvertibleGrammar.Monad import Language.Sexp (Sexp) import qualified Language.Sexp as Sexp import Language.SexpGrammar.Base import Language.SexpGrammar.Class import Language.SexpGrammar.Combinators ---------------------------------------------------------------------- -- Sexp interface -- | Run grammar in parsing direction parseSexp :: SexpG a -> Sexp -> Either String a parseSexp g a = runGrammarMonad Sexp.dummyPos showPos (runParse g a) where showPos (Sexp.Position fn line col) = fn ++ ":" ++ show line ++ ":" ++ show col -- | Run grammar in generating direction genSexp :: SexpG a -> a -> Either String Sexp genSexp g a = runGrammarMonad Sexp.dummyPos (const "<no location information>") (runGen g a) ---------------------------------------------------------------------- -- ByteString interface (machine-oriented) -- | Deserialize a value from a lazy 'ByteString'. The input must -- contain exactly one S-expression. Comments are ignored. decode :: SexpIso a => TL.Text -> Either String a decode = decodeWith sexpIso -- | Like 'decode' but uses specified grammar. decodeWith :: SexpG a -> TL.Text -> Either String a decodeWith g input = Sexp.decode input >>= parseSexp g -- | Serialize a value as a lazy 'ByteString' with a non-formatted -- S-expression encode :: SexpIso a => a -> Either String ByteString encode = encodeWith sexpIso -- | Like 'encode' but uses specified grammar. encodeWith :: SexpG a -> a -> Either String ByteString encodeWith g = fmap Sexp.encode . genSexp g ---------------------------------------------------------------------- -- ByteString interface (human-oriented) -- | Parse a value from 'ByteString'. The input must contain exactly -- one S-expression. Unlike 'decode' it takes an additional argument -- with a file name which is being parsed. It is used for error -- messages. decodeNamed :: SexpIso a => FilePath -> TL.Text -> Either String a decodeNamed fn = decodeNamedWith sexpIso fn -- | Like 'decodeNamed' but uses specified grammar. decodeNamedWith :: SexpG a -> FilePath -> TL.Text -> Either String a decodeNamedWith g fn input = Sexp.parseSexp fn input >>= parseSexp g -- | Pretty-prints a value serialized to a lazy 'ByteString'. encodePretty :: SexpIso a => a -> Either String TL.Text encodePretty = encodePrettyWith sexpIso -- | Like 'encodePretty' but uses specified grammar. encodePrettyWith :: SexpG a -> a -> Either String TL.Text encodePrettyWith g = fmap Sexp.prettySexp . genSexp g