如何从单词列表中创建 Haskell 解析器?

How can I make a Haskell parser from a list of words?

我是 Haskell 初学者,使用 Attoparsec 在文本中查找一些颜色表达式。例如,我希望能够匹配文本中的 "light blue-green" 和 "light blue green"。但当然我需要一个通用的解决方案来处理任何类似的字符串。所以我一直在想它会像

"light" >> sep >> "blue" >> sep >> "green"
  where sep = inClass "\n\r- "

换句话说,我想我需要一种方法来将 >> sep >> 插入到单词列表中。类似于:

import qualified Data.Text as T
import Data.Attoparsec.Text

-- | Makes a parser from a list of words, accepting
-- spaces, newlines, and hyphens as separators.
wordListParser :: [T.Text] -> Parser
wordListParser wordList = -- Some magic here

或者我完全以错误的方式思考这个问题,有更简单的方法吗?

编辑:这个最小的非工作示例感觉差不多了:

{-# LANGUAGE OverloadedStrings #-}

import Replace.Attoparsec.Text
import Data.Attoparsec.Text as AT
import qualified Data.Text as T
import Control.Applicative (empty)

wordListParser :: [T.Text] -> Parser T.Text
wordListParser (w:ws) = string w >> satisfy (inClass " -") >> wordListParser ws
wordListParser [w] = string w
wordListParser [] = empty  -- or whatever the empty parser is

main :: IO ()
main = parseTest (wordListParser (T.words "light green blue")) "light green-blue"

我认为可以 运行 加上

stack runhaskell ThisFile.hs --package attoparsec replace-attoparsec text

我不熟悉 attoparsec,但您可以使用递归解决方案:

wordListParser :: [T.Text] -> Parser
wordListParser [] = empty
wordListParser [w] = text w
wordListParser (w:ws) = text w >> inClass "\n\r- " >> wordListParser ws

假设您有颜色的数据类型,这就是我要做的;如果你不这样做,只需用它代替你正在使用的东西。函数 parseColourGen 接受任何 space 分隔的 Text,并生成一个接受颜色的解析器,其中每个单词由一个或多个合法分隔符分隔。

import Prelude hiding (concat, words)
import Control.Applicative ((<|>))
import Data.Attoparsec.Text
import Data.List (intersperse)
import Data.Text (concat, pack, singleton, Text,  words)

data Colour = LightBlue | DarkBlue | VibrantRed deriving Show

parseColourGen :: Text -> Parser [Text]
parseColourGen = sequence . intersperse (mempty <$ many1 legalSep) . 
                   fmap string . words

parseColour :: [(Text, Colour)] -> Parser Colour
parseColour = foldl1 (<|>) . fmap (\(text, colour) ->
  colour <$ parseColourGen text)

legalSep :: Parser Text
legalSep = singleton <$> satisfy (inClass "\n\r- ")

然后您可以将 wordList 提供给解析器;但是,它需要是一个关联列表:

wordList :: [(Text, Colour)]
wordList = [("light blue", LightBlue), ("dark blue", DarkBlue), ("vibrant red", VibrantRed)]

这样,您可以在一个地方配置所有颜色及其对应的颜色名称,然后您可以 运行 解析器,如下所示:

> parse (parseColour wordList) $ pack "vibrant-red"
Done "" VibrantRed

编辑

在对你的问题进行编辑之后,我想我对你想要的东西有了更好的理解。 FWIW,我仍然更喜欢上面的解决方案,但这是修复最后一段代码的方法:

  1. 正如编译器应该告诉你的那样,模式 (w:ws)[w] 重叠,所以如果你想要 运行 时间捕捉单元素模式,你必须将它放置在上面。
  2. a >> b表示"run action a, discard its result, then run action b and use that result"。这就是为什么您的解析器(通过上述修复)将输出 Done "" "blue"。解决这个问题的一个简单方法是使用 do 符号来绑定所有三个计算的结果,以及 return 它们的连接。

这是您的代码现在的样子:

wordListParser :: [Text] -> Parser Text
wordListParser [w] = string w
wordListParser (w:ws) = do
  a <- string w
  b <- satisfy (inClass " -")
  c <- wordListParser ws
  return (a `append` (singleton b) `append` c) -- singleton :: Char -> Text
wordListParser [] = empty

最后一件事:您当前的实现不会解析 Windows 换行符 (\n\r)。我不知道你是否从你的分隔符中删除了 \n\r,但如果你没有并且 Windows 文件对你来说是可能的,那么请记住这一点。