Parsec не может разобрать, если символы следуют за моей строкой

Я пытаюсь написать что-то для анализа моего шаблона Django, однако мой парсер дает сбой, если что-то следует за {% endblock %}

Вот что у меня есть до сих пор

import Control.Monad
import Text.ParserCombinators.Parsec


data Piece = StaticPiece String 
           | BlockPiece String [Piece]
           | VarPiece String
  deriving (Show)

noWhitespace = many1 $ oneOf "_" <|> alphaNum

parseBlock = do
  blockName <- between (string "{% block" >> spaces) (spaces >> string "%}") noWhitespace <?> "block tag"
  blockContent <- many (parsePiece (void $ try $ (string "{% endblock %}")))
  return $ BlockPiece blockName blockContent

parseVar = do
  var <- between (string "{{" >> spaces) (spaces >> string "}}") noWhitespace <?> "variable"
  return $ VarPiece var

parseStatic end = do
  s <- manyTill (anyChar) $ end <|> (void $ lookAhead $ try $ parseNonStatic)
  return $ StaticPiece s 

parseNonStatic = try parseBlock <|> parseVar
parsePiece s = try parseNonStatic <|> (parseStatic s)

parsePieces = manyTill (parsePiece eof) eof

main :: IO ()
main = do
  putStrLn "1"
  print $ parse parsePieces "" "Blah blah blah"
  putStrLn "2"
  print $ parse parsePieces "" "{{ some_var }} string {{ other_var }} s"
  putStrLn "3"
  print $ parse parsePieces "" "{% block body %}{% endblock %}"
  putStrLn "4"
  print $ parse parsePieces "" "{% block body %}{{ hello }}{% endblock %}"
  putStrLn "5"
  print $ parse parsePieces "" "{% block body %}{% {% endblock %}"
  putStrLn "6"
  print $ parse parseBlock ""  "{% block body %}{% endblock %} "
  putStrLn "7"
  print $ parse parsePieces "" "{% block body %} {} { {{ }{ {{{}} cool } {% block inner_body %} Hello: {{ hello }}{% endblock %} {% endblock %}"
  putStrLn "8"
  print $ parse parsePieces "" "{% block body %} {} {{ cool }} {% block inner_body %} Hello: {{ hello }}{% endblock %}{% endblock %} ldsakjf"
  print ">>"
  --
  print $ parse parseBlock ""  "{% block body %}{% endblock %} "

Я думаю, что как-то вместо того, чтобы смотреть на строку от начала до конца, он как-то смотрит на нее с конца. Если вы посмотрите на #7, StaticPiece " " находится внутри самого внутреннего блока, хотя он должен быть в блоке body. Любая помощь будет оценена по достоинству.

Отредактируйте приведенные выше выходные данные кода:

1
Right [StaticPiece "Blah blah blah"]
2
Right [VarPiece "some_var",StaticPiece " string ",VarPiece "other_var",StaticPiece " s"]
3
Right [BlockPiece "body" [StaticPiece ""]]
4
Right [BlockPiece "body" [VarPiece "hello",StaticPiece ""]]
5
Right [BlockPiece "body" [StaticPiece "{% "]]
6
Left (line 1, column 32):
unexpected end of input
expecting "{% endblock %}", block tag or variable
7
Right [BlockPiece "body" [StaticPiece " {} { {{ }{ {{{}} cool } ",BlockPiece "inner_body" [StaticPiece " Hello: ",VarPiece "hello",StaticPiece "",StaticPiece " "]]]
8
Right [StaticPiece "{% block body %} {} ",VarPiece "cool",StaticPiece " {% block inner_body %} Hello: ",VarPiece "hello",StaticPiece "{% endblock %}{% endblock %} ldsakjf"]
">>"
Left (line 1, column 32):
unexpected end of input
expecting "{% endblock %}", block tag or variable

person DantheMan    schedule 21.02.2014    source источник
comment
Можете ли вы включить вывод вашего кода?   -  person Karolis Juodelė    schedule 21.02.2014
comment
Является ли проблема manyTill (parsePiece eof) eof?   -  person AndrewC    schedule 21.02.2014
comment
Мне показалось, что эта часть выглядит странно, но я не смог отрефакторить ее без получения бесконечного цикла.   -  person DantheMan    schedule 21.02.2014
comment
Правильно ли я думаю, что в шаблоне Django все, что не находится между {% block body %} и {% endblock %} или {{enclosed in moustaches}}, является статической частью?   -  person AndrewC    schedule 21.02.2014
comment
Ну, есть теги шаблонов, такие как {% url "home:terms" %}, но я могу сделать этот парсер позже.   -  person DantheMan    schedule 21.02.2014
comment
Является ли ошибкой наличие {% без соответствия %} и т. д.?   -  person AndrewC    schedule 21.02.2014
comment
Нет, не похоже. Django просто отображает это.   -  person DantheMan    schedule 21.02.2014


Ответы (2)


Давайте перепишем некоторые парсеры, чтобы все работало гладко.

Используйте manyTill для разбора блоков с соответствующими тегами конечных блоков.

Во-первых, нам нужно использовать синтаксические анализаторы, соответствующие {% something or other %}, поэтому давайте сделаем это функцией:

tag p = between (string "{%" >> spaces) (spaces >> string "%}") p <?> "tag"
ghci> parse (tag $ string "any parser here") "" "{% any parser here %}"
Right "any parser here"

Давайте используем manyTill в parseBlock, чтобы получить тег конечного блока. Я все еще использую try, потому что tag (string "endblock") может не прочитать некоторые входные данные, например { в начале переменной или другого не тега.

parseBlock = do
  blockName <- tag (string "block" >> spaces >> noWhitespace) <?> "block tag"
  blockContent <- manyTill parsePiece (try $ tag $ string "endblock") 
  return $ BlockPiece blockName blockContent

parseStatic не должен ничего совпадать и должен приостанавливаться для проверки тегов/переменных

parseStatic является источником большинства проблем с этим синтаксическим анализатором - он допускает все, кроме тега или переменной, что всегда проблематично - синтаксические анализаторы гораздо лучше следуют правилам, чем либеральны.

Нам нужно помешать parseStatic просто съесть оставшуюся часть ввода, чтобы нестатические парсеры получили возможность попробовать еще раз, поэтому давайте заставим парсер смотреть на следующий символ, не используя его каким-либо образом. Использование одного символа, подобного этому, позволяет избежать большого количества возвратов, хотя позже мы увидим, что нужно сделать некоторые комбинации.

peekChar = void . try . lookAhead .char 

parseStatic также не должно совпадать с пустой строкой — синтаксические анализаторы, соответствующие пустой строке, не могут использоваться с любым комбинатором many, потому что они допускают бесконечные синтаксические анализы, такие как [StaticPiece "",StaticPiece "",StaticPiece ""..]. Вот почему мы разрешаем любой символ, который нам нравится (включая {), а затем столько символов, сколько нам нравится, но не {. Единственная вещь, кроме {, которая может завершать StaticPiece, — это конец ввода, поэтому здесь разрешено eof.

parseStatic = do
  c <- anyChar
  s <- manyTill anyChar (peekChar '{' <|> eof)
  return $ StaticPiece (c:s) 
ghci> parse parseStatic "" "some stuff not containing { other stuff"
Right (StaticPiece "some stuff not containing ")

Итак, мы получаем

parsePieces = manyTill parsePiece eof

Склейте эти статики вместе

Теперь мы получаем хорошие синтаксические анализы, такие как

ghci> parse parsePieces "" "{{ some_var }} string {{ other_var }} s"
Right [VarPiece "some_var",StaticPiece " string ",VarPiece "other_var",StaticPiece " s"]

но и более уродливые, такие как

ghci> parse parsePieces "" "{% block body %} {} { {{ }{ {{{}} cool } {% block inner_body %} Hello: {{ hello }}{% endblock %} {% endblock %}"
Right [BlockPiece "body" [StaticPiece " ",StaticPiece "{} ",StaticPiece "{ ",StaticPiece "{",StaticPiece "{ }",StaticPiece "{ ",StaticPiece "{",StaticPiece "{",StaticPiece "{}} cool } ",BlockPiece "inner_body" [StaticPiece " Hello: ",VarPiece "hello"],StaticPiece " "]]

потому что parseStatic останавливается каждый раз, когда мы нажимаем {. Давайте объединим соседние статические объекты в один с помощью нескольких вспомогательных функций:

isStatic :: Piece -> Bool
isStatic (StaticPiece _) = True
isStatic _ = False

unStatic :: Piece -> String
unStatic (StaticPiece s) = s
unStatic _ = error "unStatic: applied to something other than a StaticPiece"

Мы будем использовать span :: (a -> Bool) -> [a] -> ([a], [a]) для сбора нестатических данных и объединения статики:

combineStatics :: [Piece] -> [Piece] 
combineStatics pieces = let (nonstatics,therest) = span (not.isStatic) pieces in
    nonstatics ++ combine therest where
      combine [] = []
      combine ps = let (statics,more) = span isStatic ps in
        (StaticPiece . concat . map unStatic) statics : combineStatics more

и перепишите parseBlock, чтобы объединить любую статику в содержимом блока:

parseBlock = do
  blockName <- tag (string "block" >> spaces >> noWhitespace) <?> "block tag"
  blockContent <- manyTill parsePiece (try $ tag $ string "endblock")
  return $ BlockPiece blockName (combineStatics blockContent)

Теперь это работает хорошо

Тесты теперь работают так, как я думаю, вы надеетесь:

1
Right [StaticPiece "Blah blah blah"]
2
Right [VarPiece "some_var",StaticPiece " string ",VarPiece "other_var",StaticPiece " s"]
3
Right [BlockPiece "body" []]
4
Right [BlockPiece "body" [VarPiece "hello"]]
5
Right [BlockPiece "body" [StaticPiece "{% "]]
6
Right (BlockPiece "body" [])
7
Right [BlockPiece "body" [StaticPiece " {} { {{ }{ {{{}} cool } ",BlockPiece "inner_body" [StaticPiece " Hello: ",VarPiece "hello"],StaticPiece " "]]
8
Right [BlockPiece "body" [StaticPiece " {} ",VarPiece "cool",StaticPiece " ",BlockPiece "inner_body" [StaticPiece " Hello: ",VarPiece "hello"]],StaticPiece " ldsakjf"]
">>"
Right (BlockPiece "body" [])
person AndrewC    schedule 21.02.2014
comment
Большое спасибо за подробное объяснение. Пока я не освоился с Parsec, его было сложно визуализировать и отлаживать. Лучше, чем мое решение ниже. - person DantheMan; 22.02.2014

Думаю, я понял это.

Я изменил код так, чтобы parseBlock потреблял {% endblock %}, а не parseStatic.

parseBlockContent end = 
  manyTill (parsePiece (lookAhead $ try $ end)) (try $ end)

parseBlock = do
  blockName <- parseTemplateTag (string "block") wordString <?> "block tag"
  blockContent <- parseBlockContent (void $ string "{% endblock %}")
  return $ BlockPiece blockName blockContent

Было бы неплохо иметь его, чтобы ему не нужно было так много возвращаться назад, тем более что parseStatic должен потреблять целое {% block %} {% endblock %}, чтобы сказать, следует ли продолжать.

person DantheMan    schedule 21.02.2014