Правильно разбирать отступы строки в 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 ' ')