{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
module Data.SCargot.Repr
( -- $reprs
-- * Elementary SExpr representation
SExpr(..)
-- * Rich SExpr representation
, RichSExpr(..)
, toRich
, fromRich
-- * Well-Formed SExpr representation
, WellFormedSExpr(..)
, toWellFormed
, fromWellFormed
) where
import Data.Data (Data)
import Data.Foldable (Foldable(..))
import Data.Traversable (Traversable(..))
import Data.Typeable (Typeable)
import GHC.Exts (IsList(..), IsString(..))
#if !MIN_VERSION_base(4,8,0)
import Prelude hiding (foldr)
#endif
-- | All S-Expressions can be understood as a sequence
-- of @cons@ cells (represented here by 'SCons'), the
-- empty list @nil@ (represented by 'SNil') or an
-- @atom@.
data SExpr atom
= SCons (SExpr atom) (SExpr atom)
| SAtom atom
| SNil
deriving (Eq, Show, Read, Functor, Data, Typeable, Foldable, Traversable)
instance IsString atom => IsString (SExpr atom) where
fromString = SAtom . fromString
instance IsList (SExpr atom) where
type Item (SExpr atom) = SExpr atom
fromList = foldr SCons SNil
toList = go
where go (SCons x xs) = x : go xs
go SNil = []
go (SAtom {}) = error "Unable to turn atom into list"
-- | Sometimes the cons-based interface is too low
-- level, and we'd rather have the lists themselves
-- exposed. In this case, we have 'RSList' to
-- represent a well-formed cons list, and 'RSDotted'
-- to represent an improper list of the form
-- @(a b c . d)@. This representation is based on
-- the structure of the parsed S-Expression, and not on
-- how it was originally represented: thus, @(a . (b))@ is going to
-- be represented as @RSList[RSAtom a, RSAtom b]@
-- despite having been originally represented as a
-- dotted list.
data RichSExpr atom
= RSList [RichSExpr atom]
| RSDotted [RichSExpr atom] atom
| RSAtom atom
deriving (Eq, Show, Read, Functor, Data, Typeable, Foldable, Traversable)
instance IsString atom => IsString (RichSExpr atom) where
fromString = RSAtom . fromString
instance IsList (RichSExpr atom) where
type Item (RichSExpr atom) = RichSExpr atom
fromList = RSList
toList (RSList xs) = xs
toList (RSDotted {}) = error "Unable to turn dotted list into haskell list"
toList (RSAtom {}) = error "Unable to turn atom into Haskell list"
-- | It should always be true that
--
-- > fromRich (toRich x) == x
--
-- and that
--
-- > toRich (fromRich x) == x
toRich :: SExpr atom -> RichSExpr atom
toRich (SAtom a) = RSAtom a
toRich (SCons x xs) = go xs (toRich x:)
where go (SAtom a) rs = RSDotted (rs []) a
go SNil rs = RSList (rs [])
go (SCons y ys) rs = go ys (rs . (toRich y:))
toRich SNil = RSList []
-- | This follows the same laws as 'toRich'.
fromRich :: RichSExpr atom -> SExpr atom
fromRich (RSAtom a) = SAtom a
fromRich (RSList xs) = foldr SCons SNil (map fromRich xs)
fromRich (RSDotted xs x) = foldr SCons (SAtom x) (map fromRich xs)
-- | A well-formed s-expression is one which does not
-- contain any dotted lists. This means that not
-- every value of @SExpr a@ can be converted to a
-- @WellFormedSExpr a@, although the opposite is
-- fine.
data WellFormedSExpr atom
= WFSList [WellFormedSExpr atom]
| WFSAtom atom
deriving (Eq, Show, Read, Functor, Data, Typeable, Foldable, Traversable)
instance IsList (WellFormedSExpr atom) where
type Item (WellFormedSExpr atom) = WellFormedSExpr atom
fromList = WFSList
toList (WFSList xs) = xs
toList (WFSAtom {}) = error "Unable to turn atom into Haskell list"
instance IsString atom => IsString (WellFormedSExpr atom) where
fromString = WFSAtom . fromString
-- | This will be @Nothing@ if the argument contains an
-- improper list. It should hold that
--
-- > toWellFormed (fromWellFormed x) == Right x
--
-- and also (more tediously) that
--
-- > case toWellFormed x of
-- > Left _ -> True
-- > Right y -> x == fromWellFormed y
toWellFormed :: SExpr atom -> Either String (WellFormedSExpr atom)
toWellFormed SNil = return (WFSList [])
toWellFormed (SAtom a) = return (WFSAtom a)
toWellFormed (SCons x xs) = do
x' <- toWellFormed x
go xs (x':)
where go (SAtom _) _ = Left "Found atom in cdr position"
go SNil rs = return (WFSList (rs []))
go (SCons y ys) rs = do
y' <- toWellFormed y
go ys (rs . (y':))
-- | Convert a WellFormedSExpr back into a SExpr.
fromWellFormed :: WellFormedSExpr atom -> SExpr atom
fromWellFormed (WFSAtom a) = SAtom a
fromWellFormed (WFSList xs) =
foldr SCons SNil (map fromWellFormed xs)
{- $reprs
This module contains several different representations for
s-expressions. The s-cargot library underlying uses the
'SExpr' type as its representation type, which is a binary
tree representation with an arbitrary type for its leaves.
This type is not always convenient to manipulate in Haskell
code, this module defines two alternate representations
which turn a sequence of nested right-branching cons pairs
into Haskell lists: that is to say, they transform between
@
SCons a (SCons b (SCons c SNil)) \<=\> RSList [a, b, c]
@
These two types differ in how they handle non-well-formed
lists, i.e. lists that end with an atom. The 'RichSExpr'
format handles this with a special constructor for lists
that end in an atom:
@
SCons a (SCons b (SAtom c)) \<=\> RSDotted [a, b] c
@
On the other hand, the 'WellFormedSExpr' type elects
not to handle this case. This is unusual for Lisp source code,
but is a reasonable choice for configuration or data
storage formats that use s-expressions, where
non-well-formed lists would be an unnecessary
complication.
To make working with these types less verbose, there are other
modules that export pattern aliases and helper functions: these
can be found at "Data.SCargot.Repr.Basic",
"Data.SCargot.Repr.Rich", and "Data.SCargot.Repr.WellFormed".
-}