我真的很想在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
并且应该成功;显然,其他事情正在发生。我
难以弄清楚如何解析之间之间的其他事情
解析器大多重叠。
我喜欢一些关于如何更具特色的结构的提示,如果我以愚蠢的方式做某事,请告诉我。干杯!谢谢你的时间!
答案 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