Revolucent icon

A tiny toy parser combinator library

Revolucent | PRO | 07/31/24 12:51:48 AM UTC | 0 ⭐ | 16193 👁️ | Never ⏰ | []
Haskell |

3.72 KB

|

None

|

0 👍

/

0 👎

{-# LANGUAGE FlexibleInstances, FunctionalDependencies, TupleSections #-}
 
module Parsy where
 
import Control.Applicative
import Control.Monad.Error.Class
import RIO
import RIO.List
 
class Collection e c | c -> e where
  elemAtIndex :: c -> Word -> Maybe e
 
instance Collection a [a] where
  elemAtIndex c i
    | genericLength c <= i = Nothing
    | otherwise = Just $ c `genericIndex` i
 
data ParseError = NoMatch | EOD deriving (Eq, Show)
 
newtype Parser c a = Parser { unParser :: c -> Word -> (Either ParseError a, Word) }
 
runParser :: c -> Parser c a -> Either (ParseError, Word) a
runParser collection parser = let (result, index) = unParser parser collection 0 in
  case result of
    Left e -> Left (e, index)
    Right a -> Right a
 
instance Functor (Parser c) where
  fmap f (Parser parse) = Parser $ \c i -> let 
      (e, i') = parse c i
    in (fmap f e, i')
 
instance Applicative (Parser c) where
  pure a = Parser $ const (Right a,) 
  (Parser f) <*> (Parser a) = Parser $ \c i -> let
      (f', fi) = f c i
      (a', ai) = a c fi
    in (f' <*> a', ai)
 
instance Monad (Parser c) where
  return = pure
  (Parser a) >>= f = Parser $ \c i -> let
      (a', ai) = a c i
    in case a' of
      Left e -> (Left e, i)
      Right a -> unParser (f a) c ai
 
instance MonadError ParseError (Parser c) where
  throwError e = Parser $ const (Left e,) 
  catchError (Parser a) handler = Parser $ \c i -> let
      (a', ai) = a c i
    in case a' of
      Left e -> unParser (handler e) c i
      Right a -> (Right a, ai)
 
instance Alternative (Parser c) where
  empty = Parser undefined
  a <|> b = catchError a $ const b
 
advanceBy :: Int -> Parser c ()
advanceBy delta = Parser $ const $ \i -> (Right (), addDelta i)
  where
    addDelta i = fromIntegral $ delta + fromIntegral i
 
advance :: Parser c ()
advance = advanceBy 1
 
collection :: Parser c c
collection = Parser $ \c i -> (Right c, i)
 
index :: Parser c Word
index = Parser $ const $ \i -> (Right i, i)
 
putIndex :: Word -> Parser c () 
putIndex = Parser . const . const . (Right (),) 
 
collectionWithIndex :: Parser c (c, Word)
collectionWithIndex = Parser $ \c i -> (Right (c, i), i)
 
current :: Collection a c => Parser c (Maybe a) 
current = collectionWithIndex <&> uncurry elemAtIndex 
 
eod :: Collection a c => Parser c ()
eod = current >>= maybe (return ()) (const $ throwError NoMatch) 
 
atEOD :: Collection a c => Parser c Bool
atEOD = isNothing <$> current 
 
satisfy :: Collection a c => (a -> Bool) -> Parser c a
satisfy predicate = do
  a <- current
  case a of
    Just a -> if predicate a
      then advance >> return a
      else throwError NoMatch
    Nothing -> throwError EOD
 
equals :: (Eq a, Collection a c) => a -> Parser c a
equals a = satisfy (== a)
 
many1 :: Parser c a -> Parser c [a]
many1 parser = liftA2 (:) parser $ many parser
 
many1SepBy :: Parser c a -> Parser c sep -> Parser c [a]
many1SepBy parser sep = liftA2 (:) parser $ many (sep *> parser)
 
manySepBy :: Parser c a -> Parser c sep -> Parser c [a]
manySepBy parser sep = many1SepBy parser sep <|> pure []
 
peek :: Parser c a -> Parser c ()
peek parser = index >>= \i -> void parser >> putIndex i 
 
skip :: Parser c a -> Parser c ()
skip = void
 
skipMany :: Parser c a -> Parser c ()
skipMany = void . many
 
skipMany1 :: Parser c a -> Parser c ()
skipMany1 = void . many1
 
skipUntil :: Collection e c => Parser c a -> Parser c ()
skipUntil parser = do
  err <- catchError (peek parser $> Nothing) (return . Just)
  case err of
    Nothing -> return ()
    Just EOD -> throwError EOD
    Just _ -> advance >> skipUntil parser
 
skipTo :: Collection e c => Parser c a -> Parser c a
skipTo parser = skipUntil parser >> parser
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎