{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}   -- Why ???

-----------------------------------------------------------------------------
-- |
-- Module      :  Parsimony.UserState
-- Copyright   :  (c) Iavor S. Diatchki 2009
-- License     :  BSD3
--
-- Maintainer  :  iavor.diatchki@gmail.com
-- Stability   :  provisional
--
-- Support for parsers with custom state.
--
-----------------------------------------------------------------------------




module Parsimony.UserState
  ( ParserU, UserState(..)
  , lifted
  , getUserState, setUserState, updateUserState
  , uparse, uparseSource
  ) where

import Parsimony.Prim
import Parsimony.Stream
import Parsimony.Pos
import Parsimony.Error
import Parsimony.Combinator

-- NOTE: We could generalize this further by providing
-- a class that abstract over 'extract' and 'inject'

-- | An input stream annotated with some user state.
data UserState user stream  = UserState { userState    :: !user
                                        , parserStream :: !stream
                                        }

-- | The type of parsers with a user state.
type ParserU u s = Parser (UserState u s)


extract :: State (UserState u s) -> (State s, u)
extract s = (s { stateInput = xs }, u)
  where UserState u xs = stateInput s

inject :: State s -> u -> State (UserState u s)
inject s u = s { stateInput = UserState u (stateInput s) }

instance Stream stream token => Stream (UserState user stream) token where
  getToken s =
    case extract s of
      (s1,u) ->
        case getToken s1 of
          Error err -> Error err
          Ok a s2   -> Ok a (inject s2 u)

-- | Turn a parser without user space into ine that supports
-- user state manipulation.
lifted             :: Parser s a -> ParserU u s a
lifted              = mapState extract inject

-- | Get the user state.
getUserState       :: ParserU u s u
getUserState        = userState `fmap` getInput

-- | Set the user state.
setUserState       :: u -> ParserU u s ()
setUserState u      = updateInput (\i -> i { userState = u })

-- | Update the user state.
updateUserState    :: (u -> u) -> ParserU u s ()
updateUserState f   = updateInput (\i -> i { userState = f (userState i) })

uparse             :: ParserU u s a -> u -> s -> Either ParseError a
uparse p u          = uparseSource p u ""

uparseSource       :: ParserU u s a -> u -> SourceName -> s
                   -> Either ParseError a
uparseSource p u n s  = parseSource p n (UserState u s)