正则表达式替换为回调

时间:2015-10-19 14:42:03

标签: regex haskell

我想执行字符串搜索并使用正则表达式替换,但无法找到满足我需要的功能。我正在寻找类似于我的神话regexReplace的东西,我的潜在签名如下:

regexReplace :: Regex -> (String -> String) -> String -> String

regexReplace :: Regex -> (RegexMatch -> String) -> String -> String

这大致相当于PHP中的preg_replace_callback。它采用由Regex表示的编译正则表达式,替换函数(其参数可以是String或某些匹配类型RegexMatch),源字符串并返回结果。

在那里的许多Haskell正则表达式库中,与此行为最接近的匹配是什么?

更新

我可能需要在IO内运行回调,因此我可能最终需要以下函数签名:

regexReplace :: Monad m => Regex -> (RegexMatch -> m String) -> String -> m String

这显然是一个比原始问题陈述更普遍的问题。我最终可能会使用Parsec或类似的东西。

基本上,我有以下程序,但无法弄清楚如何填空:

{-# LANGUAGE RecordWildCards #-}

module Main where

import Data.Maybe
import System.Environment

data Regex = Regex {
  regexPattern :: String
}
data RegexMatch = RegexMatch {
  regexGroups :: [String]
}

regexReplace :: Monad m => Regex -> (RegexMatch -> m String) -> String -> m String
regexReplace Regex{..} f source =
  -- What do I put here?
  undefined

replacementValue :: [String] -> IO String
replacementValue groups =
  case groups of
    whole : name : defaultValue : [] -> do
      lookupResult <- lookupEnv name
      let value = fromMaybe defaultValue lookupResult
      return value
    otherwise -> error "Unexpected groups"

main :: IO ()
main = do
  replaceResult <- regexReplace
    (Regex "ENV:([A-Za-z_][A-Za-z_0-9]*):([A-Za-z_][A-Za-z_0-9]*)")
    (\RegexMatch{..} -> replacementValue regexGroups)
    "some input containing ENV:foo:bar replacement expressions"
  putStrLn replaceResult

结论:

根据@DanielWagner的建议使用regex-applicative,我想出了下面的解决方案非常优雅:

{-# LANGUAGE RecordWildCards #-}

module Main (main) where

import Data.Char
import Data.Functor.Compose
import Data.Maybe
import System.Environment
import Text.Regex.Applicative

data EnvMatch = EnvMatch {
    envMatchName :: String
  , envMatchDefault :: String
}

-- Matches pattern "_env:SOME_NAME:SOME_DEFAULT_VALUE"
envPattern :: RE Char EnvMatch
envPattern = EnvMatch
    <$ string "_env:"
    <*> token
    <* string ":"
    <*> token
    where
        token :: RE Char String
        token = many (psym $ \c -> isAlphaNum c || c == '_')

expandEnv :: EnvMatch -> IO String
expandEnv EnvMatch{..} = fmap (fromMaybe envMatchDefault) (lookupEnv envMatchName)

queryTransform :: RE Char (IO String)
queryTransform = getCompose . (concat <$>) . sequenceA . map Compose $
    [
        pure <$> many anySym
      , expandEnv <$> envPattern
      , pure <$> many anySym
    ]

runQueryTransform :: String -> IO (Maybe String)
runQueryTransform = sequenceA . match queryTransform

main :: IO ()
main = do
  result <- runQueryTransform "hello\"_env:HOME:default_home_dir\"world"
  print result
  -- Yields: Just "hello\"/home/user\"world"

谢谢,@丹尼尔瓦格纳!

3 个答案:

答案 0 :(得分:6)

您可能希望regex-applicative提供:

match :: RE Char String -> String -> Maybe String

您可以在构建RE Char String类型值的代码中替换匹配的特定部分。例如,这是一个函数,它找到ab个字符的字符串并将它们反转:

import Text.Regex.Applicative
asAndBs   = many (psym (   `elem` "ab"))
noAsAndBs = many (psym (`notElem` "ab"))
transformation = concat <$> sequenceA [noAsAndBs, reverse <$> asAndBs, noAsAndBs]

一些例子在ghci中运行:

> match transformation "ntoheuuaaababbboenuth"
Just "ntoheuubbbabaaaoenuth"
> match transformation "aoesnuthaosneut"
Nothing

要处理您更新的问题:此处是一个转换,它会查找一串ab个字符,并询问用户要替换它们的内容。它重复使用之前的asAndBsnoAsAndBs,仅修改应用于它们的转换。我还提供了queryTransform的示例驱动程序,仅用于说明如何使用它。基本思想是建立一个生成替换字符串的IO动作而不是平坦的替换字符串。然后,调用match的消费者可以根据需要执行IO操作。

import Data.Functor.Compose
queryTransform = getCompose . (concat <$>) . sequenceA . map Compose $
    [ pure <$> noAsAndBs
    , getLine <$ asAndBs
    , pure <$> noAsAndBs
    ]
runQueryTransform = getLine >>= sequenceA . match queryTransform

我希望你能认识到之前queryTransform结构和transformation结构之间的相似之处(特别注意(concat <$>) . sequenceA结构就像之前一样)。这是ghci中的一些例子:

> runQueryTransform
oeunthaaabbbaboenuth
replacement
Just "oeunthreplacementoenuth"
> runQueryTransform
aoeunthaoeunth
Nothing

答案 1 :(得分:0)

您有chrome.tabs.onActivated,其中包含abort。我不确定PHP中的回调是什么,但我认为你不能直接将其转换为Haskell。

答案 2 :(得分:0)

如果要在Haskell中执行搜索和替换,请考虑 模式匹配 megaparsec 解析器而不是正则表达式。接着就,随即 您可以使用 streamEdit, 确切地具有您神话般的regexReplace函数的签名,或者 streamEditT,允许在IO内部运行回调。

这是您优雅的regex-applicative解决方案的解析器变体, 需要queryTransformrunQueryTransform函数。

:set -XRecordWildCards
:set -XTypeFamilies
import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import System.Environment
import Data.Char
import Data.Maybe

data EnvMatch = EnvMatch
    { envMatchName    :: String
    , envMatchDefault :: String
    }

-- Matches pattern "_env:SOME_NAME:SOME_DEFAULT_VALUE"
envPattern = EnvMatch
    <$ string "_env:"
    <*> tokes
    <* string ":"
    <*> tokes
  where
    tokes = many $ satisfy (\c -> isAlphaNum c || c == '_')

expandEnv :: EnvMatch -> IO String
expandEnv EnvMatch{..} = fmap (fromMaybe envMatchDefault) (lookupEnv envMatchName)

streamEditT envPattern expandEnv "hello\"_env:HOME:default_home_dir\"world"
"hello\"/home/user\"world"