{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} module Data.SCargot.Repr.WellFormed ( -- * 'WellFormedSExpr' representation R.WellFormedSExpr(..) , R.toWellFormed , R.fromWellFormed -- * Constructing and Deconstructing , cons , uncons -- * Useful pattern synonyms , pattern (:::) , pattern L , pattern A , pattern Nil -- * Useful processing functions , fromPair , fromList , fromAtom , asPair , asList , isAtom , isNil , asAtom , asAssoc , car , cdr ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>), (<*>), pure) #endif import Data.SCargot.Repr as R -- | Produce the head and tail of the s-expression (if possible). -- -- >>> uncons (L [A "el", A "eph", A "ant"]) -- Just (WFSAtom "el",WFSList [WFSAtom "eph",WFSAtom "ant"]) uncons :: WellFormedSExpr a -> Maybe (WellFormedSExpr a, WellFormedSExpr a) uncons R.WFSAtom {} = Nothing uncons (R.WFSList []) = Nothing uncons (R.WFSList (x:xs)) = Just (x, R.WFSList xs) -- | Combine the two-expressions into a new one. This will return -- @Nothing@ if the resulting s-expression is not well-formed. -- -- >>> cons (A "el") (L [A "eph", A "ant"]) -- Just (WFSList [WFSAtom "el",WFSAtom "eph",WFSAtom "ant"]) -- >>> cons (A "pachy") (A "derm")) -- Nothing cons :: WellFormedSExpr a -> WellFormedSExpr a -> Maybe (WellFormedSExpr a) cons _ (R.WFSAtom {}) = Nothing cons x (R.WFSList xs) = Just (R.WFSList (x:xs)) -- | A shorter infix alias to grab the head and tail of a `WFSList`. This -- pattern is unidirectional, because it cannot be guaranteed that it -- is used to construct well-formed s-expressions; use the function "cons" -- instead. -- -- >>> let sum (x ::: xs) = x + sum xs; sum Nil = 0 #if MIN_VERSION_base(4,8,0) pattern (:::) :: WellFormedSExpr a -> WellFormedSExpr a -> WellFormedSExpr a #endif pattern x ::: xs <- (uncons -> Just (x, xs)) -- | A shorter alias for `WFSList` -- -- >>> L [A "pachy", A "derm"] -- WFSList [WFSAtom "pachy",WFSAtom "derm"] #if MIN_VERSION_base(4,8,0) pattern L :: [WellFormedSExpr t] -> WellFormedSExpr t #endif pattern L xs = R.WFSList xs -- | A shorter alias for `WFSAtom` -- -- >>> A "elephant" -- WFSAtom "elephant" #if MIN_VERSION_base(4,8,0) pattern A :: t -> WellFormedSExpr t #endif pattern A a = R.WFSAtom a -- | A shorter alias for `WFSList` @[]@ -- -- >>> Nil -- WFSList [] #if MIN_VERSION_base(4,8,0) pattern Nil :: WellFormedSExpr t #endif pattern Nil = R.WFSList [] getShape :: WellFormedSExpr a -> String getShape WFSAtom {} = "atom" getShape (WFSList []) = "empty list" getShape (WFSList sx) = "list of length " ++ show (length sx) -- | Utility function for parsing a pair of things. -- -- >>> fromPair (isAtom "pachy") (asAtom return) (L [A "pachy", A "derm"]) -- Right ((), "derm") -- >>> fromPair (isAtom "pachy") fromAtom (L [A "pachy"]) -- Left "Expected two-element list" fromPair :: (WellFormedSExpr t -> Either String a) -> (WellFormedSExpr t -> Either String b) -> WellFormedSExpr t -> Either String (a, b) fromPair pl pr (L [l, r]) = (,) <$> pl l <*> pr r fromPair _ _ sx = Left ("fromPair: expected two-element list; found " ++ getShape sx) -- | Utility function for parsing a list of things. -- -- >>> fromList fromAtom (L [A "this", A "that", A "the-other"]) -- Right ["this","that","the-other"] -- >>> fromList fromAtom (A "pachyderm") -- Left "asList: expected proper list; found dotted list" fromList :: (WellFormedSExpr t -> Either String a) -> WellFormedSExpr t -> Either String [a] fromList p (L ss) = mapM p ss fromList _ sx = Left ("fromList: expected list; found " ++ getShape sx) -- | Utility function for parsing a single atom -- -- >>> fromAtom (A "elephant") -- Right "elephant" -- >>> fromAtom (L [A "elephant"]) -- Left "fromAtom: expected atom; found list" fromAtom :: WellFormedSExpr t -> Either String t fromAtom (A a) = return a fromAtom sx = Left ("fromAtom: expected atom; found " ++ getShape sx) -- | Parses a two-element list using the provided function. -- -- >>> let go (A l) (A r) = return (l ++ r); go _ _ = Left "expected atoms" -- >>> asPair go (L [A "pachy", A "derm"]) -- Right "pachyderm" -- >>> asPair go (L [A "elephant"]) -- Left "asPair: expected two-element list; found list of length 1" asPair :: ((WellFormedSExpr t, WellFormedSExpr t) -> Either String a) -> WellFormedSExpr t -> Either String a asPair f (L [l, r]) = 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 (L [A "el", A "eph", A "ant"]) -- Right "elephant" -- >>> asList go (A "pachyderm") -- Left "asList: expected list; found atom" asList :: ([WellFormedSExpr t] -> Either String a) -> WellFormedSExpr t -> Either String a asList f (L ls) = f ls asList _ sx = Left ("asList: expected list; found " ++ getShape sx) -- | Match a given literal atom, failing otherwise. -- -- >>> isAtom "elephant" (A "elephant") -- Right () -- >>> isAtom "elephant" (L [A "elephant"]) -- Left "isAtom: expected atom; found list" isAtom :: Eq t => t -> WellFormedSExpr 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) -- | Match an empty list, failing otherwise. -- -- >>> isNil (L []) -- Right () -- >>> isNil (A "elephant") -- Left "isNil: expected nil; found atom" isNil :: WellFormedSExpr t -> Either String () isNil Nil = return () isNil sx = Left ("isNil: expected nil; 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) (L []) -- Left "asAtom: expected atom; found list" asAtom :: (t -> Either String a) -> WellFormedSExpr 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 (L [ L [A "legs", A "four"], L [ A "trunk", A "one"] ]) -- Right "legs: four\ntrunk: one\n" -- >>> asAssoc defList (L [ L [A "legs", A "four"], L [ A "elephant"] ]) -- Left "asAssoc: expected pair; found list of length 1" asAssoc :: ([(WellFormedSExpr t, WellFormedSExpr t)] -> Either String a) -> WellFormedSExpr t -> Either String a asAssoc f (L ss) = gatherPairs ss >>= f where gatherPairs (L [a, b] : ts) = (:) <$> pure (a, b) <*> gatherPairs ts gatherPairs [] = pure [] gatherPairs (sx:_) = Left ("asAssoc: expected pair; found " ++ getShape sx) asAssoc _ sx = Left ("asAssoc: expected list; found " ++ getShape sx) -- | Run the parser on the first element of a Haskell list of "WellFormedSExpr" values, -- failing if the list is empty. This is useful in conjunction with the `asList` -- function. car :: (WellFormedSExpr t -> Either String t') -> [WellFormedSExpr t] -> Either String t' car f (x:_) = f x car _ [] = Left "car: Taking car of zero-element list" -- | Run the parser on all but the first element of a Haskell list of "WellFormedSExpr" values, -- failing if the list is empty. This is useful in conjunction with the `asList` -- function. cdr :: ([WellFormedSExpr t] -> Either String t') -> [WellFormedSExpr t] -> Either String t' cdr f (_:xs) = f xs cdr _ [] = Left "cdr: Taking cdr of zero-element list"