如何从单词列表中创建 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,我仍然更喜欢上面的解决方案,但这是修复最后一段代码的方法:
- 正如编译器应该告诉你的那样,模式
(w:ws)
和 [w]
重叠,所以如果你想要 运行 时间捕捉单元素模式,你必须将它放置在上面。
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 文件对你来说是可能的,那么请记住这一点。
我是 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,我仍然更喜欢上面的解决方案,但这是修复最后一段代码的方法:
- 正如编译器应该告诉你的那样,模式
(w:ws)
和[w]
重叠,所以如果你想要 运行 时间捕捉单元素模式,你必须将它放置在上面。 a >> b
表示"run actiona
, discard its result, then run actionb
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 文件对你来说是可能的,那么请记住这一点。