2013-09-23 82 views
2

我目前正在編寫一個Haskell程序,在某些時候,我需要通過用我自己的信息和函數對其他人進入系統的Haskell代碼進行操作。例如,我可能希望找到用戶寫東西像所有的地方:Haskell括號匹配查找和替換

A =形狀(矩形3 5)

,並改變這些條目與附加數據(例如,行號,他們出現在或者是誰寫了他們的用戶信息:

A = TrackedShape(74「約翰」(矩形3 5))

爲了做到這一點查找和替換,我也嘗試過使用正則表達式,但發現在很多情況下,它們沒有足夠的表現力去捕獲所有的用例,特別是對於上面的例子,我需要t o捕獲包含在形狀構造函數中的所有內容,因此需要知道匹配的括號是什麼。

我也試着看看解析器如haskell-src-exts會對我有用,但我不確定。看起來,儘管這些庫最初可能對解析Haskell代碼有好處,但它們缺乏解析代碼,更改解析樹的能力,然後將解析的代碼更改爲其原始形式,同時保留其原始結構原文。

是否有任何庫對這類任務有用?或者,如果失敗了,是否有某種我可以編寫的功能可以提供強大的搜索功能並替代它?

回答

0

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會給你原始的源信息,因此理論上你可以在操作過程中保留這些信息,然後在打印中使用它。我懷疑你只能使用行號在適當的點上引入換行符(和縮進後),而行間的間隔相對標準。

一個問題是,當您解析它時,您將在源代碼中丟失註釋。我能想到的避免這種情況的唯一方法是解析它,並嘗試使用原始源位置來決定修改原始文件的位置(而不是從解析後的表單中再次分離)。但是通常解析代碼並以可管理的形式保留所有註釋(包括內聯註釋)是一項相當棘手的任務,而且您通常無法找到庫。

+0

是的,澄清。當我說「保持結構」時,我的意思是保留縮進,換行符等。我在屏幕上所做的替換會立即顯現給用戶,所以我不希望它突然銷燬所有的縮進。我希望它看起來像*他們的*代碼,恰好被增加了 –

+0

也許你寫了一個非常簡單的解析器(使用parsec或attoparsec),它只是解析圓括號和文本。然後,您可以將reqular表達式應用於parens中包含的任何表達式。 – mhwombat

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甚至應用型分析器工作。但是,像正則表達式一樣,一旦你習慣了它,這是一種很好的工作方式。有很多非常完整的解析器教程,並且瞭解Haskell對應用程序有一些很好的解釋。<$>是fmap的中綴版本,並且<*>以與>>=類似(但功能較弱)的方式將表達式連接在一起。因此,在我們的例子表達(++) <$> f <*> g相當於

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

事實上,該功能本來是這樣寫的(有一個應用型和單子接口),但應用性的風格可以非常簡潔,正則表達式像解析器。

本示例中使用的其他函數是<|>,它提供了交替(即以這種方式或以這種方式進行解析。因此,在括號中,兩個選項打開一個新的括號或解析中間不是)和<**>,它們類似於<*>,只是它們放棄了其中一方的結果。 <指向保留的結果。

請注意,我注意到實現了額外的功能來檢測行號,但Parsec可以做到這一點,因爲解析器monad實際上是一個monad變換器,因此可以放在狀態monad的頂部以保留任意信息解析。 Parsec擁有非常豐富的功能,可以完成這樣的工作,所以它絕對是您想要完成這項工作的工具。

+0

「,因爲任何應用程序產生一個Monad」 呃......很確定這是不正確的。 – singpolyma

+0

哎呀,錯了!我實際上已經刪除了評論,因爲我認爲無論如何這一點都不是特別有用。 –