gsub :: String  -- ^ Input String
     -> String  -- ^ Regexp Pattern
     -> String  -- ^ Replacement String
     -> String  -- ^ Output String
gsub text pattern replacement = replaceMatches 0 matches text
  where
    matches = (text =~ pattern :: [MatchText String])
    rl = length replacement
    replaceMatches _ [] text = text
    replaceMatches accum (m:ms) text = replaceMatches accum' ms text'
      where
        (o, l) = snd (m ! 0)
        (pre, post) = splitAt (o + accum) text
        accum' = accum + (rl - l + 1)
        post' = drop l post
        text' = concat [ pre
                       , replacePlaceholder (fst $ m ! 0) pattern replacement
                       , post' ]

        replacePlaceholder :: String -> String -> String -> String
        replacePlaceholder expr pattern sub =
            concat $ zipWith f ('_':sub) sub
          where
            matches = (expr =~ pattern :: [MatchText String]) !! 0
            f :: Char -> Char -> String
            f '\\' i
              | isDigit i = fst $ matches ! read [i]
              | otherwise = '\\':[i]
            f _ '\\' = []
            f _ x = [x]
gsub :: String  -- ^ Input String
     -> String  -- ^ Regexp Pattern
     -> String  -- ^ Replacement String
     -> String
gsub text pattern replacement = replaceMatches 0 matches text
  where
    matches = (text =~ pattern :: [MatchText String])
    rl = length replacement
    replaceMatches _ [] text = text
    replaceMatches accum (m:ms) text = replaceMatches accum' ms text'
      where
        (o, l) = snd (m ! 0)
        (pre, post) = splitAt (o + accum) text
        accum' = accum + (rl - l + 1)
        post' = drop l post
        text' = concat [ pre
                       , replacePlaceholder (fst $ m ! 0) pattern replacement
                       , post' ]

        replacePlaceholder :: String -> String -> String -> String
        replacePlaceholder expr pattern sub =
            concat $ zipWith f ('_':sub) sub
          where
            matches = (expr =~ pattern :: [MatchText String]) !! 0
            f :: Char -> Char -> String
            f '\\' i
              | isDigit i = fst $ matches ! read [i]
              | otherwise = '\\':[i]
            f _ '\\' = []
            f _ x = [x]
gsub text pattern replacement = replaceMatches 0 matches text
  where
    matches = (text =~ pattern :: [MatchText String])
    rl = length replacement
    replaceMatches _ [] text = text
    replaceMatches accum (m:ms) text = replaceMatches accum' ms text'
      where
        (o, l) = snd (m ! 0)
        (pre, post) = splitAt (o + accum) text
        accum' = accum + (rl - l + 1)
        post' = drop l post
        text' = concat [ pre
                       , replacePlaceholder (fst $ m ! 0) pattern replacement
                       , post' ]

        replacePlaceholder :: String -> String -> String -> String
        replacePlaceholder expr pattern sub =
            concat $ zipWith f ('_':sub) sub
          where
            matches = (expr =~ pattern :: [MatchText String]) !! 0
            f :: Char -> Char -> String
            f '\\' i
              | isDigit i = fst $ matches ! read [i]
              | otherwise = '\\':[i]
            f _ '\\' = []
            f _ x = [x]