【问题标题】:Parser for Quoted string using Parsec使用 Parsec 的带引号字符串的解析器
【发布时间】:2014-06-08 12:47:58
【问题描述】:

我想解析这样的输入字符串:"this is \"test \" message \"sample\" text"

现在,我编写了一个解析器,用于解析不带引号的单个文本:

parseString :: Parser String
parseString = do
  char '"'
  x <- (many $ noneOf "\"")
  char '"'
  return x

这会解析像这样的简单字符串:"test message"

然后我为带引号的字符串写了一个解析器:

quotedString :: Parser String
quotedString = do
  initial <- string "\\\""
  x <- many $ noneOf "\\\"" 
  end <- string "\\\""
  return $ initial ++ x ++ end

这样的字符串解析器:\"test message\"

有没有一种方法可以将两个解析器结合起来,从而获得我想要的目标?解决这个问题的具体方法是什么?

【问题讨论】:

  • 为什么要去掉首尾引号,而保留转义的反斜杠?我认为您希望将输入 "\"ab\\\"c\"" 解析为 "\"ab\\\"c\""(严格解析以进行验证)或 "ab\"c",但您似乎想要 "ab\\\"c",这似乎并不那么明显有用。
  • @dfeuer 没有什么特别的原因,只是在玩 Parsec。

标签: parsing haskell parsec


【解决方案1】:

这就是我要做的:

escape :: Parser String
escape = do
    d <- char '\\'
    c <- oneOf "\\\"0nrvtbf" -- all the characters which can be escaped
    return [d, c]

nonEscape :: Parser Char
nonEscape = noneOf "\\\"\0\n\r\v\t\b\f"

character :: Parser String
character = fmap return nonEscape <|> escape

parseString :: Parser String
parseString = do
    char '"'
    strings <- many character
    char '"'
    return $ concat strings

现在你需要做的就是调用它:

parse parseString "test" "\"this is \\\"test \\\" message \\\"sample\\\" text\""

解析器组合器起初有点难以理解,但一旦你掌握了它,它们就会比编写 BNF 语法更容易。

【讨论】:

  • 不应该 nonEscape 只是 noneOf "\\\"",允许特殊字符按字面意思出现,同时可能大大加快处理速度?
  • @dfeuer 我想他只是添加了一些额外的字符来演示它,以防我想添加它们。 :)
  • @Sibi,我的意思是最好将额外的转义符放入escape 的定义中而不将它们从nonEscape 中排除。 nonEscape 显然必须排除的唯一事物是 '\"''\\'
  • @dfeuer 在 7 个特殊字符(即[0nrvtbf])中,无法在键盘上正常键入空字符。必须复制粘贴。 Haskell 字符串(或大多数编程语言)中不允许使用换行符 \n\r。某些语言(如 JavaScript)允许转义换行符,但 Haskell 不允许转义。垂直制表符和换页几乎从未在编程中使用过。空格(空格和换行符除外)永远不应被忽视。这足以禁止所有这些特殊字符作为字符串中的文字。这也是一个很好的解释
  • 由于解析器组合器的实现方式,noneEscape = "\\\"\0\n\r\v\t\b\f"nonEscape = "\\\"" 都没有区别。它仍然需要相同的时间。这是因为解析器是使用一组查找表来实现的,这些查找表确定了解析器的下一个状态。想想确定性下推自动机。对于给定的状态,您有一个将每个输入符号映射到新状态的向量。因此,无论输入符号是什么,只需一次查找即可到达新状态。 noneOf 组合器产生一个只有一层深的解析器。快。
【解决方案2】:
quotedString = do
    char '"'
    x <- many (noneOf "\"" <|> (char '\\' >> char '\"'))
    char '"'
    return x

我相信,这应该可行。

【讨论】:

  • 这将在结果中包含 \。 "\"" will be parsed to \"` 不是"
  • @Banthar,这似乎是 OP 的意图。但是,该代码似乎不像 Aadit M. Shah 的答案那样灵活,而且看起来很难扩展它以支持转义反斜杠。
【解决方案3】:

如果有人正在寻找更开箱即用的解决方案,this answer in code-review 正好提供。这是一个正确导入的完整示例:

import           Text.Parsec
import           Text.Parsec.Language
import           Text.Parsec.Token

lexer :: GenTokenParser String u Identity
lexer = makeTokenParser haskellDef

strParser :: Parser String
strParser = stringLiteral lexer

parseString :: String -> Either ParseError String
parseString = parse strParser ""

【讨论】:

    【解决方案4】:

    我想解析带引号的字符串并删除在解析步骤中用于转义的任何反斜杠。在我的简单语言中,唯一可转义的字符是双引号和反斜杠。这是我的解决方案:

    quotedString = do
      string <- between (char '"') (char '"') (many quotedStringChar)
      return string
      where
        quotedStringChar = escapedChar <|> normalChar
        escapedChar = (char '\\') *> (oneOf ['\\', '"'])
        normalChar = noneOf "\""
    

    【讨论】:

    • "do x
    【解决方案5】:

    我更喜欢以下内容,因为它更易于阅读:

    quotedString :: Parser String
    quotedString = do
        a <- string "\""
        b <- concat <$> many quotedChar
        c <- string "\""
        -- return (a ++ b ++ c) -- if you want to preserve the quotes
        return b
        where quotedChar = try (string "\\\\")
                       <|> try (string "\\\"")
                       <|> ((noneOf "\"\n") >>= \x -> return [x] )
    

    Aadit 的解决方案可能更快,因为它不使用try,但它可能更难阅读。

    请注意,它与 Aadit 的解决方案不同。我的解决方案忽略了字符串中的转义内容,只关心\"\\

    例如,假设您的字符串中有一个制表符。 我的解决方案成功地将"\"\t\"" 解析为Right "\t"。 Aadit 的解决方案是 unexpected "\t" expecting "\\" or "\""

    另请注意,Aadit 的解决方案仅接受“有效”转义。例如,它拒绝"\"\\a\""\a 不是有效的转义序列(根据man ascii,它代表系统铃声并且有效)。我的解决方案只返回Right "\\a"

    所以我们有两个不同的用例。

    • 我的解决方案:用可能转义的引号和转义的转义符解析带引号的字符串

    • Aadit 的解决方案:使用有效转义序列解析带引号的字符串,其中有效转义表示 "\\\"\0\n\r\v\t\b\f"

    【讨论】:

      【解决方案6】:

      详细说明@Priyatham 的回应

      pEscString::Char->Parser String
      pEscString e= do
        char e;
        s<-many (
          do{char '\\';c<-anyChar;return ['\\',c]}
          <|>many1 (noneOf (e:"\\")))
        char e
        return$concat s
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多