关于解析:如何从单词列表中制作 Haskell 解析器?

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

我是 Haskell 初学者,使用 Attoparsec 在文本中查找一些颜色表达式。例如,我希望能够匹配文本中的"浅蓝绿色"和"浅蓝绿色"。但当然,我需要一个针对任何这样的字符串的通用解决方案。所以我一直在想它会像

1
2
3
4
"light">> sep >>"blue">> sep >>"green"
  where sep = inClass"\
\
-"

换句话说,我认为我需要一种将 >> sep >> 插入单词列表的方法。类似于:

1
2
3
4
5
6
7
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

或者我可能完全错误地考虑了这个问题,还有更简单的方法吗?

编辑:这个最小的非工作示例感觉就像它几乎就在那里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{-# 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"

我认为可以用

之类的东西运行

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"\
\
-"
)

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

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

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

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

编辑

在编辑您的问题后,我想我更了解您想要什么。 FWIW,我仍然更喜欢上面的解决方案,但这里是如何修复你的最后一个代码块:

  • 正如编译器应该告诉您的那样,模式 (w:ws)[w] 重叠,因此如果您希望运行时捕获单元素模式,则必须将其放在顶部。
  • a >> b 表示"运行操作 a,丢弃其结果,然后运行操作 b 并使用该结果"。这就是为什么您的解析器(带有上述修复程序)将输出 Done"""blue" 的原因。解决此问题的一种简单方法是使用 do 表示法来绑定所有三个计算的结果,并返回它们的连接。
  • 这是您的代码现在的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    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 换行符 (\
    \
    )。我不知道您是否从分隔符中删除了 \
    \
    ,但如果您没有删除并且 Windows 文件对您来说是可能的,请记住这一点。


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

    1
    2
    3
    4
    5
    6
    wordListParser :: [T.Text] -> Parser
    wordListParser [] = empty
    wordListParser [w] = text w
    wordListParser (w:ws) = text w >> inClass"\
    \
    -"
    >> wordListParser ws