packages feed

tokenizer-monad-0.1.0.0: src/Control/Monad/Tokenizer.hs

{-# LANGUAGE OverloadedStrings, BangPatterns #-}

module Control.Monad.Tokenizer where

import Control.Monad
import Data.Char
import Data.Monoid
import qualified Data.Text as T
    
-- | Tokenizer monad. Use runTokenizer to run it
newtype Tokenizer a = Tokenizer { runTokenizer' :: (T.Text, Int, T.Text) -> (a,[T.Text] -> [T.Text],T.Text,Int,T.Text) }

instance Functor Tokenizer where
    fmap = liftM

instance Applicative Tokenizer where
    pure = return
    (<*>) = ap

instance Monad Tokenizer where
    return a = Tokenizer $ \(whole,count,tail) -> (a,id,whole,count,tail)
    m >>= f = Tokenizer $ \(whole,count,tail) ->
              let (a1,o1,w1,!c1,t1) = runTokenizer' m (whole,count,tail)
                  (a2,o2,w2,!c2,t2) = runTokenizer' (f a1) (w1,c1,t1)
              in (a2,o1.o2,w2,c2,t2)

-- | Check if the next input chars agree with the given string
lookAhead :: [Char] -> Tokenizer Bool
lookAhead chars =
    Tokenizer $ \(whole,count,tail) ->
        let h = T.unpack $ T.take (length chars) tail
        in (h == chars, id, whole, count, tail)
                 
-- | Proceed to the next character
walk :: Tokenizer ()
walk = Tokenizer $ \(whole,count,tail) ->
       if T.null tail
         then ((),id,whole,count,tail)
         else ((),id,whole,count+1,T.tail tail)

-- | Peek the current character
peek :: Tokenizer Char
peek = Tokenizer $ \(whole,count,tail) -> (th tail,id,whole,count,tail)
    where th t | T.null t = '\0'
               | otherwise = T.head t

-- | Peek the current character and proceed
pop :: Tokenizer Char
pop = peek <* walk
                             
-- | Break at the current position and emit the scanned token
emit :: Tokenizer ()
emit = Tokenizer $ \(whole,count,tail) -> ((),(T.take count whole:),tail,0,tail)

-- | Break at the current position and discard the scanned token
discard :: Tokenizer ()
discard = Tokenizer $ \(whole,count,tail) -> ((),id,tail,0,tail)

-- | Proceed as long as a given function succeeds
walkWhile :: (Char -> Bool) -> Tokenizer ()
walkWhile f = do
  c <- peek
  when (c /= '\0' && f c) $
       walk >> walkWhile f

-- | Proceed as long as a given fold returns Just (generalization of walkWhile)
walkFold :: a -> (Char -> a -> Maybe a) -> Tokenizer ()
walkFold s0 f = do
  c <- peek
  unless (c == '\0') $ case f c s0 of
      Nothing -> return ()
      Just s -> walk >> walkFold s f

-- | Repeat a given tokenizer as long as the end of text is not reached
untilEOT :: Tokenizer () -> Tokenizer ()
untilEOT f = do
  eot <- Tokenizer $ \(whole,count,tail) -> (T.null tail,id, whole, count, tail)
  unless eot $ f >> untilEOT f

-- | Split a string into tokens using the given tokenizer
runTokenizer :: Tokenizer () -> T.Text -> [T.Text]
runTokenizer m input =
    let input' = T.toLower input
    in case runTokenizer' m (input',0,input') of
        (_, tokens, _, _, _) -> tokens []

-- | Split a string into tokens using the given tokenizer, case sensitive version
runTokenizerCS :: Tokenizer () -> T.Text -> [T.Text]
runTokenizerCS m input =
    case runTokenizer' m (input,0,input) of
        (_, tokens, _, _, _) -> tokens []