用于查找和替换的Haskell括号匹配

时间:2013-09-23 11:15:01

标签: regex parsing haskell replace parentheses

我目前正在编写一个Haskell程序,在某些时候,我需要通过使用我自己的信息和函数来增加其他人进入系统的Haskell代码。例如,我可能希望找到用户编写的所有地方:

a =形状(矩形3 5)

并使用其他数据更改这些条目(例如,它们出现的行号或有关编写它们的用户的信息:

a = TrackedShape(74“John”(矩形3 5))

为了执行此查找和替换,我尝试使用正则表达式,但发现在许多情况下,它们的表达力不足以捕获所有用例。特别是对于上面的例子,我需要捕获形状构造函数中包含的所有内容,因此需要知道匹配的括号是什么。

我还试图看看haskell-src-exts这样的解析器对我有用,但我不确定。看来,虽然这些类型的库最初可能适合解析Haskell代码,但它们缺乏解析代码,更改解析树,然后将解析后的代码返回更改为其原始形式的能力。保留原文的结构。

是否有任何库可用于此类任务?或者,如果失败了,我能编写哪种功能可以提供强大的搜索并替换它吗?

3 个答案:

答案 0 :(得分:4)

当您使用Haskell并且正则表达式失败时,请联系Parsec!我个人认为这是Haskell最好的功能之一,我在其他语言中工作时会错过,解析无上下文语言甚至上下文敏感语言与使用正则表达式一样简单,甚至更容易。

import Text.ParserCombinators.Parsec
import Control.Applicative ((<$>),(<*>),(*>),(<*))
import Control.Monad
import Text.Printf

-- Handy helper to concat successive parsers that return strings
infixl 4 <++>
f <++> g = (++) <$> f <*> g

-- Parse a balanced number of brackets
brackets :: Parser String
brackets = string "(" <++> (join <$> many brackets) <++> string ")" <|> many1 (noneOf "()")

-- Parser that will perform your example replace
replaceShape :: Int -> String -> Parser String
replaceShape line name = printf "TrackedShape (%i \"%s\" %s)" line name <$> (string "Shape " *> brackets)

在GHCi中进行测试:

> parseTest (replaceShape 10 "John") "Shape (Rectangle 3 5)"
"TrackedShape (10 \"John\" (Rectangle 3 5))"

如果您以前没有使用Applicatives甚至Applicative解析器,上面的代码可能看起来相对难以理解。但是,就像正则表达式一样,一旦习惯它,它就是一种很好的工作方式。有很多非常完整的解析器教程,并且了解一下Haskell对Applicatives有一些很好的解释。 <$>是fmap的中缀版本,<*>以类似(但不太强大)的方式将表达式连接到>>=。因此,在我们的例子中,表达式(++) <$> f <*> g等同于

do
    a <- f
    b <- g
    return $ a ++ b

事实上,该函数可能是以这种方式编写的(有一个Applicative和Monad接口),但是应用程序样式允许非常简洁,正则表达式像解析器。

示例中使用的其他函数是<|>,它提供了交替(即以这种方式或那种方式解析。所以在括号示例中,两个替代方案是打开一个新的括号或解析中间的东西,不是是一个括号。)和<**>,它们类似于<*>,除非它们丢弃其中一个边的结果。 &lt;指向保留的结果。

请注意,我已经注意到了实现检测行号的额外功能,但是Parsec可以这样做,因为解析器monad实际上是monad转换器,因此可以放在状态monad之上以在解析期间保留任意信息。 Parsec有一个非常丰富的功能集来做这样的事情,所以它绝对是你想要的工作。

答案 1 :(得分:0)

haskell-src-exts确实有一个代码打印机:http://hackage.haskell.org/packages/archive/haskell-src-exts/1.14.0/doc/html/Language-Haskell-Exts-Pretty.html当你说保留结构时,你的意思是保留换行符的原始位置吗?

此模块http://hackage.haskell.org/packages/archive/haskell-src-exts/1.14.0/doc/html/Language-Haskell-Exts-Annotated.html中的默认parseFile功能确实为您提供了原始源信息,因此理论上您可以在操作期间保留该信息,然后在打印中使用它。我怀疑你可能只使用行号来在适当的点引入换行符(以及跟随缩进),并且行内的间距相对标准。

一个问题是,在解析它时,您将丢失源中的注释。我能想到避免这种情况的唯一方法是解析它,并尝试使用原始源位置来决定修改原始文件的位置(而不是从解析后的表单再次吐出)。但一般来说,解析代码并以可管理的形式保留所有注释(包括内联注释)是一项非常棘手的任务,而且您经常找不到库。

答案 2 :(得分:0)

megaparsec解析器知道 行号,而replace-megaparsec包允许您 搜索模式匹配项,然后编辑找到的匹配项。这是使用的解决方案 Replace.Megaparsec.streamEdit

import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char

let pattern :: Parsec Void String (Int,String)
    pattern = do
        string "Shape("
        lineNumber <- unPos <$> sourceLine <$> getSourcePos
        parenInner <- many $ noneOf ")"
        string ")"
        return (lineNumber, parenInner)

let editor (lineNumber, parenInner) =
        "TrackedShape(" ++
        show lineNumber ++
        " \"John\" (" ++
        parenInner ++
        "))"
>>> streamEdit pattern editor "a = Shape(Rectangle 3 5)"
"a = TrackedShape(1 \"John\" (Rectangle 3 5))"