如何在 Parsec ParserT monad 中表达解析逻辑

How to express parsing logic in Parsec ParserT monad

我正在 "Write Yourself a Scheme in 48 hours" 学习 Haskell,但我 运行 遇到了一个我并不真正理解的问题。这是 this section.

底部练习中的问题 2

任务是改写

import Text.ParserCombinators.Parsec
parseString :: Parser LispVal
parseString = do
                char '"'
                x <- many (noneOf "\"")
                char '"'
                return $ String x

这样正确转义的引号(例如在 "This sentence \" 中是无意义的)被解析器接受。

在命令式语言中,我可能会写这样的东西(大致是 pythonic 伪代码):

def parseString(input): 
  if input[0] != "\"" or input[len(input)-1] != "\"":
    return error
  input = input[1:len(input) - 1] # slice off quotation marks  
  output = "" # This is the 'zero' that accumulates over the following loop
  # If there is a '"' in our string we want to make sure the previous char
  # was '\'  

  for n in range(len(input)):
    if input[n] == "\"":
      try:
        if input[n - 1] != "\":
          return error
      catch IndexOutOfBoundsError:
        return error
    output += input[n]
  return output

我一直在查看 docs for Parsec,但我只是想不出如何将它作为一个单子表达式来使用。

我做到了:

parseString :: Parser LispVal
parseString = do
                char '"'
                regular <- try $ many (noneOf "\"\")
                quote <- string "\\""
                char '"'
                return $ String $ regular ++ quote

但这只适用于一个引号,而且它必须位于字符串的末尾——我想不出一个函数表达式来完成我的循环和 if 语句在命令式伪代码。

感谢您花时间阅读本文并给我建议。

解决方案是将字符串文字定义为起始引号 + 许多有效字符 + 结束引号,其中 "valid character" 是转义序列或非引号。

所以有一行更改为 parseString:

parseString = do char '"'
                 x <- many validChar
                 char '"'
                 return $ String x

然后我们添加定义:

validChar = try escapeSequence <|> satisfy ( /= '"' )
escapeSequence = do { char '\'; anyChar }

escapeSequence 可以改进以允许一组有限的转义序列。

尝试这样的事情:

dq :: Char
dq = '"'

parseString :: Parser Val
parseString = do
  _ <- char dq
  x <- many ((char '\' >> escapes) <|> noneOf [dq])
  _ <- char dq
  return $ String x
    where
      escapes = dq <$ char dq
            <|> '\n' <$ char 'n'
            <|> '\r' <$ char 'r'
            <|> '\t' <$ char 't'
            <|> '\' <$ char '\'