2012-04-16 14 views
1

「チャンク」のリストを解析したい文字列がいくつかあります。私の文字列は、このhaskellで文字列を解析する

"some text [[anchor]] some more text, [[another anchor]]. An isolated [" 

のように見て、私はこの

[ 
    TextChunk "some text ", 
    Anchor "anchor", 
    TextChunk " some more text, " 
    Anchor "another anchor", 
    TextChunk ". An isolated [" 
] 

のようなものを取り戻すことを期待し、私は私が必要なものを行う機能と型を記述するために管理してきましたが、彼らはあまりに醜いようです。 これを行うにはより良い方法がありますか?

data Token = TextChunk String | Anchor String deriving (Show) 
data TokenizerMode = EatString | EatAnchor deriving (Show) 

tokenize::[String] -> [Token] 
tokenize xs = 
    let (_,_,tokens) = tokenize' (EatString, unlines xs, [TextChunk ""]) 
    in reverse tokens 

tokenize' :: (TokenizerMode, String, [Token]) -> (TokenizerMode, String,[Token]) 
-- If we're starting an anchor, add a new anchor and switch modes 
tokenize' (EatString, '[':'[':xs, tokens) = tokenize' (EatIdentifier, xs, (Identifier ""):tokens) 
-- If we're ending an anchor ass a new text chunk and switch modes 
tokenize' (EatAnchor, ']':']':xs, tokens) = tokenize' (EatString, xs, (TextChunk ""):tokens) 
-- Otherwise if we've got stuff to consume append it 
tokenize' (EatString, x:xs, (TextChunk t):tokens) = tokenize'(EatString, xs, (TextChunk (t++[x])):tokens) 
tokenize' (EatAnchor, x:xs, (Identifier t):tokens) = tokenize'(EatAnchor, xs, (Identifier (t++[x])):tokens) 
--If we've got nothing more to consume we're done. 
tokenize' (EatString, [], tokens) = (EatString, [], tokens) 
--We'll only get here if we're given an invalid string 
tokenize' xx = error ("Error parsing .. so far " ++ (show xx)) 
+2

これは実際にトークン化していないので、解析しています。そして、すべてのあなたの解析ニーズのために、Parsec。 –

+0

@CatPlusPlusは、その解析で一致するテキストとタイトルを更新することに同意しました。 –

+0

@CatPlusPlus parsecを使ってどのように見えるか教えてください。私は私の好みに少し不明瞭なドキュメント/ tutesを見つけています。 –

答えて

11

これが唯一のブラケットを含め、動作するはずです:

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

data Text = TextChunk String 
      | Anchor String 
      deriving Show 

chunkChar = noneOf "[" <|> try (char '[' <* notFollowedBy (char '[')) 
chunk  = TextChunk <$> many1 chunkChar 
anchor = Anchor <$> (string "[[" *> many (noneOf "]") <* string "]]") 
content = many (chunk <|> anchor) 

parseS :: String -> Either ParseError [Text] 
parseS input = parse content "" input 

chunkCharパーサが2つの開口ブラケットと一致したときにバックトラックできるようにするtryの使用に注意してください。 tryがなければ、最初の括弧はその時点で消費されていました。

4

は、ここでは、2つの相互に再帰的な関数を使用して単純化したバージョンです。

module Tokens where 

data Token = TextChunk String | Anchor String deriving (Show) 

tokenize :: String -> [Token] 
tokenize = textChunk emptyAcc 


textChunk :: Acc -> String -> [Token] 
textChunk acc []   = [TextChunk $ getAcc acc] 
textChunk acc ('[':'[':ss) = TextChunk (getAcc acc) : anchor emptyAcc ss 
textChunk acc (s:ss)  = textChunk (snocAcc acc s) ss 

anchor :: Acc -> String -> [Token] 
anchor acc []    = error $ "Anchor not terminated" 
anchor acc (']':']':ss) = Anchor (getAcc acc) : textChunk emptyAcc ss 
anchor acc (s:ss)   = anchor (snocAcc acc s) ss 


-- This is a Hughes list (also called DList) which allows 
-- efficient 'Snoc' (adding to the right end). 
-- 
type Acc = String -> String 

emptyAcc :: Acc 
emptyAcc = id 

snocAcc :: Acc -> Char -> Acc 
snocAcc acc c = acc . (c:) 

getAcc :: Acc -> String 
getAcc acc = acc [] 

このバージョンでは、入力はテキスト中の隣接する2つのアンカーがある場合、起動時またはアンカーで終わるかどうかは、空TextChunksを生成するという問題があります。

アキュムレータが空であるが、それは約2倍のロングコードを行う場合TextChunkを生成しないにチェックを追加するために単純明快である - モナドを使って

+0

空のTextChunを気にしていたら、空のTextChunを後処理としてかなり簡単に除外することができました。 –

+0

リストへの追加についてのパフォーマンスポインタと、DListがうまく動作してくれてありがとう。 –

1

ソリューション...多分私は結局Parsecのために達するだろうParsec。どのように動作する

import Text.ParserCombinators.Parsec 

data Text = TextChunk String 
      | Anchor String 
      deriving Show 

inputString = "some text [[anchor]] some more text, [[another anchor]]." 

content :: GenParser Char st [Text] 
content = do 
    s1 <- many (noneOf "[") 
    string "[[" 
    s2 <- many (noneOf "]") 
    string "]]" 
    s3 <- many (noneOf "[") 
    string "[[" 
    s4 <- many (noneOf "]") 
    string "]]." 
    return $ [TextChunk s1, Anchor s2, TextChunk s3, Anchor s4] 


parseS :: String -> Either ParseError [Text] 
parseS input = parse content "" input 

> parseS inputString 
Right [TextChunk "some text ",Anchor "anchor",TextChunk " some more text, ",Anchor "another anchor"] 
it :: Either ParseError [Text] 
+2

もっと一般的には、 'chunk = TextChunk <$> many1(noneOf" [")'と 'anchor = Anchor <$>(文字列[[" * * many(noneOf "])の' content = many(チャンク<|>アンカー) ")<* string">] ")'( 'Control.Applicative'からのいくつかのショートカットを使用します)。それはテキストチャンクとアンカーの任意の組み合わせで動作するはずです – hammar

+0

@ハマー、それはほぼOKですが、私はそれがテキスト[''を許可しないと推測しています。私はそれを明確にするために私のサンプル文字列にそれを追加します。ただ、[[stuff]]をアンカーとして扱い、テキストチャンクにはまっていればいいだけです。 –