Parsec:处理重叠的解析器
Parsec: Handling Overlapping Parsers
我对 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)
应该抓住所有字符直到下一个指令开始,然后应该失败并且 return 字符列表直到那一点;然后
它应该回落到之前的 many
并且应该再次尝试解析 dir
并且应该会成功;显然还有其他事情正在发生。我是
无法弄清楚如何解析事物 between other things when
解析器大部分重叠。
我想要一些关于如何更地道地构建它的提示,如果我以愚蠢的方式做某事,请告诉我。干杯!感谢您的宝贵时间!
many1 (anyChar <* notFollowedBy directive)
这只解析后面没有指令的字符。
{{ value }}
This is normal Text
{{ expression }}
当解析中间的文本时,它会在最后一个 t
处停止,留下未使用指令之前的换行符(因为它是一个字符后跟一个指令),所以下一次迭代,您尝试解析指令但失败了。然后你在那个换行符上重试 txt
,解析器希望它后面没有指令,但它找到了一个,因此错误。
你有几个问题。首先,在 Parsec 中,如果解析器使用任何输入然后失败,那就是一个错误。所以,当解析器:
anyChar <* notFollowedBy directive
失败(因为字符是后跟一个指令),它失败after 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
仅使用指令后面没有的字符,因此排除了指令之前的 space ,并且您无法在解析器中使用 的字符是后面跟着一个指令,所以卡住了。
你实际上想要这样的东西:
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)
应该可以正常工作。
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
我对 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)
应该抓住所有字符直到下一个指令开始,然后应该失败并且 return 字符列表直到那一点;然后
它应该回落到之前的 many
并且应该再次尝试解析 dir
并且应该会成功;显然还有其他事情正在发生。我是
无法弄清楚如何解析事物 between other things when
解析器大部分重叠。
我想要一些关于如何更地道地构建它的提示,如果我以愚蠢的方式做某事,请告诉我。干杯!感谢您的宝贵时间!
many1 (anyChar <* notFollowedBy directive)
这只解析后面没有指令的字符。
{{ value }}
This is normal Text
{{ expression }}
当解析中间的文本时,它会在最后一个 t
处停止,留下未使用指令之前的换行符(因为它是一个字符后跟一个指令),所以下一次迭代,您尝试解析指令但失败了。然后你在那个换行符上重试 txt
,解析器希望它后面没有指令,但它找到了一个,因此错误。
你有几个问题。首先,在 Parsec 中,如果解析器使用任何输入然后失败,那就是一个错误。所以,当解析器:
anyChar <* notFollowedBy directive
失败(因为字符是后跟一个指令),它失败after 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
仅使用指令后面没有的字符,因此排除了指令之前的 space ,并且您无法在解析器中使用 的字符是后面跟着一个指令,所以卡住了。
你实际上想要这样的东西:
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)
应该可以正常工作。
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