packages feed

parcom-lib-0.2.0.0: Text/Parcom/Core.hs

{-#LANGUAGE MultiParamTypeClasses #-}
module Text.Parcom.Core
( ParcomError (..)
, SourcePosition (..)
, ParcomT, parseT
, Parcom, parse
, peek, next, atEnd
, try, handle
, notFollowedBy
, (<?>), (<|>), empty
, Stream, Token
)
where

import qualified Text.Parcom.Stream as Stream
import Text.Parcom.Stream (Stream, Token)
import Control.Monad (liftM, ap)
import Control.Applicative
import Control.Monad.Identity
import Control.Monad.Trans.Class

data SourcePosition =
    SourcePosition
        { posFileName :: String
        , posLine :: Int
        , posColumn :: Int
        }
        deriving (Show)

data ParcomError =
    ParcomError
        { peErrorDescription :: String
        , peSourcePosition :: SourcePosition
        }
        deriving (Show)

data ParcomState s =
    ParcomState
        { psSourcePosition :: SourcePosition
        , psStream :: s
        }

type Parcom s t a = ParcomT s t Identity a
newtype ParcomT s t m a = ParcomT { runParcomT :: ParcomState s -> m (Either ParcomError a, ParcomState s) }

instance (Monad m) => Monad (ParcomT s t m) where
    return x = ParcomT (\s -> return (Right x, s))
    fail err = ParcomT (\s -> return (Left $ ParcomError err (psSourcePosition s), s))
    m >>= f = ParcomT $ \s -> do -- in the m Monad
        (a, s') <- runParcomT m s
        case a of
            Left err -> return (Left err, s')
            Right ma -> do
                runParcomT (f ma) s'

instance MonadTrans (ParcomT s t) where
    lift a = ParcomT $ \s -> do
        v <- a
        return (Right v, s)

instance (Monad m) => Functor (ParcomT s t m) where
    fmap f xs = xs >>= return . f

instance (Monad m) => Applicative (ParcomT s t m) where
    pure = return
    (<*>) = ap

instance (Monad m) => Alternative (ParcomT s t m) where
    (<|>) = alt
    empty = fail "empty"

runParserT :: (Stream s t, Token t) => ParcomT s t m a -> String -> s -> m (Either ParcomError a, ParcomState s)
runParserT p fn str =
    runParcomT p state
    where
        state =
            ParcomState
                { psSourcePosition = SourcePosition fn 1 1
                , psStream = str }

parseT :: (Stream s t, Token t, Monad m) => ParcomT s t m a -> String -> s -> m (Either ParcomError a)
parseT p fn str = fst `liftM` runParserT p fn str

parse :: (Stream s t, Token t) => Parcom s t a -> String -> s -> Either ParcomError a
parse p fn str = runIdentity $ parseT p fn str

getState :: Monad m => ParcomT s t m (ParcomState s)
getState = ParcomT $ \s -> return (Right s, s)

useState :: Monad m => (ParcomState s -> a) -> ParcomT s t m a
useState f = ParcomT $ \s -> return (Right $ f s, s)

setState :: Monad m => ParcomState s -> ParcomT s t m ()
setState s = ParcomT $ \_ -> return (Right (), s)

modifyState :: Monad m => (ParcomState s -> ParcomState s) -> ParcomT s t m ()
modifyState f = ParcomT $ \s -> return (Right (), f s)

-- | Very general error / success handler
-- Each of the error / success branches takes both the old and the new state,
-- so that the branch handler itself can decide whether to backtrack or
-- continue with the new state.
handle' :: Monad m
    => ParcomT s t m a
    -> (ParcomError -> ParcomState s -> ParcomState s -> m (Either ParcomError b, ParcomState s))
    -> (a -> ParcomState s -> ParcomState s -> m (Either ParcomError b, ParcomState s))
    -> ParcomT s t m b
handle' p errorH successH = ParcomT $ \s -> do
    (r, s') <- runParcomT p s
    case r of
        -- parse failed: run error handler
        Left e -> errorH e s s'
        -- parse succeeded: run success handler
        Right x -> successH x s s'

handle :: Monad m
    => ParcomT s t m a
    -> (ParcomError -> ParcomT s t m b)
    -> (a -> ParcomT s t m b)
    -> ParcomT s t m b
handle p f t =
    handle' p (\e _ s' -> runParcomT (f e) s') (\x _ s' -> runParcomT (t x) s')

handleB :: Monad m
    => ParcomT s t m a
    -> (ParcomError -> ParcomT s t m b)
    -> (a -> ParcomT s t m b)
    -> ParcomT s t m b
handleB p f t =
    handle' p (\e s _ -> runParcomT (f e) s) (\x s _ -> runParcomT (t x) s)

-- | Backtracking modifier; restores the parser state to the previous situation
-- if the wrapped parser fails.
try :: Monad m => ParcomT s t m a -> ParcomT s t m a
try p = handle' p (\e s _ -> return (Left e, s)) (\x _ s' -> return (Right x, s'))

-- | Return the result of the first parser that succeeds.
alt :: Monad m => ParcomT s t m a -> ParcomT s t m a -> ParcomT s t m a
alt a b = handle' a (\e s _ -> runParcomT b s) (\x _ s' -> return (Right x, s'))

-- | Succeeds iff the given parser fails
notFollowedBy :: (Monad m, Stream s t) => ParcomT s t m a -> ParcomT s t m ()
notFollowedBy p = handle' p (\_ s _ -> return (Right (), s)) (\x s _ -> runParcomT (fail "something followed that shouldn't") s)
    
-- | Gets the next token from the stream without consuming it.
-- Fails at end-of-input.
peek :: (Monad m, Stream s t) => ParcomT s t m t
peek = do
    str <- psStream `liftM` getState
    if Stream.atEnd str
        then fail "Unexpected end of input"
        else return (Stream.peek str)

-- | Checks whether end-of-input has been reached.
atEnd :: (Monad m, Stream s t) => ParcomT s t m Bool
atEnd = useState (Stream.atEnd . psStream)

nextLine :: SourcePosition -> SourcePosition
nextLine s =
    s { posLine = posLine s + 1, posColumn = 1 }

nextColumn :: SourcePosition -> SourcePosition
nextColumn s =
    s { posColumn = posColumn s + 1 }

-- | Gets the next token from the stream and consumes it.
-- Fails at end-of-input.
next :: (Monad m, Stream s t, Token t) => ParcomT s t m t
next = do
    str <- psStream `liftM` getState
    if Stream.atEnd str
        then fail "Unexpected end of input"
        else do
            let (t, str') = Stream.pop str
            modifyState $ \state -> state { psStream = str' }
            if Stream.isLineDelimiter t
                then modifyState $ \s -> s { psSourcePosition = nextLine (psSourcePosition s) }
                else modifyState $ \s -> s { psSourcePosition = nextColumn (psSourcePosition s) }
            return t

(<?>) :: (Monad m, Stream s t) => ParcomT s t m a -> String -> ParcomT s t m a
p <?> expected = p <|> fail ("Expected " ++ expected)