2013-08-14 25 views
6

Chcę utworzyć kombinator parsera, który będzie zbierać wszystkie wiersze poniżej bieżącego miejsca, których poziomy wcięcia będą większe lub równe około i. Myślę, że idea jest prosta:Prawidłowe wycinanie linii wcięcia w uu-parsinglib w Haskell

Spożywać linię - jeśli jej wcięcie jest:

  • ok -> zrób to dla kolejnych wierszy
  • tak -> fail

rozważmy następujący Kod:

import qualified Text.ParserCombinators.UU as UU 
import   Text.ParserCombinators.UU hiding(parse) 
import   Text.ParserCombinators.UU.BasicInstances hiding (Parser) 

-- end of line 
pEOL = pSym '\n' 

pSpace = pSym ' ' 
pTab = pSym '\t' 

indentOf s = case s of 
    ' ' -> 1 
    '\t' -> 4 

-- return the indentation level (number of spaces on the beginning of the line) 
pIndent = (+) <$> (indentOf <$> (pSpace <|> pTab)) <*> pIndent `opt` 0 

-- returns tuple of (indentation level, result of parsing the second argument) 
pIndentLine p = (,) <$> pIndent <*> p <* pEOL 

-- SHOULD collect all lines below witch indentations greater or equal i 
myParse p i = do 
    (lind, expr) <- pIndentLine p 
    if lind < i 
     then pFail 
     else do 
      rest <- myParse p i `opt` [] 
      return $ expr:rest 

-- sample inputs 
s1 = " a\ 
    \\n a\ 
    \\n" 

s2 = " a\ 
    \\na\ 
    \\n" 

-- execution 
pProgram = myParse (pSym 'a') 1 

parse p s = UU.parse ((,) <$> p <*> pEnd) (createStr (LineColPos 0 0 0) s) 

main :: IO() 
main = do 
    print $ parse pProgram s1 
    print $ parse pProgram s2 
    return() 

co daje następujący wynik:

("aa",[]) 
Test.hs: no correcting alternative found 

Wynik dla s1 jest prawidłowy. Wynik dla s2 powinien zużyć najpierw "a" i przestać konsumować. Skąd bierze się ten błąd?

Odpowiedz

1

Parsery, które budujesz, zawsze będą próbowały kontynuować; w razie potrzeby dane wejściowe zostaną odrzucone lub dodane. Jednak pFail to ślepy zaułek. Działa jako element jednostkowy dla <|>.

W pańskim parserze nie ma innej alternatywy, jeśli dane wejściowe nie są zgodne z językiem rozpoznawanym przez analizator składni. W twojej specyfikacji mówisz, że chcesz, żeby parser zawodził na wejściu s2. Teraz kończy się niepowodzeniem z komunikatem mówiącym, że się nie udało i jesteś zaskoczony.

Może nie chcesz, aby to się nie udało, ale chcesz przestać akceptować dalsze informacje? W takim przypadku zastąpi pFail przez return [].

Należy zauważyć, że tekst:

do 
    rest <- myParse p i `opt` [] 
    return $ expr:rest 

może być zastąpiony przez (expr:) <$> (myParse p i `opt` [])

Naturalnym sposobem rozwiązania problemu jest prawdopodobnie coś

pIndented p = do i <- pGetIndent 
      (:) <$> p <* pEOL <*> pMany (pToken (take i (repeat ' ')) *> p <* pEOL) 

pIndent = length <$> pMany (pSym ' ') 
+0

Dziękuję, ale nie ma jeszcze całkowicie rozwiązać mój problem (zaktualizowałem dany kod) - co zrobić, jeśli wcięcia mogą być spacjami lub zakładkami, gdzie karty są 4 spacje? –

+0

dodatkowe 'return []' does ** not ** działa tak, jak chcemy - jeśli zastąpimy 'pFail' przez' return [] 'drugi" a "zostanie zużyty przez parser (zostanie on zużyty i' [] 'zostanie zwrócony) - Nie chcę, aby drugi" a "w przykładzie s2 został zużyty. –

+0

Jeśli chcesz poprawnie obsługiwać tabulatory (i nie zastępować tabulatorów tylko czterema spacjami), będziesz musiał zaprogramować swój własny mały skończony automat stanów, który "wie", co oznacza tabulator. –