Правильно разбирать отступы строки в uu-parsinglib в Haskell

Я хочу создать комбинатор синтаксического анализатора, который будет собирать все строки ниже текущего места, уровни отступов которых будут больше или равны некоторым i, Я думаю, что идея проста:

Поглотить строку - если ее отступ равен:

  • хорошо -> сделать это для следующих строк
  • неправильно -> потерпеть неудачу

Давайте рассмотрим следующий код:

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 ()

Который дает следующий вывод:

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

Результат для s1 верно. Результат для s2 должен потреблять сначала "а" и перестать потреблять. Откуда эта ошибка?

1 ответ

Парсеры, которые вы создаете, всегда будут пытаться продолжить; при необходимости ввод будет отброшен или добавлен. тем не мение pFail это тупик. Он действует как единичный элемент для <|>,

Однако в вашем парсере нет другой альтернативы, если входные данные не соответствуют языку, распознаваемому парсером. В вашей спецификации вы говорите, что хотите, чтобы парсер не работал на входе s2. Теперь это не с сообщением о том, что это не так, и вы удивлены.

Может быть, вы не хотите, чтобы это потерпело неудачу, но вы хотите прекратить принимать дальнейшие вводные данные? В этом случае замените pFail от return [],

Обратите внимание, что текст:

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

можно заменить на (expr:) <$> (myParse p i `opt` [])

Естественный способ решить вашу проблему, вероятно, что-то вроде

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

pIndent = length <$> pMany (pSym ' ')
Другие вопросы по тегам