【发布时间】:2013-08-17 23:27:50
【问题描述】:
问题
今天遇到一个问题,不知道怎么解决。这对我来说很奇怪,因为我写的代码应该(根据我目前的知识)是正确的。
因此,您可以在下面找到示例解析器组合器。最重要的是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)
根据:Combinator Parsing: A Short Tutorial
并且用于定义运算符的优先级。
【问题讨论】:
-
我没有很好的解决方案,但您的描述似乎正是正在发生的事情。如果我定义
let pOperator = applyAll <$> pElement <*> (pMany (flip <$> (Multi <$ pSome pWSpace) <*> pElement) <|> pure [])我会得到预期的结果,所以pMany在匹配空格后似乎正在提交另一个匹配。 -
@JohnL:这很奇怪。请注意,将
pProgram = pParens pOperator替换为pProgram = pParens pElement会得到很好的结果(当然也不能解决问题),但它表明pMany可以按预期工作 - 不会消耗任何元素。 -
@JohnL: 另外这个问题不能用
... <|> pure []解决,因为它只适用于1个字符输入,例如(x x )会失败。 -
好的,这个问题“理论上可以解决”,方法是用自定义组合器替换库
pMany并替换很多库函数(如pChainl等以使用我们自定义的pMany组合器)。这当然是丑陋的解决方案,但到目前为止它仍然有效。我很想看到合适的。自定义pMany可以声明如下:pMany p = (:) <$> p <*> pMany p <|> pure [] -
再次解决,但 pMany 可以替换为 pList_ng 吗?
标签: parsing haskell parsec parser-combinators uu-parsinglib