當您使用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擁有非常豐富的功能,可以完成這樣的工作,所以它絕對是您想要完成這項工作的工具。
是的,澄清。當我說「保持結構」時,我的意思是保留縮進,換行符等。我在屏幕上所做的替換會立即顯現給用戶,所以我不希望它突然銷燬所有的縮進。我希望它看起來像*他們的*代碼,恰好被增加了 –
也許你寫了一個非常簡單的解析器(使用parsec或attoparsec),它只是解析圓括號和文本。然後,您可以將reqular表達式應用於parens中包含的任何表達式。 – mhwombat