Haskell 为什么Parsec选择运算符似乎取决于解析器的顺序?

Haskell 为什么Parsec选择运算符似乎取决于解析器的顺序?,haskell,parsec,Haskell,Parsec,我试图解析一种非常简单的语言,它只包含十进制数或二进制数。例如,以下是一些有效的输入: #b1 #d1 #b0101 #d1234 我在使用Parsec的choice运算符时遇到问题:。根据教程:: [选择运算符]尝试第一个解析器,如果失败,则尝试第二个解析器。如果其中一个成功,则返回该解析器返回的值 但根据我的经验,我发现解析器的顺序很重要。这是我的节目: import System.Environment import Text.ParserCombinators.Parsec main

我试图解析一种非常简单的语言,它只包含十进制数或二进制数。例如,以下是一些有效的输入:

#b1
#d1
#b0101
#d1234
我在使用Parsec的choice运算符时遇到问题:
。根据教程::

[选择运算符]尝试第一个解析器,如果失败,则尝试第二个解析器。如果其中一个成功,则返回该解析器返回的值

但根据我的经验,我发现解析器的顺序很重要。这是我的节目:

import System.Environment
import Text.ParserCombinators.Parsec

main :: IO ()
main = do
  (x:_) <- getArgs 
  putStrLn ( "Hello, " ++ readExp x)

bin :: Parser String
bin = do string "#b"
         x <- many( oneOf "01")
         return x

dec :: Parser String
dec = do string "#d"
         x <- many( oneOf "0123456789")
         return x

-- Why does order matter here?
parseExp = (bin <|> dec) 

readExp :: String -> String
readExp input = case parse parseExp "test" input of
                      Left error -> "Error: " ++ show error
                      Right val  -> "Found val" ++ show val 
编撰 跑 如果我按如下方式更改解析器的顺序:

parseExp = (dec <|> bin) 
parseExp=(dec-bin)
然后只检测到二进制数,程序无法识别十进制数

通过我执行的测试,我发现只有当其中一个解析器开始解析输入时,才会出现此问题,例如,如果找到哈希字符
#
,则bin解析器被激活,最终失败,因为预期的下一个字符是
b
,而不是
d
。似乎应该有某种回溯发生,我不知道

谢谢你的帮助

仔细阅读:

[选择运算符]尝试第一个解析器,如果失败,则尝试 第二个。如果其中一个成功,则返回 那个解析器

这意味着首先尝试第一个解析器,如果成功,则根本不尝试第二个解析器!这意味着第一个解析器具有更高的优先级,因此通常
是不可交换的

一个简单的反例可以用一些总是成功的解析器来制作,例如

dummy :: Parser Bool
dummy = return True <|> return False
不会像人们预期的那样起作用。只要第一个分支成功地使用了
'a'
,parsec就会对其进行提交。这意味着,如果
'c'
随后出现,那么第一个分支将失败,但第二个分支尝试已经太晚了。整个解析器完全失败了

为了解决这个问题,我们可以去掉公共前缀,例如

p = char 'a' >> ( (char 'b' >> ...) <|> (char 'c' >> ...) )
try
基本上告诉parsec在
try
下的整个解析器成功之前不要提交到分支。如果被滥用,它可能会导致parsec将文件的很大一部分保存在内存中,但用户至少可以对此进行一些控制。理论上,完全的不确定性可以通过总是在
的整个左分支上添加
try
来恢复。但是,不建议这样做,因为这会促使程序员编写一个效率低下的解析器,这既有内存问题,也有一个事实,即可以很容易地编写一个语法,需要指数数量的回溯才能成功解析。

Parsec有两种“失败”:有消耗输入的失败,还有那些没有成功的失败。为了避免回溯(因此保留输入的时间比需要的时间长/通常对垃圾收集器不友好),
()
在第一个解析器使用输入时立即提交给第一个解析器;所以,如果它的第一个参数消耗输入并失败,那么它的第二个解析器就永远不会成功。您可以使用
try
显式请求回溯行为,因此:

Text.Parsec> parse (string "ab" <|> string "ac") "" "ac"
Left (line 1, column 1):
unexpected "c"
expecting "ab"
Text.Parsec> parse (try (string "ab") <|> string "ac") "" "ac"
Right "ac"

在您的例子中,您需要同样地排除“#”标记。

但是在我的例子中,第一个解析器没有成功。它在第一个字符匹配后被激活,但之后由于找不到下一个预期字符而失败。我希望下一个解析器应该被激活,但这并没有发生,正如我粘贴的输出所示。@mandark这是因为parsec是如何工作的。我将进行编辑。更令人惊讶的是,我看到了
attoparsec
解析器,它们的成功或失败取决于
的参数顺序。对此,我得问我自己的问题。”仔细阅读:“我认为文件的这一部分一点也不清楚。
dummy :: Parser Bool
dummy = return True <|> return False
p = (char 'a' >> char 'b' >> ...) <|>
    (char 'a' >> char 'c' >> ...)
p = char 'a' >> ( (char 'b' >> ...) <|> (char 'c' >> ...) )
p = (try (char 'a' >> char 'b') >> ...) <|>
    (char 'a' >> char 'c' >> ...)
Text.Parsec> parse (string "ab" <|> string "ac") "" "ac"
Left (line 1, column 1):
unexpected "c"
expecting "ab"
Text.Parsec> parse (try (string "ab") <|> string "ac") "" "ac"
Right "ac"
Text.Parsec> parse (char 'a' >> (("ab" <$ char 'b') <|> ("ac" <$ char 'c'))) "" "ac"
Right "ac"