arrowp-qq-0.2: examples/Parser/Parser.lhs
> {-# OPTIONS -F -pgmF arrowp-ext #-}
LL(1) parser combinators: an arrow-ized (and greatly cut-down) version
of those of "Deterministic, Error-Correcting Combinator Parsers", by
Swierstra and Duponcheel. This version uses statically constructed
parse tables, but doesn't do error correction.
> module Parser(
> Symbol(eof), Sym(Sym),
> Parser, symbol, runParser
> ) where
> import Control.Arrow
> import Control.Arrow.Operations
> import Control.Arrow.Transformer
> import Control.Arrow.Transformer.Error
> import Control.Arrow.Transformer.State
> import Control.Arrow.Transformer.Static
> import Control.Category
> import Prelude hiding (id, (.))
We require a symbol for EOF, distinguished from all existing symbols:
> class (Ord s, Show s) => Symbol s where
> eof :: s
Combine a token with other information
> data Sym s v = Sym s v
> token (Sym s _) = s
> value (Sym _ v) = v
> instance (Show s, Show v) => Show (Sym s v) where
> showsPrec p (Sym s v) = showParen True
> (shows s . showString ", " . shows v)
> eofSym :: Symbol s => Sym s v
> eofSym = Sym s (error (show s ++ " has no value"))
> where s = eof
A dynamic parser may fail or transform a list of symbols.
> type DynamicParser s v a = StateArrow [Sym s v] (ErrorArrow String a)
> liftDynamic :: ArrowChoice a => a b c -> DynamicParser s v a b c
> liftDynamic f = lift (lift f)
The auxilliary definitions fetchHead and advance, with their explicit
type signatures, are needed to avoid nasty type errors.
> fetchHead :: ArrowChoice a => DynamicParser s v a b (Sym s v)
> fetchHead = proc _ -> do
> (s:_) <- fetch -< ()
> returnA -< s
> getToken :: ArrowChoice a => DynamicParser s v a b s
> getToken = fetchHead >>> arr token
> advance :: ArrowChoice a => DynamicParser s v a b (Sym s v)
> advance = proc _ -> do
> (s:ss) <- fetch -< ()
> store -< ss
> returnA -< s
The dynamic symbol parser ignores the symbol, as it will already have
been checked by the table lookup.
> unitDP :: ArrowChoice a => DynamicParser s v a b v
> unitDP = advance >>> arr value
Use the static information to construct a dynamic parser.
If the table is empty, we know statically that any lookup will fail.
> mkDynamic :: (Symbol s, ArrowChoice a) =>
> Maybe (a b c) -> Table s (DynamicParser s v a b c) ->
> DynamicParser s v a b c
> mkDynamic Nothing t = arr id &&& getToken >>> lookupTable t err
> where err = proc _ -> raise -< "expected " ++ show (keys t)
> mkDynamic (Just f) t
> | isEmptyTable t = liftDynamic f
> | otherwise = arr id &&& getToken >>> lookupTable t base
> where base = proc (b, _) -> liftDynamic f -< b
If a parser arrow can recognize the empty string, it needs a function
to transform input to output.
> data Parser s v a b c = SP {
> emptyP :: StaticMonadArrow Maybe a b c,
> table :: Table s (DynamicParser s v a b c),
> dynamic :: DynamicParser s v a b c
> -- dynamic = mkDynamic empty table
> }
> mkParser :: (Symbol s, ArrowChoice a) =>
> StaticMonadArrow Maybe a b c -> Table s (DynamicParser s v a b c) ->
> Parser s v a b c
> mkParser e t = SP {
> emptyP = e,
> table = t,
> dynamic = mkDynamic (unwrapM e) t
> }
> symbol :: (Symbol s, ArrowChoice a) => s -> Parser s v a b v
> symbol s = mkParser (wrapM Nothing) (unitTable s unitDP)
> eofParser :: (Symbol s, ArrowChoice a) => Parser s v a b b
> eofParser = proc x -> do
> symbol eof -< ()
> returnA -< x
> instance (Symbol s, ArrowChoice a) => Arrow (Parser s v a) where
> arr f = mkParser (arr f) emptyTable
> first (SP{emptyP = e, table = t}) =
> mkParser (first e) (fmap first t)
> instance (Symbol s, ArrowChoice a) => Category (Parser s v a) where
> id = arr id
> ~SP{emptyP = e2, table = t2, dynamic = d2} . SP{emptyP = e1, table = t1} =
> if isEmptyTable common
> then mkParser (e1 >>> e2) (plusTable t1' t2')
> else error ("parse conflict (concatenation) on " ++
> show (keys common))
> where common = intersectTable t1' t2'
> t1' = fmap (>>> d2) t1
> t2' = seqEmptyTable (unwrapM e1) t2
> seqEmptyTable Nothing _ = emptyTable
> seqEmptyTable (Just f) t = fmap (liftDynamic f >>>) t
> instance (Symbol s, ArrowChoice a) => ArrowZero (Parser s v a) where
> zeroArrow = mkParser (wrapM Nothing) emptyTable
> instance (Symbol s, ArrowChoice a) => ArrowPlus (Parser s v a) where
> SP{emptyP = e1, table = t1} <+> SP{emptyP = e2, table = t2} =
> if isEmptyTable common
> then mkParser (wrapM (plusEmpty (unwrapM e1) (unwrapM e2)))
> (plusTable t1 t2)
> else error ("parse conflict (union) on " ++ show (keys common))
> where common = intersectTable t1 t2
> plusEmpty Nothing e2 = e2
> plusEmpty e1 Nothing = e1
> plusEmpty _ _ = error "Empty-Empty"
> instance (Symbol s, ArrowChoice a, ArrowLoop a) =>
> ArrowLoop (Parser s v a) where
> loop (SP{emptyP = e, table = t}) =
> mkParser (wrapM (fmap loop (unwrapM e))) (fmap loop t)
Run a parser on a complete input
> runParser :: (Symbol s, ArrowChoice a) =>
> Parser s v a () b -> ErrorArrow String a [Sym s v] b
> runParser p = proc ss -> do
> (v, _) <- rp -< ((), ss ++ [eofSym])
> returnA -< v
> where rp = runState (dynamic (p >>> eofParser))
general combinators
> option :: ArrowPlus a => (b -> c) -> a b c -> a b c
> option f p = arr f <+> p
> many :: ArrowPlus a => a b c -> a b [c]
> many p = option (const []) (some p)
> some :: ArrowPlus a => a b c -> a b [c]
> some p = some_p
> where some_p = proc b -> do
> c <- p -< b
> cs <- many_p -< b
> returnA -< c:cs
> many_p = option (const []) (some_p)
A different design:
> optional :: ArrowPlus a => a b b -> a b b
> optional p = arr id <+> p
> star :: ArrowPlus a => a b b -> a b b
> star p = p' where p' = optional (p >>> p')
> plus :: ArrowPlus a => a b b -> a b b
> plus p = p' where p' = p >>> optional p'
Tables.
During parser construction, these are represented as lists of pairs,
ordered by the key. Then these are transformed into balanced search
trees for use in parsing.
> newtype Table k v = Table [(k, v)]
> emptyTable :: Table k v
> emptyTable = Table []
> unitTable :: k -> v -> Table k v
> unitTable k v = Table [(k, v)]
> instance Functor (Table k) where
> fmap f (Table kvs) = Table [(k, f v) | (k, v) <- kvs]
Combine two tables. In case of conflicts, the first takes precedence.
> plusTable :: Ord k => Table k v -> Table k v -> Table k v
> plusTable (Table t1) (Table t2) = Table (merge t1 t2)
> where merge [] kvs2 = kvs2
> merge kvs1 [] = kvs1
> merge (kvs1@(p1@(k1, _):kvs1')) (kvs2@(p2@(k2, _):kvs2')) =
> case compare k1 k2 of
> LT -> p1:merge kvs1' kvs2
> EQ -> p1:merge kvs1' kvs2'
> GT -> p2:merge kvs1 kvs2'
> intersectTable :: Ord k => Table k v1 -> Table k v2 -> Table k (v1, v2)
> intersectTable (Table t1) (Table t2) = Table (merge t1 t2)
> where merge [] _ = []
> merge _ [] = []
> merge (kvs1@((k1, v1):kvs1')) (kvs2@((k2, v2):kvs2')) =
> case compare k1 k2 of
> LT -> merge kvs1' kvs2
> EQ -> (k1, (v1, v2)):merge kvs1' kvs2'
> GT -> merge kvs1 kvs2'
> isEmptyTable :: Table k v -> Bool
> isEmptyTable (Table t) = null t
> keys :: Table k v -> [k]
> keys (Table kvs) = map fst kvs
> data SearchTree k v = Empty | Node (SearchTree k v) k v (SearchTree k v)
Make a balanced search tree from a table
> searchTree :: Ord k => Table k v -> SearchTree k v
> searchTree (Table kvs) = fst (mkTree (length kvs) kvs)
> where mkTree :: Int -> [(k,v)] -> (SearchTree k v, [(k,v)])
> mkTree n kvs = if n == 0 then (Empty, kvs)
> else let size_l = (n-1) `div` 2
> (l, (k, v):kvs') = mkTree size_l kvs
> (r, kvs'') = mkTree (n-1-size_l) kvs'
> in (Node l k v r, kvs'')
Construct an arrow that searches for dynamic inputs in the statically
constructed tree of arrows.
> lookupTable :: (ArrowChoice a, Ord k) =>
> Table k (a b c) -> a (b, k) c -> a (b, k) c
> lookupTable t def = look (searchTree t)
> where look Empty = def
> look (Node l k a r) = proc (v, x) -> case compare x k of
> LT -> look l -< (v, x)
> EQ -> a -< v
> GT -> look r -< (v, x)