我目前正在编写一个Haskell程序,在某些时候,我需要通过使用我自己的信息和函数来增加其他人进入系统的Haskell代码。例如,我可能希望找到用户编写的所有地方:
a =形状(矩形3 5)
并使用其他数据更改这些条目(例如,它们出现的行号或有关编写它们的用户的信息:
a = TrackedShape(74“John”(矩形3 5))
为了执行此查找和替换,我尝试使用正则表达式,但发现在许多情况下,它们的表达力不足以捕获所有用例。特别是对于上面的例子,我需要捕获形状构造函数中包含的所有内容,因此需要知道匹配的括号是什么。
我还试图看看haskell-src-exts这样的解析器对我有用,但我不确定。看来,虽然这些类型的库最初可能适合解析Haskell代码,但它们缺乏解析代码,更改解析树,然后将解析后的代码返回更改为其原始形式的能力。保留原文的结构。
是否有任何库可用于此类任务?或者,如果失败了,我能编写哪种功能可以提供强大的搜索并替换它吗?
答案 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))"