Attoparsec:匹配任何具有公共前缀的字符串
Attoparsec: matching any of strings with common prefix
我正在尝试解析一组有限的有效字符串,这些字符串具有与 attoparsec 相同的前缀。但是,我的尝试导致 Partial
结果或过早的 Done
:
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import qualified Data.Attoparsec.Text as PT
data Thing = Foobar | Foobaz | Foobarz
thingParser1 = PT.string "foobarz" *> return Foobarz
<|> PT.string "foobaz" *> return Foobaz
<|> PT.string "foobar" *> return Foobar
thingParser2 = PT.string "foobar" *> return Foobar
<|> PT.string "foobaz" *> return Foobaz
<|> PT.string "foobarz" *> return Foobarz
我想要的是“foobar”产生Foobar
,“foobarz”产生Foobarz
,“foobaz”产生Foobaz
。然而
PT.parse thingParser1 "foobar"
结果为 PT.Partial
和
PT.parse thingParser2 "foobarz"
结果为 PT.Done "z" Foobar
。
如您所见,备选方案的顺序在解析器组合器库的 parsec 系列中很重要。它将首先尝试左侧的解析器,只有在失败时才继续使用右侧的解析器。
另一件需要注意的事情是您的解析器不要求输入在解析后结束。您可以通过使用 parseOnly
instead of parse
to run the actual parser. Or you can use the maybeResult
or eitherResult
函数将 Result
分别转换为 Maybe
或 Either
来强制执行此操作。
该解决方案适用于 thingParser1
,但 thingParser2
仍然无效。这是因为您需要在单个 try
下同时拥有 string
解析器和 endOfInput
,这会起作用:
thingParser3 = Foobar <$ PT.string "foobar" <* endOfInput
<|> Foobaz <$ PT.string "foobaz" <* endOfInput
<|> Foobarz <$ PT.string "foobarz" <* endOfInput
稍微好一点的方法是快速向前看,看看 z
是否跟在 foobar
之后,您可以这样做:
thingParser4 = Foobar <$ (do
PT.string "foobar"
c <- peekChar
guard (maybe True (/= 'z') c))
<|> Foobaz <$ PT.string "foobaz"
<|> Foobarz <$ PT.string "foobarz"
但是这种回溯也会降低性能,所以我会坚持使用 thingParser1
实现。
我正在尝试解析一组有限的有效字符串,这些字符串具有与 attoparsec 相同的前缀。但是,我的尝试导致 Partial
结果或过早的 Done
:
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import qualified Data.Attoparsec.Text as PT
data Thing = Foobar | Foobaz | Foobarz
thingParser1 = PT.string "foobarz" *> return Foobarz
<|> PT.string "foobaz" *> return Foobaz
<|> PT.string "foobar" *> return Foobar
thingParser2 = PT.string "foobar" *> return Foobar
<|> PT.string "foobaz" *> return Foobaz
<|> PT.string "foobarz" *> return Foobarz
我想要的是“foobar”产生Foobar
,“foobarz”产生Foobarz
,“foobaz”产生Foobaz
。然而
PT.parse thingParser1 "foobar"
结果为 PT.Partial
和
PT.parse thingParser2 "foobarz"
结果为 PT.Done "z" Foobar
。
如您所见,备选方案的顺序在解析器组合器库的 parsec 系列中很重要。它将首先尝试左侧的解析器,只有在失败时才继续使用右侧的解析器。
另一件需要注意的事情是您的解析器不要求输入在解析后结束。您可以通过使用 parseOnly
instead of parse
to run the actual parser. Or you can use the maybeResult
or eitherResult
函数将 Result
分别转换为 Maybe
或 Either
来强制执行此操作。
该解决方案适用于 thingParser1
,但 thingParser2
仍然无效。这是因为您需要在单个 try
下同时拥有 string
解析器和 endOfInput
,这会起作用:
thingParser3 = Foobar <$ PT.string "foobar" <* endOfInput
<|> Foobaz <$ PT.string "foobaz" <* endOfInput
<|> Foobarz <$ PT.string "foobarz" <* endOfInput
稍微好一点的方法是快速向前看,看看 z
是否跟在 foobar
之后,您可以这样做:
thingParser4 = Foobar <$ (do
PT.string "foobar"
c <- peekChar
guard (maybe True (/= 'z') c))
<|> Foobaz <$ PT.string "foobaz"
<|> Foobarz <$ PT.string "foobarz"
但是这种回溯也会降低性能,所以我会坚持使用 thingParser1
实现。