用Haskell解析报价之间

时间:2018-09-13 18:31:30

标签: parsing haskell parsec megaparsec

需求取自DOT language specification,更准确地说,我正在尝试解析[ID]属性,例如,

  

任何可能包含转义引号(\“)1的双引号字符串(” ...“);

以下应该是一个最小的示例。

{-# LANGUAGE OverloadedStrings #-}
module Main where

import           Text.Megaparsec
import           Text.Megaparsec.Char
import           Data.Void
import           Data.Char
import           Data.Text               hiding ( map
                                        , all
                                        , concat
                                        )

type Parser = Parsec Void Text

escape :: Parser String
escape = do
    d <- char '\\'
    c <- oneOf ['\\', '\"', '0', 'n', 'r', 'v', 't', 'b', 'f']
    return [d, c]

nonEscape :: Parser Char
nonEscape = noneOf ['\\', '\"', '\0', '\n', '\r', '\v', '\t', '\b', '\f']

identPQuoted :: Parser String
identPQuoted =
    let inner = fmap return (try nonEscape) <|> escape
    in  do
      char '"'
      strings <- many inner
      char '"'
      return $ concat strings

identP :: Parser Text
identP = identPQuoted >>= return . pack

main = parseTest identP "\"foo \"bar\""

上面的代码第二次失败,并返回"foo ",即使我想要foo "bar

我不明白为什么。我认为megaparsec将反复应用inner直到解析最后的"。但是它仅重复应用nonEscape解析器,并且第一次失败,并且使用escape,然后它似乎跳过了内部字符串的其余部分,而直接移至最终引号。

1 个答案:

答案 0 :(得分:6)

您输入的文本为"foo "bar",其中不包含任何转义的引号。它被解析为"foo "的完整ID(后跟bar",将被忽略)。

如果要确保解析器使用所有可用输入,可以使用

parseTest (identP <* eof) "..."

如果要向解析器提供带有转义引号的ID,例如...

"foo \"bar"

...那么您需要转义所有特殊字符以将它们嵌入Haskell源代码:

main = parseTest identP "\"foo \\\"bar\""

\"代表文字",而\\代表文字\