2012-03-30 7 views
4

Ich entschied mich selbst zu lehren, wie man Parsec verwendet, und ich habe ein bisschen eine Straßensperre mit dem Spielzeugprojekt, das ich mir selbst zugewiesen habe, getroffen.Haskell: Warum wird mein Parser nicht korrekt zurückverfolgt?

Ich versuche HTML zu analysieren, und zwar:

<html> 
    <head> 
    <title>Insert Clever Title</title> 
    </head> 
    <body> 
    What don't you like? 
    <select id="some stuff"> 
     <option name="first" font="green">boilerplate</option> 
     <option selected name="second" font="blue">parsing HTML with regexes</option> 
     <option name="third" font="red">closing tags for option elements 
    </select> 
    That was short. 
    </body> 
</html> 

Mein Code ist:

{-# LANGUAGE FlexibleContexts, RankNTypes #-} 
module Main where 

import System.Environment (getArgs) 
import Data.Map hiding (null) 
import Text.Parsec hiding ((<|>), label, many, optional) 
import Text.Parsec.Token 
import Control.Applicative 

data HTML = Element { tag :: String, attributes :: Map String (Maybe String), children :: [HTML] } 
      | Text { contents :: String } 
    deriving (Show, Eq) 

type HTMLParser a = forall s u m. Stream s m Char => ParsecT s u m a 

htmlDoc :: HTMLParser HTML 
htmlDoc = do 
    spaces 
    doc <- html 
    spaces >> eof 
    return doc 

html :: HTMLParser HTML 
html = text <|> element 

text :: HTMLParser HTML 
text = Text <$> (many1 $ noneOf "<") 

label :: HTMLParser String 
label = many1 . oneOf $ ['a' .. 'z'] ++ ['A' .. 'Z'] 

value :: HTMLParser String 
value = between (char '"') (char '"') (many anyChar) <|> label 

attribute :: HTMLParser (String, Maybe String) 
attribute = (,) <$> label <*> (optionMaybe $ spaces >> char '=' >> spaces >> value) 

element :: HTMLParser HTML 
element = do 
    char '<' >> spaces 
    tag <- label 
    -- at least one space between each attribute and what was before 
    attributes <- fromList <$> many (space >> spaces >> attribute) 
    spaces >> char '>' 
    -- nested html 
    children <- many html 
    optional $ string "</" >> spaces >> string tag >> spaces >> char '>' 
    return $ Element tag attributes children 

main = do 
    source : _ <- getArgs 
    result <- parse htmlDoc source <$> readFile source 
    print result 

Das Problem scheint zu sein, dass mein Parser nicht schließen Tags mag - es scheint gierig < unter der Annahme bedeutet immer einen Starttag (soweit ich das beurteilen kann):

% HTMLParser temp.html 
Left "temp.html" (line 3, column 32): 
unexpected "/" 
expecting white space 

I habe ein bisschen damit herumgespielt, und ich bin mir nicht sicher, warum es nicht hinter dem char '<' Spiel zurückgeht.

+6

Parsec Backtracking nur bei einem Fehler, wenn Sie 'try' verwenden. – ehird

+1

Und manchmal nicht einmal dann -.-. Attoparsec ist in dieser Hinsicht noch schlimmer. –

Antwort

2

Wie ehird sagte, musste ich versuchen, verwenden:

attribute = (,) <$> label <*> (optionMaybe . try $ spaces >> char '=' >> spaces >> value) 
--... 
attributes <- fromList <$> many (try $ space >> spaces >> attribute) 
--... 
children <- many $ try html 
optional . try $ string "</" >> spaces >> string tag >> spaces >> char '>'