Parsec:处理重叠的解析器

时间:2017-04-20 20:09:25

标签: parsing haskell parsec

我真的很想在Haskell中解析,但它最有意义。

我正在努力构建模板程序,主要是为了更好地学习解析;模板可以通过{{ value }}表示法插入值。

这是我当前的解析器,

data Template a = Template [Either String a]
data Directive = Directive String

templateFromFile :: FilePath -> IO (Either ParseError (Template Directive))
templateFromFile = parseFromFile templateParser

templateParser :: Parser (Template Directive)
templateParser = do
  tmp <- template
  eof
  return tmp

template :: Parser (Template Directive)
template = Template <$> many (dir <|> txt)
    where
      dir = Right <$> directive
      txt = Left <$> many1 (anyChar <* notFollowedBy directive)

directive :: Parser Directive
directive = do
  _ <- string "{{"
  txt <- manyTill anyChar (string "}}")
  return $ Directive txt

然后我在一个像这样的文件上运行它:

{{ value }}

This is normal Text

{{ expression }}

当我使用templateFromFile "./template.txt"运行时,我收到错误:

Left "./template.txt" (line 5, column 17):
unexpected Directive " expression "

为什么会发生这种情况,我该如何解决?

我的基本理解是many1 (anyChar <* notFollowedBy directive) 应该抓住所有字符直到下一个指令的开始,然后应该失败并返回字符列表直到那一点;然后 它应该回退到之前的many并且应该尝试再次解析dir并且应该成功;显然,其他事情正在发生。我 难以弄清楚如何解析之间之间的其他事情 解析器大多重叠。

我喜欢一些关于如何更具特色的结构的提示,如果我以愚蠢的方式做某事,请告诉我。干杯!谢谢你的时间!

3 个答案:

答案 0 :(得分:2)

你有几个问题。首先,在Parsec中,如果解析器消耗任何输入然后失败,那就是错误。所以,当解析器:

anyChar <* notFollowedBy directive

失败(因为字符后跟一个指令),它在 anyChar消耗输入后失败,并立即生成错误。因此,解析器:

let p1 = many1 (anyChar <* notFollowedBy directive)
如果它遇到指令,

永远不会成功。例如:

parse p1 "" "okay"   -- works
parse p1 "" "oops {{}}"  -- will fail after consuming "oops "

您可以通过插入try子句来解决此问题:

let p2 = many1 (try (anyChar <* notFollowedBy directive))
parse p2 "" "okay {{}}"

产生Right "okay"并揭示第二个问题。解析器p2仅使用未遵循指令的字符,因此排除指令之前的空格,并且您的解析器中无法使用 所遵循的字符通过指令,它会被卡住。

你真的想要这样的东西:

let p3 = many1 (notFollowedBy directive *> anyChar)

首先检查在当前位置,我们在抓取角色之前没有查看指令。不需要try子句,因为如果失败,则会在不消耗输入的情况下失败。 (notFollowedBy从不消耗输入,根据文档。)

parse p3 "" "okay" -- returns Right "okay"
parse p3 "" "okay {{}}" -- return Right "okay "
parse p3 "" "{{fails}}"  -- correctly fails w/o consuming input

所以,用你的原始例子:

txt = Left <$> many1 (notFollowedBy directive *> anyChar)

应该可以正常工作。

答案 1 :(得分:0)

many1 (anyChar <* notFollowedBy directive)

这只解析未遵循指令的字符。

{{ value }}

This is normal Text

{{ expression }}

当解析中间的文本时,它将停在最后t,在指令未消耗之前留下换行符(因为它是一个字符后跟一个指令),所以下一次迭代,你尝试解析指令而你失败了。然后你在那个换行符上重试txt,解析器希望它不会被一个指令跟着,但它找到一个,因此错误。

答案 2 :(得分:0)

replace-megaparsec 是用于使用解析器进行搜索和替换的库。的 搜索和替换功能是 streamEdit, 可以找到您的{{ value }}模式,然后替换为其他文本。

streamEdit是基于通用版本构建的 您的template函数中 sepCap

import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Char

input = unlines
    [ "{{ value }}"
    , ""
    , "This is normal Text"
    , ""
    , "{{ expression }}"
    ]

directive :: Parsec Void String String
directive = do
    _ <- string "{{"
    txt <- manyTill anySingle (string "}}")
    return  txt

editor k = fmap toUpper k

streamEdit directive editor input
 VALUE 

This is normal Text

 EXPRESSION