Parsec3引用字符串的文本解析器,引号之间允许一切

时间:2016-07-15 19:29:28

标签: string parsing haskell parsec

我之前已经问过这个问题(here)但事实证明所提供的解决方案并未处理所有测试用例。另外,我需要' Text'解析器而不是' String',所以我需要parsec3。

好的,解析器应该允许引号之间的每种类型的char,甚至是引号。引用文本的末尾标有'字符,后跟|,输入的空格或结尾。

所以,

  

' AA'''' |

应该返回一个字符串

  

AA'''

这就是我所拥有的:

import Text.Parsec
import Text.Parsec.Text


quotedLabel :: Parser Text
quotedLabel = do -- reads the first quote.
    spaces
    string "'"
    lab <-  liftM pack $ endBy1 anyChar endOfQuote
    return  lab

endOfQuote = do
    string "'"
    try(eof) <|> try( oneOf "| ")

现在,问题在于eof的类型与oneOf "| "的类型不同,因此编译会失败。

我该如何解决这个问题?有没有更好的方法来实现我想要做的事情?

2 个答案:

答案 0 :(得分:2)

要更改任何仿函数计算的结果,您可以使用:

fmap (const x) functor_comp

e.g:

getLine :: IO String
fmap (const ()) getLine :: IO ()

eof :: Parser ()
oneOf "| "  :: Parser Char

fmap (const ()) (oneOf "| ") :: Parser ()

另一种选择是使用Control.Applicative中的运算符:

getLine *> return 3  :: IO Integer

执行getLine,丢弃结果并返回3.

在您的情况下,您可以使用:

try(eof) <|> try( oneOf "| " *> return ())

答案 1 :(得分:2)

<强>空白

首先评论处理空白区域......

通常的做法是编写解析器以便它们 消耗令牌后面的空格 或句法单位。定义组合器是很常见的:

lexeme p = p <* spaces

轻松将解析器 p 转换为丢弃空格的解析器 遵循 p 分析的任何内容。例如,如果你有

number = many1 digit

只要你想吃掉它就可以使用lexeme number 数字后面的空格。

有关处理空白和其他建议的更多方法 解析语言时,请参阅this Megaparsec tutorial

标签表达

根据your previous SO question显示您想要的内容 解析表单的表达式:

label1 | label2 | ... | labeln

其中每个标签可以是简单标签或带引号的标签。

解析此模式的惯用方法是使用sepBy,如下所示:

labels :: Parser String
labels = sepBy1 (try quotedLabel <|> simpleLabel) (char '|')

我们定义了simpleLabel和quotedLabel 它们中可能出现什么字符。对于simpleLabel有效 字符是非|和非空间:

simpleLabel :: Parser String
simpleLabel = many (noneOf "| ")

quotedLabel是单引号,后跟运行 有效的quotedLabel字符后跟一个结尾 单引号:

sq = char '\''

quotedLabel :: Parser String
quotedLabel = do
  char sq
  chs <- many validChar
  char sq
  return chs

validChar是非单引号或单引号 引号后面没有eof或竖条:

validChar = noneOf [sq] <|> try validQuote

validQuote = do
  char sq
  notFollowedBy eof
  notFollowedBy (char '|')
  return sq

如果仅出现单引号,则第一个notFollowedBy将失败 在输入结束之前。如果第二个notFollowedBy将失败 下一个字符是垂直条。因此两者的顺序 只有在跟随非垂直条形字符时才会成功 单引号。在这种情况下,应该解释单引号 作为字符串的一部分而不是终止单引号。

不幸的是,这并不是很有效,因为 notFollowedBy的当前实施 将永远成功使用一个不消耗任何东西的解析器 输入 - 即像eof。 (有关详细信息,请参阅this issue。)

要解决此问题,我们可以使用此替代方案 实现:

notFollowedBy' :: (Stream s m t, Show a) => ParsecT s u m a -> ParsecT s u m ()
notFollowedBy' p = try $ join $
      do {a <- try p; return (unexpected (show a));}
  <|> return (return ())

这是完整的解决方案,包含一些测试。添加一些lexeme 调用你可以让这个解析器占用你决定的任何空白区域 它并不重要。

import Text.Parsec hiding (labels)
import Text.Parsec.String
import Control.Monad

notFollowedBy' :: (Stream s m t, Show a) => ParsecT s u m a -> ParsecT s u m ()
notFollowedBy' p = try $ join $
      do {a <- try p; return (unexpected (show a));}
  <|> return (return ())

sq = '\''

validChar = do
  noneOf "'" <|> try validQuote

validQuote = do
  char sq
  notFollowedBy' eof
  notFollowedBy (char '|')
  return sq

quotedLabel :: Parser String
quotedLabel = do
  char sq
  str <- many validChar
  char sq
  return str

plainLabel :: Parser String
plainLabel = many (noneOf "| ")

labels :: Parser [String]
labels = sepBy1 (try quotedLabel <|> try plainLabel) (char '|')

test input expected = do
  case parse (labels <* eof) "" input of
    Left e -> putStrLn $ "error: " ++ show e
    Right v -> if v == expected
                 then putStrLn $ "OK - got: " ++ show v
                 else putStrLn $ "NOT OK - got: " ++ show v ++ "  expected: " ++ show expected

test1 = test "a|b|c"      ["a","b","c"]
test2 = test "a|'b b'|c"  ["a", "b b", "c"]
test3 = test "'abc''|def" ["abc'", "def" ]
test4 = test "'abc'"      ["abc"]
test5 = test "x|'abc'"    ["x","abc"]