I want to express the parse and pretty-print of a data structure with a single, declarative expression. I also want to make the syntax general enough to adapt to any serialization/deserialization format, binary or textual, and for any string format.
The first is done. I used the idea of Formlets and applied it to parse-print. This is the resulting expression for an example datatype:
data P = I {getInt :: Int} | S {getString :: String} deriving (Show)
This is the instance of Parselet for P to parse/print from/to Strings (see the class definition below) :
instance ParseLet P String where
parse mpx = I <$> (str "I" *> pString (sel getInt mpx )) <|> S <$> (str "S" *> pString (sel getString mpx ))
The single expression produces the text serialization and deserialization:
main = do putStrLn . serial $ S "hi" print (deserial "I 2" :: Maybe P )
This is the output:
e>runghc demos\parselets.hs
S {getString="hi"}
Just (I 2)
To do this,I coded some applicative instance that wraps both a non monadic parser and a non-monadic serializer. I also found a way to express conditional serialization as an Alternative expression within an Applicative expression (sel), so that it mimic the shape of an applicative parser expression
Because there is a single expression for serialization and deserializartion, it can be guaranteed that the first will produce a result that will be read without errors by the second:
This is the complete source of parselets.hs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | {-# LANGUAGE ScopedTypeVariables ,TypeSynonymInstances ,FlexibleInstances ,MultiParamTypeClasses #-} import Control.Applicative import Data.Monoid import System.IO.Unsafe import Control.Exception as CE import Data.List(isPrefixOf) import Data.Maybe import Debug.Trace (!>)= flip trace data RS v a= RS v (Maybe a) newtype RSView v a= RSView{runRSView :: (v -> (RS v a,v))} instance Functor (RSView v) where fmap f (RSView p)=RSView $ \v -> let (RS v1 x, r)= p v in (RS v1 (fmap f x),r) instance Monoid v => Applicative( RSView v) where pure a = RSView ( \v -> (RS mempty $ Just a,v)) RSView f <*> RSView g= RSView ( \v -> let (RS v1 k,r) = f v (RS v2 x,r2) = g r in (RS (mappend v1 v2) (k <*> x),r2)) instance Monoid v => Alternative (RSView v) where empty= RSView $ \v -> (RS mempty Nothing, v) RSView f <|> RSView g= RSView ( \v -> let rs@(RS v1 k,r) = f v in case k of Just _ -> rs Nothing -> g v ) class Monoid v => ParseLet a v where parse :: Maybe a -> RSView v a -- must not use pattern match serial :: ParseLet a v => a -> v serial x = getSerial $ (runRSView $ parse (Just x)) mempty where getSerial (RS v _,_)= v deserial :: ParseLet a v => v -> Maybe a deserial str= getDeserial ( (runRSView ( parse Nothing)) str) where getDeserial (RS _ x,_)= x sel f mpx= unsafePerformIO $ CE.handle (\(e:: SomeException) -> return Nothing) $ let x= f $ fromJust mpx in x `seq` return (Just x) pString :: (Read a, Show a)=> Maybe a -> RSView String a pString (Just fpx)= RSView $ \str -> (RS (show$ fpx) (Just fpx),str) pString Nothing = RSView $ \str -> case readsPrec 1 str of [] -> (RS " " Nothing, str) (x,r):_ -> (RS " " (Just x), r) --str :: String -> RSView String () str s= RSView ( \st -> let readit= if isPrefixOf s st then Just() else Nothing in (RS (s++" ") readit , drop (length s) st)) data P = I Int | S String deriving (Read, Show) instance ParseLet P String where parse mpx = I <$> (str "I" *> pString (sel (\(I x) -> x) mpx )) <|> S <$> (str "S" *> pString (sel (\(S s) -> s) mpx )) main = do putStrLn . serial $ S "hi" print (deserial "I 2" :: Maybe P ) |
No comments:
Post a Comment