packages feed

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)