我试图了解many
Haskell 的 Megaparsec 库中的行为。我本以为many
当输入解析器失败时会返回一个空列表,但事实似乎并非如此。
特别是,我对以下最少的代码感到困惑:
import Text.Megaparsec (Parsec, many, parseTest)
import Text.Megaparsec.Char (string)
import Data.Void
test :: Parsec Void String [String] -> String -> IO ()
test = parseTest
main = do
test (many (string "a" >> string "b")) "ac" -- error
test (many (string "a" <> string "b")) "ac" -- error
test (many (string "ab")) "ac" -- []
其输出如下:
1:2:
|
1 | ac
| ^
unexpected 'c'
expecting 'b'
1:2:
|
1 | ac
| ^
unexpected 'c'
expecting 'b'
[]
我不明白为什么前两个测试失败而第三个测试失败。
string "a" >> string "b"
解析器和解析器将解析的内容没有区别string "a" <> string "b"
,只是解析成功时返回的内容不同("b"
第一个与"ab"
第二个),因此前两种情况的行为相同(即都失败)也就不足为奇了)。它们失败的原因是解析器
many p
运行解析器p
直到失败。如果 的失败p
不消耗 input,则成功并返回解析器在失败之前many p
成功运行的结果列表。如果消耗 inputp
失败,则失败,其“原因”与 parser 最终失败相同。p
many p
p
当解析器
string "a" >> string "b"
在输入上运行时"ac"
,string "a"
解析器成功解析"a"
,然后string "b"
解析器解析失败"c"
,而不消耗输入。这会导致整个解析器string "a" >> string "b"
失败,并消耗输入( 消耗的输入string "a"
,即使导致失败的解析string "b"
本身没有消耗任何输入)。将它们放在一起,然后
many (string "a" >> string "b")
运行解析器string "a" >> string "b"
直到失败,这种情况发生在第一个应用程序上,原因是解析器string "b"
期望 a"b"
但得到了"c"
. 因为此失败消耗了输入("a"
消耗的string "a"
),所以整个解析器many (string "a" >> string "b")
因相同的“原因”而失败。解析器
string "ab"
有点不同,并且从 4.4.0 开始,Parsec 和 Megaparsec 版本之间的行为有所不同(请参阅 的文档tokens
)。在秒差距中,
string "ab"
应用于输入的解析器"ac"
消耗"a"
,然后在 上失败,因此在消耗输入后"c"
失败。这可能是一个坏主意,这就是为什么 Parsec在 3.1.16.0 版本中引入的原因。解析器做了更明智的事情:它要么消耗全部,要么失败而不消耗任何东西。特别是,当它应用于输入并且无法消耗时,它会在不消耗输入的情况下失败。string'
string' "ab"
"ab"
"ac"
"ab"
在 Megaparsec 中,
string
最初的行为类似于 Parsec 的string
,但是当他们修复了这个设计缺陷时,string'
他们没有引入新的组合器,而是对 进行了不兼容的更改string
,因此string "ab"
在 Megaparsec 中,就像string' "ab"
在 Parsec 中一样,要么消耗全部"ab"
,要么失败而不消耗任何东西。(更令人困惑的是,Megaparsec 也有一个
string'
组合器,但它是 的不区分大小写的版本string
,因此与 Parsec 的版本无关string'
。)