2012-04-16 2 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 Можете ли вы показать мне, как это будет выглядеть с помощью парсека? Я нахожу, что docs/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 

Обратите внимание на использование try, чтобы возвратов, когда chunkChar анализатор соответствует двум открывающей скобки. Без try первая скобка была бы потреблена в этот момент.

4

Это упрощенная версия с использованием двух взаимно-рекурсивных функций.

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 [] 

Эта версия имеет проблемы, что он будет генерировать пустые TextChunks, если вход начинается или заканчивается Якорь или, если есть два смежных якоря в тексте.

Это прямо вперед, чтобы добавить проверку, чтобы не генерировать TextChunk если аккумулятор пуст, но это делает код примерно в два раза дольше - возможно, я бы тянуться Parsec в конце концов ...

+0

Если бы я заботился о пустых TextChunks, я мог бы легко удалить пустой TextChunks в качестве почтового процесса. –

+0

Спасибо за указатель производительности о добавлении к списку, а DList работает. –

1

Решения с использованием монадического Парсек.

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

В общем, вы можете написать 'content = many (chunk <|> anchor)' с 'chunk = TextChunk <$> many1 (noneOf" [")' и 'anchor = Anchor <$> (string" [["*> many (noneOf"] ") <* string"]] ")' (используя некоторые ярлыки из 'Control.Applicative'). Это должно работать с любой комбинацией текстовых фрагментов и якорей – hammar

+0

@hammar, что почти нормально, но я предполагаю, что это не позволяет «[» в тексте. Я добавлю, что для моей строки примера, чтобы сделать ее более ясной, я хочу, чтобы «[[материал]]» рассматривался как якорь и что-то еще, чтобы застрять в текстовом фрагменте. –

Смежные вопросы