packages feed

ersatz-0.2: examples/regexp-grid/RegexpGrid/Regexp.hs

-- | A parser for a subset of the regular expression syntax.

module RegexpGrid.Regexp (Regexp (..), parseRegexp) where

import Control.Applicative hiding ((<|>), many)
import Control.Monad
import Data.Char
import Text.Parsec

data Regexp = Nil
            | AnyCharacter Regexp
            | Character Char Regexp
            | Accept [Char] Regexp
            | Reject [Char] Regexp
            | Choice [Regexp] Regexp
            | Group Regexp Regexp
            | Repeat Integer (Maybe Integer) Regexp Regexp
            | Backreference Integer Regexp
  deriving Show

type REParser = Parsec String REState (Regexp -> Regexp)
type REState  = Integer

parseRegexp :: SourceName -> String -> Either ParseError Regexp
parseRegexp = runParser (regexp <* eof) 0

regexp :: Parsec String REState Regexp
regexp = go <$> (items `sepBy` char '|')
  where
    go []   = Nil
    go [re] = re
    go res  = Choice res Nil

items :: Parsec String REState Regexp
items = go <$> many (nonModifier >>= modifier)
  where
    go = ($ Nil) . foldr (.) id

nonModifier :: REParser
nonModifier  =  AnyCharacter <$ char '.'
            <|> group
            <|> characterClass
            <|> backreference
            <|> Character <$> nonSpecialChar

group :: REParser
group = Group <$> between (char '(') (char ')') go
  where
    go = modifyState (+1) *> regexp

backreference :: REParser
backreference = do
  _ <- char '\\'
  n <- fromIntegral . digitToInt <$> digit
  numGroups <- getState
  when (n == 0 || n > numGroups) $ invalid n
  return (Backreference n)
  where
    invalid n = fail ("Invalid backreference: " ++ show n)

characterClass :: REParser
characterClass = between (char '[') (char ']') go
  where
    go  =  Reject <$> (char '^' *> many1 nonSpecialChar)
       <|> Accept <$> many1 nonSpecialChar

nonSpecialChar :: Parsec String u Char
nonSpecialChar = noneOf "\\.[](){}|^$?*+" <?> "nonspecial"

modifier :: (Regexp -> Regexp) -> REParser
modifier re  =  Repeat 0 (Just 1) re' <$ char '?'
            <|> Repeat 0 Nothing  re' <$ char '*'
            <|> Repeat 1 Nothing  re' <$ char '+'
            <|> pure re
  where
    re' :: Regexp
    re' = re Nil

{-# ANN module "HLint: ignore Use String" #-}