3

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

a = 形状(矩形 3 5)

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

a = TrackedShape(74 "约翰" (矩形 3 5))

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

我还尝试查看诸如haskell-src-exts之类的解析器是否对我有用,但我不确定。看起来,虽然这些类型的库最初可能对解析 Haskell 代码有好处,但它们缺乏解析代码、更改解析树、然后在保留原始文本结构的同时将解析后的代码改其原始形式的能力.

有没有对这类任务有用的库?或者,如果做不到这一点,我是否可以编写某种函数来为此提供强大的搜索和替换?

4

3 回答 3

5

当您使用 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 解析器,上面的代码可能看起来相对难以理解。然而,就像正则表达式一样,一旦你习惯了它,它就是一种很好的工作方式。那里有很多非常完整的解析器教程,Learn you a Haskell 对 Applicatives 有一些很好的解释。<$>是 fmap 的中缀版本,并<*>以与>>=. 所以(++) <$> f <*> g我们案例中的表达式等价于

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

事实上,函数可以这样写(有一个 Applicative 和 Monad 接口),但是 applicative 风格允许非常简洁的,像正则表达式的解析器。

示例中使用的其他函数是<|>提供交替(即以这种方式或那种方式解析。因此在括号示例中,两个备选方案是打开一个新括号或解析中间不是括号的内容。)<**>,它们是一样的,<*>只是它们丢弃了一侧的结果。< 指向保留的结果。

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

于 2013-09-24T13:52:36.113 回答
1

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

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

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

于 2013-09-23T11:51:22.837 回答
1

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))"
于 2019-08-27T13:26:40.703 回答