{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} module Data.SCargot.Repr.Basic ( -- * Basic 'SExpr' representation R.SExpr(..) -- * Constructing and Deconstructing , cons , uncons -- * Shorthand Patterns , pattern (:::) , pattern A , pattern L , pattern DL , pattern Nil -- * Lenses , _car , _cdr -- * Useful processing functions , fromPair , fromList , fromAtom , asPair , asList , isAtom , asAtom , asAssoc ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative (Applicative, (<$>), (<*>), pure) #endif import Data.SCargot.Repr as R -- | A traversal with access to the first element of a pair. -- -- >>> import Lens.Family -- >>> set _car (A "elephant") (A "one" ::: A "two" ::: A "three" ::: Nil) -- A "elelphant" ::: A "two" ::: A "three" ::: Nil -- >>> set _car (A "two" ::: A "three" ::: Nil) (A "one" ::: A "elephant") -- (A "two" ::: A "three" ::: Nil) ::: A "elephant" _car :: Applicative f => (SExpr a -> f (SExpr a)) -> SExpr a -> f (SExpr a) _car f (SCons x xs) = (:::) <$> f x <*> pure xs _car _ (SAtom a) = pure (A a) _car _ SNil = pure SNil -- | A traversal with access to the second element of a pair. -- -- >>> import Lens.Family -- >>> set _cdr (A "elephant") (A "one" ::: A "two" ::: A "three" ::: Nil) -- A "one" ::: A "elephant" -- >>> set _cdr (A "two" ::: A "three" ::: Nil) (A "one" ::: A "elephant") -- A "one" ::: A "two" ::: A "three" ::: Nil _cdr :: Applicative f => (SExpr a -> f (SExpr a)) -> SExpr a -> f (SExpr a) _cdr f (SCons x xs) = (:::) <$> pure x <*> f xs _cdr _ (SAtom a) = pure (A a) _cdr _ SNil = pure Nil -- | Produce the head and tail of the s-expression (if possible). -- -- >>> uncons (A "el" ::: A "eph" ::: A "ant" ::: Nil) -- Just (A "el",SCons (SAtom "eph") (SCons (SAtom "ant") SNil)) uncons :: SExpr a -> Maybe (SExpr a, SExpr a) uncons (SCons x xs) = Just (x, xs) uncons _ = Nothing -- | Combine the two s-expressions into a new one. -- -- >>> cons (A "el") (L ["eph", A "ant"]) -- SCons (SAtom "el) (SCons (SAtom "eph") (SCons (SAtom "ant") SNil)) cons :: SExpr a -> SExpr a -> SExpr a cons = SCons gatherDList :: SExpr a -> Maybe ([SExpr a], a) gatherDList SNil = Nothing gatherDList SAtom {} = Nothing gatherDList sx = go sx where go SNil = Nothing go (SAtom a) = return ([], a) go (SCons x xs) = do (ys, a) <- go xs return (x:ys, a) infixr 5 ::: -- | A shorter infix alias for `SCons` -- -- >>> A "pachy" ::: A "derm" -- SCons (SAtom "pachy") (SAtom "derm") #if MIN_VERSION_base(4,8,0) pattern (:::) :: SExpr a -> SExpr a -> SExpr a #endif pattern x ::: xs = SCons x xs -- | A shorter alias for `SAtom` -- -- >>> A "elephant" -- SAtom "elephant" #if MIN_VERSION_base(4,8,0) pattern A :: a -> SExpr a #endif pattern A x = SAtom x -- | A (slightly) shorter alias for `SNil` -- -- >>> Nil -- SNil #if MIN_VERSION_base(4,8,0) pattern Nil :: SExpr a #endif pattern Nil = SNil -- | An alias for matching a proper list. -- -- >>> L [A "pachy", A "derm"] -- SExpr (SAtom "pachy") (SExpr (SAtom "derm") SNil) #if MIN_VERSION_base(4,8,0) pattern L :: [SExpr a] -> SExpr a #endif pattern L xs <- (gatherList -> Right xs) #if MIN_VERSION_base(4,8,0) where L [] = SNil L (x:xs) = SCons x (L xs) #endif -- | An alias for matching a dotted list. -- -- >>> DL [A "pachy"] A "derm" -- SExpr (SAtom "pachy") (SAtom "derm") #if MIN_VERSION_base(4,8,0) pattern DL :: [SExpr a] -> a -> SExpr a #endif pattern DL xs x <- (gatherDList -> Just (xs, x)) #if MIN_VERSION_base(4,8,0) where DL [] a = SAtom a DL (x:xs) a = SCons x (DL xs a) #endif getShape :: SExpr a -> String getShape Nil = "empty list" getShape sx = go (0 :: Int) sx where go n SNil = "list of length " ++ show n go n SAtom {} = "dotted list of length " ++ show n go n (SCons _ xs) = go (n+1) xs -- | Utility function for parsing a pair of things. -- -- >>> fromPair (isAtom "pachy") (asAtom return) (A "pachy" ::: A "derm" ::: Nil) -- Right ((), "derm") -- >>> fromPair (isAtom "pachy") fromAtom (A "pachy" ::: Nil) -- Left "Expected two-element list" fromPair :: (SExpr t -> Either String a) -> (SExpr t -> Either String b) -> SExpr t -> Either String (a, b) fromPair pl pr (l ::: r ::: Nil) = (,) <$> pl l <*> pr r fromPair _ _ sx = Left ("fromPair: expected two-element list; found " ++ getShape sx) -- | Utility function for parsing a list of things. fromList :: (SExpr t -> Either String a) -> SExpr t -> Either String [a] fromList p (s ::: ss) = (:) <$> p s <*> fromList p ss fromList _ Nil = pure [] fromList _ sx = Left ("fromList: expected list; found " ++ getShape sx) -- | Utility function for parsing a single atom fromAtom :: SExpr t -> Either String t fromAtom (A a) = return a fromAtom sx = Left ("fromAtom: expected atom; found list" ++ getShape sx) gatherList :: SExpr t -> Either String [SExpr t] gatherList (x ::: xs) = (:) <$> pure x <*> gatherList xs gatherList Nil = pure [] gatherList sx = Left ("gatherList: expected list; found " ++ getShape sx) -- | Parse a two-element list (NOT a dotted pair) using the -- provided function. -- -- >>> let go (A l) (A r) = return (l ++ r); go _ _ = Left "expected atoms" -- >>> asPair go (A "pachy" ::: A "derm" ::: Nil) -- Right "pachyderm" -- >>> asPair go (A "elephant" ::: Nil) -- Left "asPair: expected two-element list; found list of length 1" asPair :: ((SExpr t, SExpr t) -> Either String a) -> SExpr t -> Either String a asPair f (l ::: r ::: SNil) = f (l, r) asPair _ sx = Left ("asPair: expected two-element list; found " ++ getShape sx) -- | Parse an arbitrary-length list using the provided function. -- -- >>> let go xs = concat <$> mapM fromAtom xs -- >>> asList go (A "el" ::: A "eph" ::: A "ant" ::: Nil) -- Right "elephant" -- >>> asList go (A "el" ::: A "eph" ::: A "ant") -- Left "asList: expected list; found dotted list of length 3" asList :: ([SExpr t] -> Either String a) -> SExpr t -> Either String a asList f ls = gatherList ls >>= f -- | Match a given literal atom, failing otherwise. -- -- >>> isAtom "elephant" (A "elephant") -- Right () -- >>> isAtom "elephant" (A "elephant" ::: Nil) -- Left "isAtom: expected atom; found list" isAtom :: Eq t => t -> SExpr t -> Either String () isAtom s (A s') | s == s' = return () | otherwise = Left "isAtom: failed to match atom" isAtom _ sx = Left ("isAtom: expected atom; found " ++ getShape sx) -- | Parse an atom using the provided function. -- -- >>> import Data.Char (toUpper) -- >>> asAtom (return . map toUpper) (A "elephant") -- Right "ELEPHANT" -- >>> asAtom (return . map toUpper) Nil -- Left "asAtom: expected atom; found empty list" asAtom :: (t -> Either String a) -> SExpr t -> Either String a asAtom f (A s) = f s asAtom _ sx = Left ("asAtom: expected atom; found " ++ getShape sx) -- | Parse an assoc-list using the provided function. -- -- >>> let def (x, y) = do { a <- fromAtom x; b <- fromAtom y; return (a ++ ": " ++ b) } -- >>> let defList xs = do { defs <- mapM def xs; return (unlines defs) } -- >>> asAssoc defList ((A "legs" ::: A "four" ::: Nil) ::: (A "trunk" ::: A "one" ::: Nil) ::: Nil) -- Right "legs: four\ntrunk: one\n" -- >>> asAssoc defList ((A "legs" ::: A "four" ::: Nil) ::: (A "elephant") ::: Nil) -- Left "asAssoc: expected pair; found list of length 1" asAssoc :: ([(SExpr t, SExpr t)] -> Either String a) -> SExpr t -> Either String a asAssoc f ss = gatherList ss >>= mapM go >>= f where go (a ::: b ::: Nil) = return (a, b) go sx = Left ("asAssoc: expected pair; found " ++ getShape sx)