A brief play with monadic parsers in Haskell

This was some of the Hello World code when I was first introduced to Haskell and Monad, kinda interesting. It had a lot of impact on me later implementing parsers in other languages.

-- define a new data type to carry the parser funtion
-- this is necessary when you want to use do notation
type ParserFunction a = String -> ([a], String, [String])
data Parser a = Parser( ParserFunction a )

-- apply function extracts the parser function
apply :: Parser a -> ParserFunction a
apply (Parser p) = p

-- parse function produce human readable output
parse :: Show a => String -> Parser a -> IO ()
parse input parser = case apply parser input of
                     ([] ,y,e) -> do putStr "Error(s):\n"
                                     putStrList (map indent e)
                                     if y != "" then
                                        do putStr "When parsing:\n\t"
                                           putStr (show y)
                                     else
                                        return ()
                     (x:_,y,e) -> do putStr "Result:\n\t"
                                     putStr (show x)
                                     if y != "" then
                                        do putStr "\nRemaining:\n\t"
                                           putStr (show y)
                                     else
                                        return ()
                                     if e != [] then
                                        do putStr "\nWarning(s):\n"
                                           putStrList (map indent e)
                                     else
                                        return ()

putStrList []     = return ()
putStrList (x:xs) = do putStr (x++"\n")
                       putStrList xs

indent str = '\t':str

-- define Parser as instance of class Monad
-- & override the bind operator and two unit monads
instance Monad Parser where
  p >>= q =
   Parser
   (
    \inp -> case apply p inp of
             ([] ,y,e) -> ([],y,e)
             (x:_,y,w) -> (x',y',w')
                            where
                              (x',y',nw) = apply (q x) y
                              w' = w ++ nw
   )
  return v = Parser( \inp -> (v:[],inp,[]) )
  fail   s = Parser( \inp -> ([]  ,inp,s:[] ) )

-- define the "atom" monads
-- allowing composition of new monads later with do notation

warn msg = Parser( \inp -> ([[]],inp,msg:[]) )

pop = Parser
      (
       \inp -> case inp of
                ""     -> apply (fail "'pop' failed.") inp
                (x:xs) -> apply (return x) xs
      )

inspect n = Parser
            (
             \inp -> if length inp > n then
                        apply (return (inp!!n)) inp
                     else
                        apply (return '\0') inp
            )

peek = inspect 0

-- pops a Char if it satisfies the predicate
popif sat msg = do x <- peek
                   if sat x then
                     do pop
                        return x
                   else
                     fail ("Unexpected '"++x:"', expects "++msg++".")

char c = popif (c==) ("'"++c:"'")

digit = popif isDigit "'0'~'9'"

-- try apply p, if fail, apply q
p +++ q = Parser
          (
           \inp -> case apply p inp of
                   ([],_,_) -> apply q inp
                   (x ,y,z) -> (x,y,z)
          )

-- apply p 0+ times
star p = plus p +++ return ([],0)

-- apply p 1+ times
plus p = do x     <- p
            (y,t) <- star p
            return (x:y,t+1)

int = do n <- digit
         m <- peek
         if isDigit m then
           if m != '0' then
             do r <- int
                return ( (digitToInt n) * 10 ^ (numDigits r) + r )
           else
             do z <- plus (char '0')
                r <- int
                return ( (digitToInt n) * 10 ^ (numDigits r + snd z) + r )
         else
           return (digitToInt n)

numDigits i | i < 10 = 1
            | i > 9  = 1 + numDigits (i / 10)

-- a set of parsers to parse expression
expr = do { t <- term
          ; do char '+'
               e <- expr
               return (t+e)
            +++
            do char '-'
               e <- expr
               return (t-e)
            +++
            return t
          }

term = do { f <- factor
          ; do char '*'
               t <- term
               return (f*t)
            +++
            do char '/'
               t <- term
               return (f/t)
            +++
            return f
          }

factor = do i <- int
            return i
         +++
         do char '('
            e <- expr
            char ')'
            return e

-- functions not implemented in Hugs98
p != q = not (p==q)

isDigit '1' = True
isDigit '2' = True
isDigit '3' = True
isDigit '4' = True
isDigit '5' = True
isDigit '6' = True
isDigit '7' = True
isDigit '8' = True
isDigit '9' = True
isDigit '0' = True
isDigit _  = False

digitToInt '0' = 0
digitToInt '1' = 1
digitToInt '2' = 2
digitToInt '3' = 3
digitToInt '4' = 4
digitToInt '5' = 5
digitToInt '6' = 6
digitToInt '7' = 7
digitToInt '8' = 8
digitToInt '9' = 9

A brief play with monadic parsers in Haskell
https://blog.bigpower.dev/A-brief-play-with-monadic-parsers-in-Haskell/
Author
Paul Chen
Posted on
October 5, 2010
Licensed under