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 分别转换为 MaybeEither 来强制执行此操作。

该解决方案适用于 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 实现。