2011-10-13 8 views
6

próbuję napisać parser korzystając parsec że będzie analizować pliki posiadające wiedzę Haskell, takie jak:Parsek - błąd „syntezator«wiele»jest stosowany do parsera, który akceptuje pusty ciąg”

The classic 'Hello, world' program. 

\begin{code} 

main = putStrLn "Hello, world" 

\end{code} 

More text. 

pisałem następujące, sort-of-inspirowany przykładów w RWH:

import Text.ParserCombinators.Parsec 

main 
    = do contents <- readFile "hello.lhs" 
     let results = parseLiterate contents 
     print results 

data Element 
    = Text String 
    | Haskell String 
    deriving (Show) 


parseLiterate :: String -> Either ParseError [Element] 

parseLiterate input 
    = parse literateFile "(unknown)" input 



literateFile 
    = many codeOrProse 

codeOrProse 
    = code <|> prose 

code 
    = do eol 
     string "\\begin{code}" 
     eol 
     content <- many anyChar 
     eol 
     string "\\end{code}" 
     eol 
     return $ Haskell content 

prose 
    = do content <- many anyChar 
     return $ Text content 

eol 
    = try (string "\n\r") 
    <|> try (string "\r\n") 
    <|> string "\n" 
    <|> string "\r" 
    <?> "end of line" 

Jakie mam nadzieję doprowadzić coś wzdłuż linii:

[Text "The classic 'Hello, world' program.", Haskell "main = putStrLn \"Hello, world\"", Text "More text."] 

(z dopuszczeniem białych znaków itp.).

To kompiluje poprawnie, ale po uruchomieniu, pojawia się błąd:

*** Exception: Text.ParserCombinators.Parsec.Prim.many: combinator 'many' is applied to a parser that accepts an empty string 

Czy ktoś może rzucić jakieś światło na to, a może pomóc roztworem proszę?

Odpowiedz

8

Jak wskazano, problem dotyczy many anyChar. Ale nie tylko w prose, ale także w code. Problem z code polega na tym, że content <- many anyChar zużyje wszystko: znaki nowej linii i znacznik \end{code}.

Musisz więc mieć sposób na odróżnienie prozy i kodu. Łatwym (ale może zbyt naiwny) sposób to zrobić, to patrzeć na ukośniki:

literateFile = many codeOrProse <* eof 

code = do string "\\begin{code}" 
      content <- many $ noneOf "\\" 
      string "\\end{code}" 
      return $ Haskell content 

prose = do content <- many1 $ noneOf "\\" 
      return $ Text content 

Teraz nie mają całkowicie pożądanego rezultatu, ponieważ Haskell część będzie także zawierać znaki nowej linii, ale można odfiltruj je dość łatwo (biorąc pod uwagę funkcję filterNewlines możesz powiedzieć `content <- filterNewlines <$> (many $ noneOf "\\")).

Edit

Okay, myślę, że znalazł rozwiązanie (wymaga najnowszej parsec wersję, ponieważ lookAhead):

import Text.ParserCombinators.Parsec 
import Control.Applicative hiding (many, (<|>)) 

main 
    = do contents <- readFile "hello.lhs" 
     let results = parseLiterate contents 
     print results 

data Element 
    = Text String 
    | Haskell String 
    deriving (Show)  

parseLiterate :: String -> Either ParseError [Element] 

parseLiterate input 
    = parse literateFile "" input 

literateFile 
    = many codeOrProse 

codeOrProse = code <|> prose 

code = do string "\\begin{code}\n" 
      c <- untilP (string "\\end{code}\n") 
      string "\\end{code}\n" 
      return $ Haskell c 

prose = do t <- untilP $ (string "\\begin{code}\n") <|> (eof >> return "") 
      return $ Text t 

untilP p = do s <- many $ noneOf "\n" 
       newline 
       s' <- try (lookAhead p >> return "") <|> untilP p 
       return $ s ++ s' 

untilP p analizuje wiersz, a następnie sprawdza, czy początek następna linia może być pomyślnie przeanalizowana przez p. Jeśli tak, zwraca pusty łańcuch, w przeciwnym razie jest on kontynuowany. Wymagany jest kod lookAhead, ponieważ w przeciwnym razie znaczniki begin \ end-tag zostaną zużyte, a code nie będą mogły ich rozpoznać.

Sądzę, że można jeszcze bardziej zwięźle napisać (tzn. Nie trzeba powtarzać string "\\end{code}\n" wewnątrz code).

+0

Problem polega na tym, że zarówno kod, jak i proza ​​mogą zawierać odwrotne ukośniki (kod z powodu lambdas itp. Oraz proza, ponieważ może zawierać polecenia TeX). – stusmith

+0

(Jedynym czynnikiem wyróżniającym jest to, że \ begin {code} i \ end {code} muszą znajdować się na newlines). – stusmith

+0

Rozumiem - nie przemyślałem tego ... Myślę, że będziesz musiał jakoś zrestrukturyzować swoją gramatykę, aby sprawdzał po każdym nowym wierszu, czy następna część to \ begin {code} lub \ end {code} string . Niestety. Nie mam takiego doświadczenia z gramatykami. – bzn

5

ja jej nie testowane, ale:

  • many anyChar można dopasować pusty ciąg
  • Dlatego prose można dopasować pusty ciąg
  • Dlatego codeOrProse można dopasować pusty ciąg
  • Dlatego literateFile może trwać wiecznie, dopasowując nieskończenie wiele pustych ciągów znaków

Zmiana prose w celu dopasowania many1 znaków może rozwiązać ten problem.

(nie jestem bardzo obeznany z parsec, ale jak będzie prose wiedzieć ile znaki powinny pasować? To może konsumować cały wkład, nie dając code parser drugą szansę spojrzeć na początku nowy segment kodu Alternatywnie, może on pasować tylko do jednego znaku w każdym wywołaniu, co czyni go bezużytecznym.)

+0

I zdecydowanie zobaczyć, co mówisz, dlatego starałem się umieścić część kod przed część prose w <|>. Chyba chcę nie-chciwych "wielu". – stusmith

0

Dla porównania, oto inna wersja wymyśliłem (lekko rozszerzony do obsługi innych przypadków):

import Text.ParserCombinators.Parsec 

main 
    = do contents <- readFile "test.tex" 
     let results = parseLiterate contents 
     print results 

data Element 
    = Text String 
    | Haskell String 
    | Section String 
    deriving (Show) 

parseLiterate :: String -> Either ParseError [Element] 

parseLiterate input 
    = parse literateFile "(unknown)" input 

literateFile 
    = do es <- many elements 
     eof 
     return es 

elements 
    = try section 
    <|> try quotedBackslash 
    <|> try code 
    <|> prose 

code 
    = do string "\\begin{code}" 
     c <- anyChar `manyTill` try (string "\\end{code}") 
     return $ Haskell c 

quotedBackslash 
    = do string "\\\\" 
     return $ Text "\\\\" 

prose 
    = do t <- many1 (noneOf "\\") 
     return $ Text t 

section 
    = do string "\\section{" 
     content <- many1 (noneOf "}") 
     char '}' 
     return $ Section content