如何在 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 '\'
我正在 "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 '\'