Незапланированное жадное поведение в uu-parsinglib

Проблема

Сегодня столкнулся с проблемой и не знаю как решить. Это очень странно для меня, потому что код, который я написал, должен (согласно моим текущим знаниям) быть правильным.

Итак, ниже вы можете найти примеры комбинаторов парсера. Наиболее важным из них является pOperator, который очень простым способом (только для демонстрационных целей) строит оператор AST. Он использует «x» и может использовать несколько «x», разделенных пробелами.

У меня также есть комбинатор pParens, который определяется как:

pPacked pParenL (pWSpaces *> pParenR)

поэтому он использует пробелы перед закрытием скобки.

Пример ввода/вывода

Правильный ввод/вывод ДОЛЖЕН быть:

in: "(x)"
out: Single "x"

in: "(x )"
out: Single "x"

но я получаю:

in: "(x)"
out: Single "x"

in: "(x )" 
out: Multi (Single "x") (Single "x")
--  Correcting steps: 
--    Inserted  'x' at position LineColPos 0 3 3 expecting one of ['\t', ' ', 'x']

но во втором примере я получаю ошибку - и парсер ведет себя так, как будто он жадно ест какие-то токены (и жадной операции нет).

Я был бы благодарен за любую помощь в этом.

Пример кода

import Prelude hiding(lex)
import Data.Char hiding (Space)
import qualified Text.ParserCombinators.UU as UU
import           Text.ParserCombinators.UU hiding(parse)
import qualified Text.ParserCombinators.UU.Utils as Utils
import           Text.ParserCombinators.UU.BasicInstances hiding (Parser)


data El = Multi El El
        | Single String
        deriving (Show)


---------- Example core grammar ----------

pElement     = Single <$> pSyms "x"
pOperator    = applyAll <$> pElement <*> pMany (flip <$> (Multi <$ pWSpaces1) <*> pElement)

---------- Basic combinators ----------

applyAll x (f:fs) = applyAll (f x) fs
applyAll x []     = x

pSpace    = pSym ' '
pTab      = pSym '\t'
pWSpace   = pSpace <|> pTab
pWSpaces  = pMany pWSpace
pWSpaces1 = pMany1 pWSpace
pMany1 p  = (:) <$> p <*> pMany p

pSyms []       = pReturn []
pSyms (x : xs) = (:) <$> pSym x <*> pSyms xs

pParenL     = Utils.lexeme $ pSym '('
pParenR     = Utils.lexeme $ pSym ')'
pParens     = pPacked pParenL (pWSpaces *> pParenR)

---------- Program ----------

pProgram = pParens pOperator
-- if you replace it with following line, it works:
--  pProgram = pParens pElement
-- so it seems like something in pOperator is greedy

tests = [ ("test", "(x)")
        , ("test", "(x )")
        ]

---------- Helpers ----------

type Parser a = P (Str Char String LineColPos) a

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

main :: IO ()
main = do 
    mapM_ (\(desc, p) -> putStrLn ("\n=== " ++ desc ++ " ===") >> run pProgram p) tests
    return ()

run :: Show t =>  Parser t -> String -> IO ()
run p inp = do  let (a, errors) =  parse p inp
                putStrLn ("--  Result: \n" ++ show a)
                if null errors then  return ()
                               else  do putStr ("--  Correcting steps: \n")
                                        show_errors errors
                putStrLn "-- "
             where show_errors :: (Show a) => [a] -> IO ()
                   show_errors = sequence_ . (map (putStrLn . show))

ВАЖНО

pOperator    = applyAll <$> pElement <*> pMany (flip <$> (Multi <$ pWSpaces1) <*> pElement)

эквивалентно:

foldr pChainl pElement (Multi <$ pWSpaces1)

согласно: Разбор комбинатора: краткий Учебник

И он используется для определения приоритета оператора.


person Wojciech Danilo    schedule 17.08.2013    source источник
comment
У меня нет хорошего решения, но ваше описание, кажется, именно то, что происходит. Если я определяю let pOperator = applyAll <$> pElement <*> (pMany (flip <$> (Multi <$ pSome pWSpace) <*> pElement) <|> pure []), я получаю ожидаемый результат, поэтому pMany, похоже, фиксирует другое совпадение после сопоставления пробела.   -  person John L    schedule 18.08.2013
comment
@JohnL: Это очень странно. Обратите внимание, что замена pProgram = pParens pOperator на pProgram = pParens pElement дает хороший результат (что, конечно, тоже не решает проблему), но показывает, что pMany МОЖЕТ работать как положено - не может потреблять элементы.   -  person Wojciech Danilo    schedule 18.08.2013
comment
@JohnL: дополнительная проблема не может быть решена с помощью ... <|> pure [], потому что он работает только для ввода 1 символа, например, для (x x ) он не работает.   -  person Wojciech Danilo    schedule 18.08.2013
comment
Хорошо, теоретически проблему можно решить, заменив библиотечный комбинатор pMany на пользовательский и заменив множество библиотечных функций (например, pChainl и т. д., чтобы использовать наш пользовательский комбинатор pMany). Это, конечно, уродливое решение, но оно работает до сих пор. Хотелось бы увидеть правильный. Пользовательский pMany можно объявить следующим образом: pMany p = (:) <$> p <*> pMany p <|> pure []   -  person Wojciech Danilo    schedule 18.08.2013
comment
Опять обходной путь, но можно ли заменить pMany на pList_ng?   -  person OllieB    schedule 18.08.2013
comment
@OllieB: Да, это дает точно такое же поведение, как и мой пользовательский pMany   -  person Wojciech Danilo    schedule 18.08.2013


Ответы (1)


Определение pMany гласит:

pMany :: IsParser p => p a -> p [a]
pMany p = pList p

и это предлагает решение. Увидев пробел, мы не должны сразу делать выбор, чтобы продолжить с большим количеством крестиков, поэтому мы определяем:

pMany :: IsParser p => p a -> p [a]
pMany_ng p = pList_ng p

Конечно, вы также можете немедленно вызвать pList_ng. Еще лучше было бы написать:

pParens (pChainr_ng (pMulti <$ pWSpaces1) px) -- 

Я не проверял, так как не уверен, должен ли быть между x-ами хотя бы один пробел и т. д.

Доайтсе

person Doaitse Swierstra    schedule 27.08.2013